Browse Source

Move `arguments` object creation to opcode (#3432)

pull/3448/head
Haled Odat 1 year ago committed by GitHub
parent
commit
329bf3b299
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      boa_engine/src/bytecompiler/declarations.rs
  2. 2
      boa_engine/src/bytecompiler/mod.rs
  3. 5
      boa_engine/src/context/mod.rs
  4. 68
      boa_engine/src/object/internal_methods/function.rs
  5. 38
      boa_engine/src/vm/code_block.rs
  6. 8
      boa_engine/src/vm/flowgraph/mod.rs
  7. 64
      boa_engine/src/vm/opcode/arguments.rs
  8. 53
      boa_engine/src/vm/opcode/define/mod.rs
  9. 38
      boa_engine/src/vm/opcode/mod.rs

28
boa_engine/src/bytecompiler/declarations.rs

@ -3,6 +3,7 @@ use std::rc::Rc;
use crate::{ use crate::{
bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind}, bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind},
environments::CompileTimeEnvironment, environments::CompileTimeEnvironment,
js_string,
vm::{create_function_object_fast, BindingOpcode, CodeBlockFlags, Opcode}, vm::{create_function_object_fast, BindingOpcode, CodeBlockFlags, Opcode},
JsNativeError, JsResult, JsNativeError, JsResult,
}; };
@ -285,6 +286,7 @@ impl ByteCompiler<'_, '_> {
let function = create_function_object_fast(code, self.context); let function = create_function_object_fast(code, self.context);
// c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
let name = js_string!(self.interner().resolve_expect(name.sym()).utf16());
self.context self.context
.create_global_function_binding(name, function, false)?; .create_global_function_binding(name, function, false)?;
} }
@ -726,14 +728,17 @@ impl ByteCompiler<'_, '_> {
// c. If varEnv is a Global Environment Record, then // c. If varEnv is a Global Environment Record, then
if var_env.is_global() { if var_env.is_global() {
// Ensures global functions are printed when generating the global flowgraph. // Ensures global functions are printed when generating the global flowgraph.
let _ = self.push_function_to_constants(code.clone()); let index = self.push_function_to_constants(code.clone());
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
let function = create_function_object_fast(code, self.context); self.emit_with_varying_operand(Opcode::GetFunction, index);
// i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true). // i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true).
self.context let name_index = self.get_or_insert_name(name);
.create_global_function_binding(name, function, true)?; self.emit(
Opcode::CreateGlobalFunctionBinding,
&[Operand::Bool(true), Operand::Varying(name_index)],
);
} }
// d. Else, // d. Else,
else { else {
@ -915,14 +920,19 @@ impl ByteCompiler<'_, '_> {
// NOTE(HalidOdat): Has been moved up, so "arguments" gets registed as // NOTE(HalidOdat): Has been moved up, so "arguments" gets registed as
// the first binding in the environment with index 0. // the first binding in the environment with index 0.
if arguments_object_needed { if arguments_object_needed {
// Note: This happens at runtime.
// a. If strict is true or simpleParameterList is false, then // a. If strict is true or simpleParameterList is false, then
if strict || !formals.is_simple() {
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList). // i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
self.emit_opcode(Opcode::CreateUnmappedArgumentsObject);
}
// b. Else, // b. Else,
else {
// i. NOTE: A mapped argument object is only provided for non-strict functions // i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter // that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters. // default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
self.emit_opcode(Opcode::CreateMappedArgumentsObject);
}
// c. If strict is true, then // c. If strict is true, then
if strict { if strict {
@ -937,7 +947,8 @@ impl ByteCompiler<'_, '_> {
env.create_mutable_binding(arguments, false); env.create_mutable_binding(arguments, false);
} }
self.code_block_flags |= CodeBlockFlags::NEEDS_ARGUMENTS_OBJECT; // e. Perform ! env.InitializeBinding("arguments", ao).
self.emit_binding(BindingOpcode::InitLexical, arguments);
} }
// 21. For each String paramName of parameterNames, do // 21. For each String paramName of parameterNames, do
@ -961,13 +972,10 @@ impl ByteCompiler<'_, '_> {
// 22. If argumentsObjectNeeded is true, then // 22. If argumentsObjectNeeded is true, then
if arguments_object_needed { if arguments_object_needed {
// MOVED: a-d. // MOVED: a-e.
// //
// NOTE(HalidOdat): Has been moved up, see comment above. // NOTE(HalidOdat): Has been moved up, see comment above.
// Note: This happens at runtime.
// e. Perform ! env.InitializeBinding("arguments", ao).
// f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ».
parameter_names.push(arguments); parameter_names.push(arguments);
} }

2
boa_engine/src/bytecompiler/mod.rs

@ -275,7 +275,7 @@ pub struct ByteCompiler<'ctx, 'host> {
json_parse: bool, json_parse: bool,
// TODO: remove when we separate scripts from the context // TODO: remove when we separate scripts from the context
context: &'ctx mut Context<'host>, pub(crate) context: &'ctx mut Context<'host>,
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
annex_b_function_names: Vec<Identifier>, annex_b_function_names: Vec<Identifier>,

5
boa_engine/src/context/mod.rs

@ -718,7 +718,7 @@ impl Context<'_> {
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
pub(crate) fn create_global_function_binding( pub(crate) fn create_global_function_binding(
&mut self, &mut self,
name: Identifier, name: JsString,
function: JsObject, function: JsObject,
configurable: bool, configurable: bool,
) -> JsResult<()> { ) -> JsResult<()> {
@ -727,8 +727,7 @@ impl Context<'_> {
let global_object = self.realm().global_object().clone(); let global_object = self.realm().global_object().clone();
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16()); let existing_prop = global_object.__get_own_property__(&name.clone().into(), self)?;
let existing_prop = global_object.__get_own_property__(&name, self)?;
// 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
let desc = if existing_prop.is_none() let desc = if existing_prop.is_none()

68
boa_engine/src/object/internal_methods/function.rs

@ -1,5 +1,5 @@
use crate::{ use crate::{
builtins::function::{arguments::Arguments, ThisMode}, builtins::function::ThisMode,
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
environments::{FunctionSlots, ThisBindingStatus}, environments::{FunctionSlots, ThisBindingStatus},
native_function::NativeFunctionObject, native_function::NativeFunctionObject,
@ -121,39 +121,6 @@ pub(crate) fn function_call(
.push_lexical(code.constant_compile_time_environment(last_env)); .push_lexical(code.constant_compile_time_environment(last_env));
} }
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let args = context.vm.stack[(fp + CallFrame::FIRST_ARGUMENT_POSITION)..].to_vec();
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(&args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
function_object,
&code.params,
&args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
Ok(CallValue::Ready) Ok(CallValue::Ready)
} }
@ -257,39 +224,6 @@ fn function_construct(
.push_lexical(code.constant_compile_time_environment(last_env)); .push_lexical(code.constant_compile_time_environment(last_env));
} }
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let args = context.vm.stack[at..].to_vec();
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(&args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
this_function_object,
&code.params,
&args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
// Insert `this` value // Insert `this` value
context context
.vm .vm

38
boa_engine/src/vm/code_block.rs

@ -59,22 +59,17 @@ bitflags! {
/// Does this function have a parameters environment. /// Does this function have a parameters environment.
const PARAMETERS_ENV_BINDINGS = 0b0000_1000; const PARAMETERS_ENV_BINDINGS = 0b0000_1000;
/// Does this function need a `"arguments"` object.
///
/// The `"arguments"` binding is the first binding.
const NEEDS_ARGUMENTS_OBJECT = 0b0001_0000;
/// The `[[ClassFieldInitializerName]]` internal slot. /// The `[[ClassFieldInitializerName]]` internal slot.
const IN_CLASS_FIELD_INITIALIZER = 0b0010_0000; const IN_CLASS_FIELD_INITIALIZER = 0b0001_0000;
/// `[[ConstructorKind]]` /// `[[ConstructorKind]]`
const IS_DERIVED_CONSTRUCTOR = 0b0100_0000; const IS_DERIVED_CONSTRUCTOR = 0b0010_0000;
const IS_ASYNC = 0b1000_0000; const IS_ASYNC = 0b0100_0000;
const IS_GENERATOR = 0b0001_0000_0000; const IS_GENERATOR = 0b0000_1000_0000;
/// Arrow and method functions don't have `"prototype"` property. /// Arrow and method functions don't have `"prototype"` property.
const HAS_PROTOTYPE_PROPERTY = 0b0010_0000_0000; const HAS_PROTOTYPE_PROPERTY = 0b0001_0000_0000;
/// Trace instruction execution to `stdout`. /// Trace instruction execution to `stdout`.
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
@ -233,13 +228,6 @@ impl CodeBlock {
.contains(CodeBlockFlags::PARAMETERS_ENV_BINDINGS) .contains(CodeBlockFlags::PARAMETERS_ENV_BINDINGS)
} }
/// Does this function need a `"arguments"` object.
pub(crate) fn needs_arguments_object(&self) -> bool {
self.flags
.get()
.contains(CodeBlockFlags::NEEDS_ARGUMENTS_OBJECT)
}
/// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value. /// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value.
pub(crate) fn in_class_field_initializer(&self) -> bool { pub(crate) fn in_class_field_initializer(&self) -> bool {
self.flags self.flags
@ -526,6 +514,15 @@ impl CodeBlock {
Instruction::CreateIteratorResult { done } => { Instruction::CreateIteratorResult { done } => {
format!("done: {done}") format!("done: {done}")
} }
Instruction::CreateGlobalFunctionBinding {
name_index,
configurable,
} => {
let name = self
.constant_string(name_index.value() as usize)
.to_std_string_escaped();
format!("name: {name}, configurable: {configurable}")
}
Instruction::Pop Instruction::Pop
| Instruction::Dup | Instruction::Dup
| Instruction::Swap | Instruction::Swap
@ -646,6 +643,8 @@ impl CodeBlock {
| Instruction::GetReturnValue | Instruction::GetReturnValue
| Instruction::SetReturnValue | Instruction::SetReturnValue
| Instruction::BindThisValue | Instruction::BindThisValue
| Instruction::CreateMappedArgumentsObject
| Instruction::CreateUnmappedArgumentsObject
| Instruction::Nop => String::new(), | Instruction::Nop => String::new(),
Instruction::U16Operands Instruction::U16Operands
@ -707,10 +706,7 @@ impl CodeBlock {
| Instruction::Reserved55 | Instruction::Reserved55
| Instruction::Reserved56 | Instruction::Reserved56
| Instruction::Reserved57 | Instruction::Reserved57
| Instruction::Reserved58 | Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved59
| Instruction::Reserved60
| Instruction::Reserved61 => unreachable!("Reserved opcodes are unrechable"),
} }
} }
} }

8
boa_engine/src/vm/flowgraph/mod.rs

@ -451,6 +451,9 @@ impl CodeBlock {
| Instruction::MaybeException | Instruction::MaybeException
| Instruction::CheckReturn | Instruction::CheckReturn
| Instruction::BindThisValue | Instruction::BindThisValue
| Instruction::CreateMappedArgumentsObject
| Instruction::CreateUnmappedArgumentsObject
| Instruction::CreateGlobalFunctionBinding { .. }
| Instruction::Nop => { | Instruction::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
@ -517,10 +520,7 @@ impl CodeBlock {
| Instruction::Reserved55 | Instruction::Reserved55
| Instruction::Reserved56 | Instruction::Reserved56
| Instruction::Reserved57 | Instruction::Reserved57
| Instruction::Reserved58 | Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved59
| Instruction::Reserved60
| Instruction::Reserved61 => unreachable!("Reserved opcodes are unrechable"),
} }
} }

64
boa_engine/src/vm/opcode/arguments.rs

@ -0,0 +1,64 @@
use crate::{
builtins::function::arguments::Arguments,
vm::{CallFrame, CompletionType},
Context, JsResult,
};
use super::Operation;
/// `CreateMappedArgumentsObject` implements the Opcode Operation for `Opcode::CreateMappedArgumentsObject`
///
/// Operation:
/// - TODO: doc
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateMappedArgumentsObject;
impl Operation for CreateMappedArgumentsObject {
const NAME: &'static str = "CreateMappedArgumentsObject";
const INSTRUCTION: &'static str = "INST - CreateMappedArgumentsObject";
const COST: u8 = 8;
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let arguments_start = context.vm.frame().fp as usize + CallFrame::FIRST_ARGUMENT_POSITION;
let function_object = context
.vm
.frame()
.function(&context.vm)
.clone()
.expect("there should be a function object");
let code = context.vm.frame().code_block().clone();
let args = context.vm.stack[arguments_start..].to_vec();
let env = context.vm.environments.current();
let arguments = Arguments::create_mapped_arguments_object(
&function_object,
&code.params,
&args,
env.declarative_expect(),
context,
);
context.vm.push(arguments);
Ok(CompletionType::Normal)
}
}
/// `CreateUnmappedArgumentsObject` implements the Opcode Operation for `Opcode::CreateUnmappedArgumentsObject`
///
/// Operation:
/// - TODO: doc
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateUnmappedArgumentsObject;
impl Operation for CreateUnmappedArgumentsObject {
const NAME: &'static str = "CreateUnmappedArgumentsObject";
const INSTRUCTION: &'static str = "INST - CreateUnmappedArgumentsObject";
const COST: u8 = 4;
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let arguments_start = context.vm.frame().fp as usize + CallFrame::FIRST_ARGUMENT_POSITION;
let args = context.vm.stack[arguments_start..].to_vec();
let arguments = Arguments::create_unmapped_arguments_object(&args, context);
context.vm.push(arguments);
Ok(CompletionType::Normal)
}
}

53
boa_engine/src/vm/opcode/define/mod.rs

@ -141,3 +141,56 @@ impl Operation for PutLexicalValue {
Self::operation(context, index as usize) Self::operation(context, index as usize)
} }
} }
/// `CreateGlobalFunctionBinding` implements the Opcode Operation for `Opcode::CreateGlobalFunctionBinding`
///
/// Operation:
/// - Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateGlobalFunctionBinding;
impl CreateGlobalFunctionBinding {
#[allow(clippy::unnecessary_wraps)]
fn operation(
context: &mut Context<'_>,
index: usize,
configurable: bool,
) -> JsResult<CompletionType> {
let name = context.vm.frame().code_block().constant_string(index);
let value = context.vm.pop();
let function = value
.as_object()
.expect("valeu should be an function")
.clone();
context.create_global_function_binding(name, function, configurable)?;
Ok(CompletionType::Normal)
}
}
impl Operation for CreateGlobalFunctionBinding {
const NAME: &'static str = "CreateGlobalFunctionBinding";
const INSTRUCTION: &'static str = "INST - CreateGlobalFunctionBinding";
const COST: u8 = 2;
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index, configurable)
}
}

38
boa_engine/src/vm/opcode/mod.rs

@ -4,6 +4,7 @@ use std::iter::FusedIterator;
use crate::{vm::CompletionType, Context, JsResult, JsValue}; use crate::{vm::CompletionType, Context, JsResult, JsValue};
// Operation modules // Operation modules
mod arguments;
mod r#await; mod r#await;
mod binary_ops; mod binary_ops;
mod call; mod call;
@ -35,6 +36,8 @@ mod value;
// Operation structs // Operation structs
#[doc(inline)] #[doc(inline)]
pub(crate) use arguments::*;
#[doc(inline)]
pub(crate) use binary_ops::*; pub(crate) use binary_ops::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use call::*; pub(crate) use call::*;
@ -2053,6 +2056,35 @@ generate_opcodes! {
/// Stack: **=>** /// Stack: **=>**
PopPrivateEnvironment, PopPrivateEnvironment,
/// Creates a mapped `arguments` object.
///
/// Performs [`10.4.4.7 CreateMappedArgumentsObject ( func, formals, argumentsList, env )`]
///
/// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject
///
/// Operands:
///
/// Stack: **=>** `arguments`
CreateMappedArgumentsObject,
/// Creates an unmapped `arguments` object.
///
/// Performs: [`10.4.4.6 CreateUnmappedArgumentsObject ( argumentsList )`]
///
/// [spec]: https://tc39.es/ecma262/#sec-createmappedargumentsobject
///
/// Stack: **=>** `arguments`
CreateUnmappedArgumentsObject,
/// Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec]
///
/// Operands: configurable: `bool`, `name_index`: `VaryingOperand`
///
/// Stack: `value` **=>**
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
CreateGlobalFunctionBinding { configurable: bool, name_index: VaryingOperand },
/// No-operation instruction, does nothing. /// No-operation instruction, does nothing.
/// ///
/// Operands: /// Operands:
@ -2190,12 +2222,6 @@ generate_opcodes! {
Reserved57 => Reserved, Reserved57 => Reserved,
/// Reserved [`Opcode`]. /// Reserved [`Opcode`].
Reserved58 => Reserved, Reserved58 => Reserved,
/// Reserved [`Opcode`].
Reserved59 => Reserved,
/// Reserved [`Opcode`].
Reserved60 => Reserved,
/// Reserved [`Opcode`].
Reserved61 => Reserved,
} }
/// Specific opcodes for bindings. /// Specific opcodes for bindings.

Loading…
Cancel
Save