mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-31 21:29:06 +00:00
LibJS: Emit bytecode for function declaration instantiation
By doing that all instructions required for instantiation are emitted once in compilation and then reused for subsequent calls, instead of running generic instantiation process for each call.
This commit is contained in:
parent
89a007327a
commit
a4f70986a0
Notes:
sideshowbarker
2024-07-17 10:31:19 +09:00
Author: https://github.com/kalenikaliaksandr
Commit: a4f70986a0
Pull-request: https://github.com/SerenityOS/serenity/pull/24272
15 changed files with 440 additions and 449 deletions
|
@ -12,6 +12,7 @@
|
|||
#include <LibJS/Bytecode/Instruction.h>
|
||||
#include <LibJS/Bytecode/Op.h>
|
||||
#include <LibJS/Bytecode/Register.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
@ -26,21 +27,190 @@ Generator::Generator(VM& vm)
|
|||
{
|
||||
}
|
||||
|
||||
CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ASTNode const& node, ReadonlySpan<FunctionParameter> parameters, FunctionKind enclosing_function_kind)
|
||||
CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function)
|
||||
{
|
||||
if (function.m_has_parameter_expressions) {
|
||||
emit<Op::CreateLexicalEnvironment>();
|
||||
}
|
||||
|
||||
for (auto const& parameter_name : function.m_parameter_names) {
|
||||
if (parameter_name.value == ECMAScriptFunctionObject::ParameterIsLocal::No) {
|
||||
auto id = intern_identifier(parameter_name.key);
|
||||
emit<Op::CreateVariable>(id, Op::EnvironmentMode::Lexical, false);
|
||||
if (function.m_has_duplicates) {
|
||||
emit<Op::SetVariable>(id, add_constant(js_undefined()), next_environment_variable_cache(), Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Lexical);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function.m_arguments_object_needed) {
|
||||
if (function.m_strict || !function.has_simple_parameter_list()) {
|
||||
emit<Op::CreateArguments>(Op::CreateArguments::Kind::Unmapped, function.m_strict);
|
||||
} else {
|
||||
emit<Op::CreateArguments>(Op::CreateArguments::Kind::Mapped, function.m_strict);
|
||||
}
|
||||
}
|
||||
|
||||
auto const& formal_parameters = function.formal_parameters();
|
||||
for (u32 param_index = 0; param_index < formal_parameters.size(); ++param_index) {
|
||||
auto const& parameter = formal_parameters[param_index];
|
||||
|
||||
if (parameter.is_rest) {
|
||||
auto argument_reg = allocate_register();
|
||||
emit<Op::CreateRestParams>(argument_reg.operand(), param_index);
|
||||
emit<Op::SetArgument>(param_index, argument_reg.operand());
|
||||
} else if (parameter.default_value) {
|
||||
auto& if_undefined_block = make_block();
|
||||
auto& if_not_undefined_block = make_block();
|
||||
|
||||
auto argument_reg = allocate_register();
|
||||
emit<Op::GetArgument>(argument_reg.operand(), param_index);
|
||||
|
||||
emit<Op::JumpUndefined>(
|
||||
argument_reg.operand(),
|
||||
Label { if_undefined_block },
|
||||
Label { if_not_undefined_block });
|
||||
|
||||
switch_to_basic_block(if_undefined_block);
|
||||
auto operand = TRY(parameter.default_value->generate_bytecode(*this));
|
||||
emit<Op::SetArgument>(param_index, *operand);
|
||||
emit<Op::Jump>(Label { if_not_undefined_block });
|
||||
|
||||
switch_to_basic_block(if_not_undefined_block);
|
||||
}
|
||||
|
||||
if (auto const* identifier = parameter.binding.get_pointer<NonnullRefPtr<Identifier const>>(); identifier) {
|
||||
if ((*identifier)->is_local()) {
|
||||
auto local_variable_index = (*identifier)->local_variable_index();
|
||||
emit<Op::GetArgument>(local(local_variable_index), param_index);
|
||||
set_local_initialized((*identifier)->local_variable_index());
|
||||
} else {
|
||||
auto id = intern_identifier((*identifier)->string());
|
||||
auto init_mode = function.m_has_duplicates ? Op::SetVariable::InitializationMode::Set : Op::SetVariable::InitializationMode::Initialize;
|
||||
auto argument_reg = allocate_register();
|
||||
emit<Op::GetArgument>(argument_reg.operand(), param_index);
|
||||
emit<Op::SetVariable>(id, argument_reg.operand(),
|
||||
next_environment_variable_cache(),
|
||||
init_mode,
|
||||
Op::EnvironmentMode::Lexical);
|
||||
}
|
||||
} else if (auto const* binding_pattern = parameter.binding.get_pointer<NonnullRefPtr<BindingPattern const>>(); binding_pattern) {
|
||||
auto input_operand = allocate_register();
|
||||
emit<Op::GetArgument>(input_operand.operand(), param_index);
|
||||
auto init_mode = function.m_has_duplicates ? Op::SetVariable::InitializationMode::Set : Bytecode::Op::SetVariable::InitializationMode::Initialize;
|
||||
TRY((*binding_pattern)->generate_bytecode(*this, init_mode, input_operand, false));
|
||||
}
|
||||
}
|
||||
|
||||
ScopeNode const* scope_body = nullptr;
|
||||
if (is<ScopeNode>(*function.m_ecmascript_code))
|
||||
scope_body = static_cast<ScopeNode const*>(function.m_ecmascript_code.ptr());
|
||||
|
||||
if (!function.m_has_parameter_expressions) {
|
||||
if (scope_body) {
|
||||
for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) {
|
||||
auto const& id = variable_to_initialize.identifier;
|
||||
if (id.is_local()) {
|
||||
emit<Op::Mov>(local(id.local_variable_index()), add_constant(js_undefined()));
|
||||
} else {
|
||||
auto intern_id = intern_identifier(id.string());
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false);
|
||||
emit<Op::SetVariable>(intern_id, add_constant(js_undefined()), next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Var);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit<Op::CreateVariableEnvironment>();
|
||||
|
||||
if (scope_body) {
|
||||
for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) {
|
||||
auto const& id = variable_to_initialize.identifier;
|
||||
auto initial_value = allocate_register();
|
||||
if (!variable_to_initialize.parameter_binding || variable_to_initialize.function_name) {
|
||||
emit<Op::Mov>(initial_value, add_constant(js_undefined()));
|
||||
} else {
|
||||
if (id.is_local()) {
|
||||
emit<Op::Mov>(initial_value, local(id.local_variable_index()));
|
||||
} else {
|
||||
emit<Op::GetVariable>(initial_value, intern_identifier(id.string()), next_environment_variable_cache());
|
||||
}
|
||||
}
|
||||
|
||||
if (id.is_local()) {
|
||||
emit<Op::Mov>(local(id.local_variable_index()), initial_value);
|
||||
} else {
|
||||
auto intern_id = intern_identifier(id.string());
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false);
|
||||
emit<Op::SetVariable>(intern_id, initial_value, next_environment_variable_cache(), Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Var);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function.m_strict && scope_body) {
|
||||
for (auto const& function_name : function.m_function_names_to_initialize_binding) {
|
||||
auto intern_id = intern_identifier(function_name);
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false);
|
||||
emit<Op::SetVariable>(intern_id, add_constant(js_undefined()), next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Var);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function.m_strict) {
|
||||
bool can_elide_declarative_environment = !function.m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations());
|
||||
if (!can_elide_declarative_environment) {
|
||||
emit<Op::CreateLexicalEnvironment>();
|
||||
}
|
||||
}
|
||||
|
||||
if (scope_body) {
|
||||
MUST(scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
MUST(declaration.for_each_bound_identifier([&](auto const& id) {
|
||||
if (id.is_local()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit<Op::CreateVariable>(intern_identifier(id.string()),
|
||||
Op::EnvironmentMode::Lexical,
|
||||
declaration.is_constant_declaration(),
|
||||
false,
|
||||
declaration.is_constant_declaration());
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto const& declaration : function.m_functions_to_initialize) {
|
||||
auto function = allocate_register();
|
||||
emit<Op::NewFunction>(function, declaration, OptionalNone {});
|
||||
if (declaration.name_identifier()->is_local()) {
|
||||
emit<Op::Mov>(local(declaration.name_identifier()->local_variable_index()), function);
|
||||
} else {
|
||||
emit<Op::SetVariable>(intern_identifier(declaration.name()), function, next_environment_variable_cache(), Op::SetVariable::InitializationMode::Set, Op::EnvironmentMode::Var);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::emit_function_body_bytecode(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GCPtr<ECMAScriptFunctionObject const> function)
|
||||
{
|
||||
Generator generator(vm);
|
||||
|
||||
for (auto const& parameter : parameters) {
|
||||
if (auto const* identifier = parameter.binding.get_pointer<NonnullRefPtr<Identifier const>>();
|
||||
identifier && (*identifier)->is_local()) {
|
||||
generator.set_local_initialized((*identifier)->local_variable_index());
|
||||
}
|
||||
}
|
||||
|
||||
generator.switch_to_basic_block(generator.make_block());
|
||||
SourceLocationScope scope(generator, node);
|
||||
generator.m_enclosing_function_kind = enclosing_function_kind;
|
||||
if (generator.is_in_generator_or_async_function()) {
|
||||
if (generator.is_in_async_function() && !generator.is_in_generator_function()) {
|
||||
// Immediately yield with no value.
|
||||
auto& start_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::Yield>(Label { start_block }, generator.add_constant(js_undefined()));
|
||||
generator.switch_to_basic_block(start_block);
|
||||
// NOTE: This doesn't have to handle received throw/return completions, as GeneratorObject::resume_abrupt
|
||||
// will not enter the generator from the SuspendedStart state and immediately completes the generator.
|
||||
}
|
||||
|
||||
if (function)
|
||||
TRY(generator.emit_function_declaration_instantiation(*function));
|
||||
|
||||
if (generator.is_in_generator_function()) {
|
||||
// Immediately yield with no value.
|
||||
auto& start_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::Yield>(Label { start_block }, generator.add_constant(js_undefined()));
|
||||
|
@ -72,8 +242,6 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ASTN
|
|||
is_strict_mode = static_cast<FunctionBody const&>(node).in_strict_mode();
|
||||
else if (is<FunctionDeclaration>(node))
|
||||
is_strict_mode = static_cast<FunctionDeclaration const&>(node).is_strict_mode();
|
||||
else if (is<FunctionExpression>(node))
|
||||
is_strict_mode = static_cast<FunctionExpression const&>(node).is_strict_mode();
|
||||
|
||||
size_t size_needed = 0;
|
||||
for (auto& block : generator.m_root_basic_blocks) {
|
||||
|
@ -214,6 +382,16 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ASTN
|
|||
return executable;
|
||||
}
|
||||
|
||||
CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate_from_ast_node(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind)
|
||||
{
|
||||
return emit_function_body_bytecode(vm, node, enclosing_function_kind, {});
|
||||
}
|
||||
|
||||
CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate_from_function(VM& vm, ECMAScriptFunctionObject const& function)
|
||||
{
|
||||
return emit_function_body_bytecode(vm, function.ecmascript_code(), function.kind(), &function);
|
||||
}
|
||||
|
||||
void Generator::grow(size_t additional_size)
|
||||
{
|
||||
VERIFY(m_current_basic_block);
|
||||
|
|
|
@ -32,7 +32,11 @@ public:
|
|||
Function,
|
||||
Block,
|
||||
};
|
||||
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> generate(VM&, ASTNode const&, ReadonlySpan<FunctionParameter> parameters, FunctionKind = FunctionKind::Normal);
|
||||
|
||||
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> generate_from_ast_node(VM&, ASTNode const&, FunctionKind = FunctionKind::Normal);
|
||||
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> generate_from_function(VM&, ECMAScriptFunctionObject const& function);
|
||||
|
||||
CodeGenerationErrorOr<void> emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function);
|
||||
|
||||
[[nodiscard]] ScopedOperand allocate_register();
|
||||
[[nodiscard]] ScopedOperand local(u32 local_index);
|
||||
|
@ -301,6 +305,8 @@ public:
|
|||
private:
|
||||
VM& m_vm;
|
||||
|
||||
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> emit_function_body_bytecode(VM&, ASTNode const&, FunctionKind, GCPtr<ECMAScriptFunctionObject const>);
|
||||
|
||||
enum class JumpType {
|
||||
Continue,
|
||||
Break,
|
||||
|
|
|
@ -30,7 +30,10 @@
|
|||
O(ContinuePendingUnwind) \
|
||||
O(CopyObjectExcludingProperties) \
|
||||
O(CreateLexicalEnvironment) \
|
||||
O(CreateVariableEnvironment) \
|
||||
O(CreateVariable) \
|
||||
O(CreateRestParams) \
|
||||
O(CreateArguments) \
|
||||
O(Decrement) \
|
||||
O(DeleteById) \
|
||||
O(DeleteByIdWithThis) \
|
||||
|
@ -43,6 +46,7 @@
|
|||
O(EnterObjectEnvironment) \
|
||||
O(EnterUnwindContext) \
|
||||
O(Exp) \
|
||||
O(GetArgument) \
|
||||
O(GetById) \
|
||||
O(GetByIdWithThis) \
|
||||
O(GetByValue) \
|
||||
|
@ -114,6 +118,7 @@
|
|||
O(Return) \
|
||||
O(RightShift) \
|
||||
O(ScheduleJump) \
|
||||
O(SetArgument) \
|
||||
O(SetVariable) \
|
||||
O(SetLocal) \
|
||||
O(StrictlyEquals) \
|
||||
|
|
|
@ -228,7 +228,7 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Envir
|
|||
|
||||
// 13. If result.[[Type]] is normal, then
|
||||
if (result.type() == Completion::Type::Normal) {
|
||||
auto executable_result = JS::Bytecode::Generator::generate(vm, script, {});
|
||||
auto executable_result = JS::Bytecode::Generator::generate_from_ast_node(vm, script, {});
|
||||
|
||||
if (executable_result.is_error()) {
|
||||
if (auto error_string = executable_result.error().to_string(); error_string.is_error())
|
||||
|
@ -345,6 +345,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
|||
}
|
||||
|
||||
auto& running_execution_context = vm().running_execution_context();
|
||||
auto* arguments = running_execution_context.arguments.data();
|
||||
auto* locals = running_execution_context.locals.data();
|
||||
auto& accumulator = this->accumulator();
|
||||
auto& executable = current_executable();
|
||||
|
@ -387,6 +388,18 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
|||
DISPATCH_NEXT(SetLocal);
|
||||
}
|
||||
|
||||
handle_GetArgument: {
|
||||
auto const& instruction = *reinterpret_cast<Op::GetArgument const*>(&bytecode[program_counter]);
|
||||
set(instruction.dst(), arguments[instruction.index()]);
|
||||
DISPATCH_NEXT(GetArgument);
|
||||
}
|
||||
|
||||
handle_SetArgument: {
|
||||
auto const& instruction = *reinterpret_cast<Op::SetArgument const*>(&bytecode[program_counter]);
|
||||
arguments[instruction.index()] = get(instruction.src());
|
||||
DISPATCH_NEXT(SetArgument);
|
||||
}
|
||||
|
||||
handle_Mov: {
|
||||
auto& instruction = *reinterpret_cast<Op::Mov const*>(&bytecode[program_counter]);
|
||||
set(instruction.dst(), get(instruction.src()));
|
||||
|
@ -547,7 +560,10 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
|||
HANDLE_INSTRUCTION(ConcatString);
|
||||
HANDLE_INSTRUCTION(CopyObjectExcludingProperties);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateLexicalEnvironment);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateVariableEnvironment);
|
||||
HANDLE_INSTRUCTION(CreateVariable);
|
||||
HANDLE_INSTRUCTION(CreateRestParams);
|
||||
HANDLE_INSTRUCTION(CreateArguments);
|
||||
HANDLE_INSTRUCTION(Decrement);
|
||||
HANDLE_INSTRUCTION(DeleteById);
|
||||
HANDLE_INSTRUCTION(DeleteByIdWithThis);
|
||||
|
@ -687,6 +703,7 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe
|
|||
if (running_execution_context.registers.size() < executable.number_of_registers)
|
||||
running_execution_context.registers.resize(executable.number_of_registers);
|
||||
|
||||
TemporaryChange restore_arguments { m_arguments, running_execution_context.arguments.span() };
|
||||
TemporaryChange restore_registers { m_registers, running_execution_context.registers.span() };
|
||||
TemporaryChange restore_locals { m_locals, running_execution_context.locals.span() };
|
||||
TemporaryChange restore_constants { m_constants, executable.constants.span() };
|
||||
|
@ -776,9 +793,26 @@ void Interpreter::enter_object_environment(Object& object)
|
|||
running_execution_context.lexical_environment = new_object_environment(object, true, old_environment);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ASTNode const& node, ReadonlySpan<FunctionParameter> parameters, FunctionKind kind, DeprecatedFlyString const& name)
|
||||
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ASTNode const& node, FunctionKind kind, DeprecatedFlyString const& name)
|
||||
{
|
||||
auto executable_result = Bytecode::Generator::generate(vm, node, parameters, kind);
|
||||
auto executable_result = Bytecode::Generator::generate_from_ast_node(vm, node, kind);
|
||||
if (executable_result.is_error())
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));
|
||||
|
||||
auto bytecode_executable = executable_result.release_value();
|
||||
bytecode_executable->name = name;
|
||||
|
||||
if (Bytecode::g_dump_bytecode)
|
||||
bytecode_executable->dump();
|
||||
|
||||
return bytecode_executable;
|
||||
}
|
||||
|
||||
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ECMAScriptFunctionObject const& function)
|
||||
{
|
||||
auto const& name = function.name();
|
||||
|
||||
auto executable_result = Bytecode::Generator::generate_from_function(vm, function);
|
||||
if (executable_result.is_error())
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));
|
||||
|
||||
|
@ -1230,6 +1264,15 @@ void CreateLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter)
|
|||
running_execution_context.saved_lexical_environments.append(make_and_swap_envs(running_execution_context.lexical_environment));
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> CreateVariableEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto& running_execution_context = interpreter.vm().running_execution_context();
|
||||
auto var_environment = new_declarative_environment(*running_execution_context.lexical_environment);
|
||||
running_execution_context.variable_environment = var_environment;
|
||||
running_execution_context.lexical_environment = var_environment;
|
||||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> EnterObjectEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto object = TRY(interpreter.get(m_object).to_object(interpreter.vm()));
|
||||
|
@ -1258,6 +1301,41 @@ ThrowCompletionOr<void> CreateVariable::execute_impl(Bytecode::Interpreter& inte
|
|||
return create_variable(interpreter.vm(), name, m_mode, m_is_global, m_is_immutable, m_is_strict);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> CreateRestParams::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto const& arguments = interpreter.vm().running_execution_context().arguments;
|
||||
auto arguments_count = interpreter.vm().running_execution_context().passed_argument_count;
|
||||
auto array = MUST(Array::create(interpreter.realm(), 0));
|
||||
for (size_t rest_index = m_rest_index; rest_index < arguments_count; ++rest_index)
|
||||
array->indexed_properties().append(arguments[rest_index]);
|
||||
interpreter.set(m_dst, array);
|
||||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> CreateArguments::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto const& function = interpreter.vm().running_execution_context().function;
|
||||
auto const& arguments = interpreter.vm().running_execution_context().arguments;
|
||||
auto const& environment = interpreter.vm().running_execution_context().lexical_environment;
|
||||
|
||||
auto passed_arguments = ReadonlySpan<Value> { arguments.data(), interpreter.vm().running_execution_context().passed_argument_count };
|
||||
Object* arguments_object;
|
||||
if (m_kind == Kind::Mapped) {
|
||||
arguments_object = create_mapped_arguments_object(interpreter.vm(), *function, function->formal_parameters(), passed_arguments, *environment);
|
||||
} else {
|
||||
arguments_object = create_unmapped_arguments_object(interpreter.vm(), passed_arguments);
|
||||
}
|
||||
|
||||
if (m_is_immutable) {
|
||||
MUST(environment->create_immutable_binding(interpreter.vm(), interpreter.vm().names.arguments.as_string(), false));
|
||||
} else {
|
||||
MUST(environment->create_mutable_binding(interpreter.vm(), interpreter.vm().names.arguments.as_string(), false));
|
||||
}
|
||||
MUST(environment->initialize_binding(interpreter.vm(), interpreter.vm().names.arguments.as_string(), arguments_object, Environment::InitializeBindingHint::Normal));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto& vm = interpreter.vm();
|
||||
|
@ -1277,6 +1355,18 @@ ThrowCompletionOr<void> SetLocal::execute_impl(Bytecode::Interpreter&) const
|
|||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> SetArgument::execute_impl(Bytecode::Interpreter&) const
|
||||
{
|
||||
// Handled in the interpreter loop.
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> GetArgument::execute_impl(Bytecode::Interpreter&) const
|
||||
{
|
||||
// Handled in the interpreter loop.
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> GetById::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto base_identifier = interpreter.current_executable().get_identifier(m_base_identifier);
|
||||
|
@ -1959,12 +2049,27 @@ ByteString CreateLexicalEnvironment::to_byte_string_impl(Bytecode::Executable co
|
|||
return "CreateLexicalEnvironment"sv;
|
||||
}
|
||||
|
||||
ByteString CreateVariableEnvironment::to_byte_string_impl(Bytecode::Executable const&) const
|
||||
{
|
||||
return "CreateVariableEnvironment"sv;
|
||||
}
|
||||
|
||||
ByteString CreateVariable::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable";
|
||||
return ByteString::formatted("CreateVariable env:{} immutable:{} global:{} {}", mode_string, m_is_immutable, m_is_global, executable.identifier_table->get(m_identifier));
|
||||
}
|
||||
|
||||
ByteString CreateRestParams::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return ByteString::formatted("CreateRestParams {}, rest_index:{}", format_operand("dst"sv, m_dst, executable), m_rest_index);
|
||||
}
|
||||
|
||||
ByteString CreateArguments::to_byte_string_impl(Bytecode::Executable const&) const
|
||||
{
|
||||
return ByteString::formatted("CreateArguments {} immutable:{}", m_kind == Kind::Mapped ? "mapped"sv : "unmapped"sv, m_is_immutable);
|
||||
}
|
||||
|
||||
ByteString EnterObjectEnvironment::to_byte_string_impl(Executable const& executable) const
|
||||
{
|
||||
return ByteString::formatted("EnterObjectEnvironment {}",
|
||||
|
@ -1988,6 +2093,16 @@ ByteString SetLocal::to_byte_string_impl(Bytecode::Executable const& executable)
|
|||
format_operand("src"sv, src(), executable));
|
||||
}
|
||||
|
||||
ByteString GetArgument::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return ByteString::formatted("GetArgument {}, {}", index(), format_operand("dst"sv, dst(), executable));
|
||||
}
|
||||
|
||||
ByteString SetArgument::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return ByteString::formatted("SetArgument {}, {}", index(), format_operand("src"sv, src(), executable));
|
||||
}
|
||||
|
||||
static StringView property_kind_to_string(PropertyKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
|
|
|
@ -96,6 +96,7 @@ private:
|
|||
GCPtr<Object> m_global_object { nullptr };
|
||||
GCPtr<DeclarativeEnvironment> m_global_declarative_environment { nullptr };
|
||||
Optional<size_t&> m_program_counter;
|
||||
Span<Value> m_arguments;
|
||||
Span<Value> m_registers;
|
||||
Span<Value> m_locals;
|
||||
Span<Value> m_constants;
|
||||
|
@ -103,6 +104,7 @@ private:
|
|||
|
||||
extern bool g_dump_bytecode;
|
||||
|
||||
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM&, ASTNode const&, ReadonlySpan<FunctionParameter>, JS::FunctionKind kind, DeprecatedFlyString const& name);
|
||||
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM&, ASTNode const&, JS::FunctionKind kind, DeprecatedFlyString const& name);
|
||||
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM&, ECMAScriptFunctionObject const&);
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,45 @@ class FunctionExpression;
|
|||
|
||||
namespace JS::Bytecode::Op {
|
||||
|
||||
class CreateRestParams final : public Instruction {
|
||||
public:
|
||||
CreateRestParams(Operand dst, u32 rest_index)
|
||||
: Instruction(Type::CreateRestParams)
|
||||
, m_dst(dst)
|
||||
, m_rest_index(rest_index)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
|
||||
private:
|
||||
Operand m_dst;
|
||||
u32 m_rest_index;
|
||||
};
|
||||
|
||||
class CreateArguments final : public Instruction {
|
||||
public:
|
||||
enum class Kind {
|
||||
Mapped,
|
||||
Unmapped,
|
||||
};
|
||||
|
||||
CreateArguments(Kind kind, bool is_immutable)
|
||||
: Instruction(Type::CreateArguments)
|
||||
, m_kind(kind)
|
||||
, m_is_immutable(is_immutable)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
|
||||
private:
|
||||
Kind m_kind;
|
||||
bool m_is_immutable { false };
|
||||
};
|
||||
|
||||
class Mov final : public Instruction {
|
||||
public:
|
||||
Mov(Operand dst, Operand src)
|
||||
|
@ -413,6 +452,17 @@ public:
|
|||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
};
|
||||
|
||||
class CreateVariableEnvironment final : public Instruction {
|
||||
public:
|
||||
explicit CreateVariableEnvironment()
|
||||
: Instruction(Type::CreateVariableEnvironment)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
};
|
||||
|
||||
class EnterObjectEnvironment final : public Instruction {
|
||||
public:
|
||||
explicit EnterObjectEnvironment(Operand object)
|
||||
|
@ -552,6 +602,46 @@ private:
|
|||
Operand m_src;
|
||||
};
|
||||
|
||||
class SetArgument final : public Instruction {
|
||||
public:
|
||||
SetArgument(size_t index, Operand src)
|
||||
: Instruction(Type::SetArgument)
|
||||
, m_index(index)
|
||||
, m_src(src)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
|
||||
size_t index() const { return m_index; }
|
||||
Operand src() const { return m_src; }
|
||||
|
||||
private:
|
||||
u32 m_index;
|
||||
Operand m_src;
|
||||
};
|
||||
|
||||
class GetArgument final : public Instruction {
|
||||
public:
|
||||
GetArgument(Operand dst, size_t index)
|
||||
: Instruction(Type::GetArgument)
|
||||
, m_index(index)
|
||||
, m_dst(dst)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
|
||||
u32 index() const { return m_index; }
|
||||
Operand dst() const { return m_dst; }
|
||||
|
||||
private:
|
||||
u32 m_index;
|
||||
Operand m_dst;
|
||||
};
|
||||
|
||||
class GetCalleeAndThisFromEnvironment final : public Instruction {
|
||||
public:
|
||||
explicit GetCalleeAndThisFromEnvironment(Operand callee, Operand this_value, IdentifierTableIndex identifier, u32 cache_index)
|
||||
|
|
|
@ -685,7 +685,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
|
|||
|
||||
// 29. If result.[[Type]] is normal, then
|
||||
// a. Set result to the result of evaluating body.
|
||||
auto executable_result = Bytecode::Generator::generate(vm, program, {});
|
||||
auto executable_result = Bytecode::Generator::generate_from_ast_node(vm, program, {});
|
||||
if (executable_result.is_error())
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));
|
||||
|
||||
|
|
|
@ -383,8 +383,14 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu
|
|||
callee_context->locals.resize(m_local_variables_names.size());
|
||||
|
||||
// Non-standard
|
||||
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->program_counter = vm.bytecode_interpreter().program_counter();
|
||||
callee_context->passed_argument_count = arguments_list.size();
|
||||
if (arguments_list.size() < m_formal_parameters.size()) {
|
||||
for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i)
|
||||
callee_context->arguments.append(js_undefined());
|
||||
}
|
||||
|
||||
// 2. Let calleeContext be PrepareForOrdinaryCall(F, undefined).
|
||||
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
|
||||
|
@ -454,8 +460,14 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ECMAScriptFunctionObject::internal_const
|
|||
callee_context->locals.resize(m_local_variables_names.size());
|
||||
|
||||
// Non-standard
|
||||
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->program_counter = vm.bytecode_interpreter().program_counter();
|
||||
callee_context->passed_argument_count = arguments_list.size();
|
||||
if (arguments_list.size() < m_formal_parameters.size()) {
|
||||
for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i)
|
||||
callee_context->arguments.append(js_undefined());
|
||||
}
|
||||
|
||||
// 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
|
||||
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
|
||||
|
@ -541,7 +553,6 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
|
|||
visitor.visit(m_name_string);
|
||||
|
||||
visitor.visit(m_bytecode_executable);
|
||||
visitor.visit(m_default_parameter_bytecode_executables);
|
||||
|
||||
for (auto& field : m_fields) {
|
||||
if (auto* property_key_ptr = field.name.get_pointer<PropertyKey>(); property_key_ptr && property_key_ptr->is_symbol())
|
||||
|
@ -567,391 +578,6 @@ void ECMAScriptFunctionObject::make_method(Object& home_object)
|
|||
// 2. Return unused.
|
||||
}
|
||||
|
||||
// 10.2.11 FunctionDeclarationInstantiation ( func, argumentsList ), https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
|
||||
ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantiation()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let calleeContext be the running execution context.
|
||||
auto& callee_context = vm.running_execution_context();
|
||||
|
||||
// 2. Let code be func.[[ECMAScriptCode]].
|
||||
ScopeNode const* scope_body = nullptr;
|
||||
if (is<ScopeNode>(*m_ecmascript_code))
|
||||
scope_body = static_cast<ScopeNode const*>(m_ecmascript_code.ptr());
|
||||
|
||||
// NOTE: Following steps were executed in ECMAScriptFunctionObject constructor.
|
||||
// 3. Let strict be func.[[Strict]].
|
||||
// 4. Let formals be func.[[FormalParameters]].
|
||||
// 5. Let parameterNames be the BoundNames of formals.
|
||||
// 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false.
|
||||
|
||||
// 7. Let simpleParameterList be IsSimpleParameterList of formals.
|
||||
bool const simple_parameter_list = has_simple_parameter_list();
|
||||
|
||||
// NOTE: Following steps were executed in ECMAScriptFunctionObject constructor.
|
||||
// 8. Let hasParameterExpressions be ContainsExpression of formals.
|
||||
// 9. Let varNames be the VarDeclaredNames of code.
|
||||
// 10. Let varDeclarations be the VarScopedDeclarations of code.
|
||||
// 11. Let lexicalNames be the LexicallyDeclaredNames of code.
|
||||
// 12. Let functionNames be a new empty List.
|
||||
// 13. Let functionsToInitialize be a new empty List.
|
||||
// 14. For each element d of varDeclarations, in reverse List order, do
|
||||
// 15. Let argumentsObjectNeeded be true.
|
||||
// 16. If func.[[ThisMode]] is lexical, then
|
||||
// 17. Else if parameterNames contains "arguments", then
|
||||
// 18. Else if hasParameterExpressions is false, then
|
||||
|
||||
GCPtr<Environment> environment;
|
||||
|
||||
// 19. If strict is true or hasParameterExpressions is false, then
|
||||
if (m_strict || !m_has_parameter_expressions) {
|
||||
// a. NOTE: Only a single Environment Record is needed for the parameters, since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval.
|
||||
// b. Let env be the LexicalEnvironment of calleeContext.
|
||||
environment = callee_context.lexical_environment;
|
||||
}
|
||||
// 20. Else,
|
||||
else {
|
||||
// a. NOTE: A separate Environment Record is needed to ensure that bindings created by direct eval calls in the formal parameter list are outside the environment where parameters are declared.
|
||||
|
||||
// b. Let calleeEnv be the LexicalEnvironment of calleeContext.
|
||||
auto callee_env = callee_context.lexical_environment;
|
||||
|
||||
// c. Let env be NewDeclarativeEnvironment(calleeEnv).
|
||||
environment = new_declarative_environment(*callee_env);
|
||||
|
||||
// d. Assert: The VariableEnvironment of calleeContext is calleeEnv.
|
||||
VERIFY(callee_context.variable_environment == callee_context.lexical_environment);
|
||||
|
||||
// e. Set the LexicalEnvironment of calleeContext to env.
|
||||
callee_context.lexical_environment = environment;
|
||||
}
|
||||
|
||||
// 21. For each String paramName of parameterNames, do
|
||||
for (auto const& parameter_name : m_parameter_names) {
|
||||
// OPTIMIZATION: Parameters that are locals don't need to be added to the environment.
|
||||
if (parameter_name.value == ParameterIsLocal::Yes)
|
||||
continue;
|
||||
|
||||
// a. Let alreadyDeclared be ! env.HasBinding(paramName).
|
||||
|
||||
// b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict functions that do not have parameter default values or rest parameters.
|
||||
|
||||
// c. If alreadyDeclared is false, then
|
||||
// NOTE: alreadyDeclared is always false because we use hash table for parameterNames
|
||||
// i. Perform ! env.CreateMutableBinding(paramName, false).
|
||||
MUST(environment->create_mutable_binding(vm, parameter_name.key, false));
|
||||
|
||||
// ii. If hasDuplicates is true, then
|
||||
if (m_has_duplicates) {
|
||||
// 1. Perform ! env.InitializeBinding(paramName, undefined).
|
||||
MUST(environment->initialize_binding(vm, parameter_name.key, js_undefined(), Environment::InitializeBindingHint::Normal));
|
||||
}
|
||||
}
|
||||
|
||||
// 22. If argumentsObjectNeeded is true, then
|
||||
if (m_arguments_object_needed) {
|
||||
Object* arguments_object;
|
||||
|
||||
// a. If strict is true or simpleParameterList is false, then
|
||||
if (m_strict || !simple_parameter_list) {
|
||||
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
|
||||
arguments_object = create_unmapped_arguments_object(vm, vm.running_execution_context().arguments);
|
||||
}
|
||||
// b. Else,
|
||||
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).
|
||||
arguments_object = create_mapped_arguments_object(vm, *this, formal_parameters(), vm.running_execution_context().arguments, *environment);
|
||||
}
|
||||
|
||||
// c. If strict is true, then
|
||||
if (m_strict) {
|
||||
// i. Perform ! env.CreateImmutableBinding("arguments", false).
|
||||
MUST(environment->create_immutable_binding(vm, vm.names.arguments.as_string(), false));
|
||||
|
||||
// ii. NOTE: In strict mode code early errors prevent attempting to assign to this binding, so its mutability is not observable.
|
||||
}
|
||||
// b. Else,
|
||||
else {
|
||||
// i. Perform ! env.CreateMutableBinding("arguments", false).
|
||||
MUST(environment->create_mutable_binding(vm, vm.names.arguments.as_string(), false));
|
||||
}
|
||||
|
||||
// c. Perform ! env.InitializeBinding("arguments", ao).
|
||||
MUST(environment->initialize_binding(vm, vm.names.arguments.as_string(), arguments_object, Environment::InitializeBindingHint::Normal));
|
||||
|
||||
// f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ».
|
||||
}
|
||||
// 23. Else,
|
||||
else {
|
||||
// a. Let parameterBindings be parameterNames.
|
||||
}
|
||||
|
||||
// NOTE: We now treat parameterBindings as parameterNames.
|
||||
|
||||
// 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList).
|
||||
// 25. If hasDuplicates is true, then
|
||||
// a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and undefined.
|
||||
// 26. Else,
|
||||
// a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and env.
|
||||
// NOTE: The spec makes an iterator here to do IteratorBindingInitialization but we just do it manually
|
||||
auto const& execution_context_arguments = vm.running_execution_context().arguments;
|
||||
|
||||
size_t default_parameter_index = 0;
|
||||
for (size_t i = 0; i < m_formal_parameters.size(); ++i) {
|
||||
auto& parameter = m_formal_parameters[i];
|
||||
if (parameter.default_value)
|
||||
++default_parameter_index;
|
||||
|
||||
TRY(parameter.binding.visit(
|
||||
[&](auto const& param) -> ThrowCompletionOr<void> {
|
||||
Value argument_value;
|
||||
if (parameter.is_rest) {
|
||||
auto array = MUST(Array::create(realm, 0));
|
||||
for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index)
|
||||
array->indexed_properties().append(execution_context_arguments[rest_index]);
|
||||
argument_value = array;
|
||||
} else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) {
|
||||
argument_value = execution_context_arguments[i];
|
||||
} else if (parameter.default_value) {
|
||||
auto& running_execution_context = vm.running_execution_context();
|
||||
|
||||
// NOTE: Registers have to be saved and restored because executable created for default parameter uses
|
||||
// running execution context.
|
||||
// FIXME: This is a hack and instead instructions for default parameters should be a part of the function's bytecode.
|
||||
auto saved_registers = running_execution_context.registers;
|
||||
for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index)
|
||||
saved_registers[register_index] = {};
|
||||
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*m_default_parameter_bytecode_executables[default_parameter_index - 1], {});
|
||||
|
||||
for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index)
|
||||
running_execution_context.registers[register_index] = saved_registers[register_index];
|
||||
|
||||
if (result_and_return_register.value.is_error())
|
||||
return result_and_return_register.value.release_error();
|
||||
argument_value = result_and_return_register.return_register_value;
|
||||
} else {
|
||||
argument_value = js_undefined();
|
||||
}
|
||||
|
||||
Environment* used_environment = m_has_duplicates ? nullptr : environment;
|
||||
|
||||
if constexpr (IsSame<NonnullRefPtr<Identifier const> const&, decltype(param)>) {
|
||||
if (param->is_local()) {
|
||||
callee_context.locals[param->local_variable_index()] = argument_value;
|
||||
return {};
|
||||
}
|
||||
Reference reference = TRY(vm.resolve_binding(param->string(), used_environment));
|
||||
// Here the difference from hasDuplicates is important
|
||||
if (m_has_duplicates)
|
||||
return reference.put_value(vm, argument_value);
|
||||
return reference.initialize_referenced_binding(vm, argument_value);
|
||||
}
|
||||
if constexpr (IsSame<NonnullRefPtr<BindingPattern const> const&, decltype(param)>) {
|
||||
// Here the difference from hasDuplicates is important
|
||||
return vm.binding_initialization(param, argument_value, used_environment);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
GCPtr<Environment> var_environment;
|
||||
|
||||
// 27. If hasParameterExpressions is false, then
|
||||
if (!m_has_parameter_expressions) {
|
||||
// a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
|
||||
|
||||
// b. Let instantiatedVarNames be a copy of the List parameterBindings.
|
||||
// NOTE: Done in implementation of step 27.c.i.1 below
|
||||
|
||||
if (scope_body) {
|
||||
// NOTE: Due to the use of MUST with `create_mutable_binding` and `initialize_binding` below,
|
||||
// an exception should not result from `for_each_var_declared_name`.
|
||||
|
||||
// c. For each element n of varNames, do
|
||||
for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) {
|
||||
auto const& id = variable_to_initialize.identifier;
|
||||
// NOTE: Following steps were executed in ECMAScriptFunctionObject constructor.
|
||||
// i. If instantiatedVarNames does not contain n, then
|
||||
// 1. Append n to instantiatedVarNames.
|
||||
if (id.is_local()) {
|
||||
callee_context.locals[id.local_variable_index()] = js_undefined();
|
||||
} else {
|
||||
// 2. Perform ! env.CreateMutableBinding(n, false).
|
||||
// 3. Perform ! env.InitializeBinding(n, undefined).
|
||||
MUST(environment->create_mutable_binding(vm, id.string(), false));
|
||||
MUST(environment->initialize_binding(vm, id.string(), js_undefined(), Environment::InitializeBindingHint::Normal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// d.Let varEnv be env
|
||||
var_environment = environment;
|
||||
}
|
||||
// 28. Else,
|
||||
else {
|
||||
// a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.
|
||||
|
||||
// b. Let varEnv be NewDeclarativeEnvironment(env).
|
||||
var_environment = new_declarative_environment(*environment);
|
||||
static_cast<DeclarativeEnvironment*>(var_environment.ptr())->ensure_capacity(m_var_environment_bindings_count);
|
||||
|
||||
// c. Set the VariableEnvironment of calleeContext to varEnv.
|
||||
callee_context.variable_environment = var_environment;
|
||||
|
||||
// d. Let instantiatedVarNames be a new empty List.
|
||||
// NOTE: Already done above.
|
||||
|
||||
if (scope_body) {
|
||||
// NOTE: Due to the use of MUST with `create_mutable_binding`, `get_binding_value` and `initialize_binding` below,
|
||||
// an exception should not result from `for_each_var_declared_name`.
|
||||
|
||||
// e. For each element n of varNames, do
|
||||
for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) {
|
||||
auto const& id = variable_to_initialize.identifier;
|
||||
|
||||
// NOTE: Following steps were executed in ECMAScriptFunctionObject constructor.
|
||||
// i. If instantiatedVarNames does not contain n, then
|
||||
// 1. Append n to instantiatedVarNames.
|
||||
|
||||
// 2. Perform ! varEnv.CreateMutableBinding(n, false).
|
||||
// NOTE: We ignore locals because they are stored in ExecutionContext instead of environment.
|
||||
if (!id.is_local())
|
||||
MUST(var_environment->create_mutable_binding(vm, id.string(), false));
|
||||
|
||||
Value initial_value;
|
||||
|
||||
// 3. If parameterBindings does not contain n, or if functionNames contains n, then
|
||||
if (!variable_to_initialize.parameter_binding || variable_to_initialize.function_name) {
|
||||
// a. Let initialValue be undefined.
|
||||
initial_value = js_undefined();
|
||||
}
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Let initialValue be ! env.GetBindingValue(n, false).
|
||||
if (id.is_local()) {
|
||||
initial_value = callee_context.locals[id.local_variable_index()];
|
||||
} else {
|
||||
initial_value = MUST(environment->get_binding_value(vm, id.string(), false));
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Perform ! varEnv.InitializeBinding(n, initialValue).
|
||||
if (id.is_local()) {
|
||||
// NOTE: Local variables are supported only in bytecode interpreter
|
||||
callee_context.locals[id.local_variable_index()] = initial_value;
|
||||
} else {
|
||||
MUST(var_environment->initialize_binding(vm, id.string(), initial_value, Environment::InitializeBindingHint::Normal));
|
||||
}
|
||||
|
||||
// 6. NOTE: A var with the same name as a formal parameter initially has the same value as the corresponding initialized parameter.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 29. NOTE: Annex B.3.2.1 adds additional steps at this point.
|
||||
// B.3.2.1 Changes to FunctionDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation
|
||||
if (!m_strict && scope_body) {
|
||||
// NOTE: Due to the use of MUST with `create_mutable_binding` and `initialize_binding` below,
|
||||
// an exception should not result from `for_each_function_hoistable_with_annexB_extension`.
|
||||
for (auto const& function_name : m_function_names_to_initialize_binding) {
|
||||
MUST(var_environment->create_mutable_binding(vm, function_name, false));
|
||||
MUST(var_environment->initialize_binding(vm, function_name, js_undefined(), Environment::InitializeBindingHint::Normal));
|
||||
}
|
||||
}
|
||||
|
||||
GCPtr<Environment> lex_environment;
|
||||
|
||||
// 30. If strict is false, then
|
||||
if (!m_strict) {
|
||||
// Optimization: We avoid creating empty top-level declarative environments in non-strict mode, if both of these conditions are true:
|
||||
// 1. there is no direct call to eval() within this function
|
||||
// 2. there are no lexical declarations that would go into the environment
|
||||
bool can_elide_declarative_environment = !m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations());
|
||||
if (can_elide_declarative_environment) {
|
||||
lex_environment = var_environment;
|
||||
} else {
|
||||
// a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
|
||||
// b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval
|
||||
// can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level
|
||||
// lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places
|
||||
// all declarations into a new Environment Record.
|
||||
lex_environment = new_declarative_environment(*var_environment);
|
||||
static_cast<DeclarativeEnvironment*>(lex_environment.ptr())->ensure_capacity(m_lex_environment_bindings_count);
|
||||
}
|
||||
}
|
||||
// 31. Else,
|
||||
else {
|
||||
// a. let lexEnv be varEnv.
|
||||
lex_environment = var_environment;
|
||||
}
|
||||
|
||||
// 32. Set the LexicalEnvironment of calleeContext to lexEnv.
|
||||
callee_context.lexical_environment = lex_environment;
|
||||
|
||||
if (!scope_body)
|
||||
return {};
|
||||
|
||||
// 33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
|
||||
// 34. For each element d of lexDeclarations, do
|
||||
// NOTE: Due to the use of MUST in the callback, an exception should not result from `for_each_lexically_scoped_declaration`.
|
||||
if (scope_body->has_lexical_declarations()) {
|
||||
MUST(scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
// NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below,
|
||||
// an exception should not result from `for_each_bound_name`.
|
||||
|
||||
// a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
|
||||
|
||||
// b. For each element dn of the BoundNames of d, do
|
||||
MUST(declaration.for_each_bound_identifier([&](auto const& id) {
|
||||
if (id.is_local()) {
|
||||
// NOTE: Local variables are supported only in bytecode interpreter
|
||||
return;
|
||||
}
|
||||
|
||||
// i. If IsConstantDeclaration of d is true, then
|
||||
if (declaration.is_constant_declaration()) {
|
||||
// 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
|
||||
MUST(lex_environment->create_immutable_binding(vm, id.string(), true));
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// 1. Perform ! lexEnv.CreateMutableBinding(dn, false).
|
||||
MUST(lex_environment->create_mutable_binding(vm, id.string(), false));
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
// 35. Let privateEnv be the PrivateEnvironment of calleeContext.
|
||||
auto private_environment = callee_context.private_environment;
|
||||
|
||||
// 36. For each Parse Node f of functionsToInitialize, do
|
||||
for (auto& declaration : m_functions_to_initialize) {
|
||||
// a. Let fn be the sole element of the BoundNames of f.
|
||||
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
|
||||
auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), lex_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.uses_this(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval());
|
||||
|
||||
// c. Perform ! varEnv.SetMutableBinding(fn, fo, false).
|
||||
if (declaration.name_identifier()->is_local()) {
|
||||
callee_context.locals[declaration.name_identifier()->local_variable_index()] = function;
|
||||
} else {
|
||||
MUST(var_environment->set_mutable_binding(vm, declaration.name(), function, false));
|
||||
}
|
||||
}
|
||||
|
||||
if (is<DeclarativeEnvironment>(*lex_environment))
|
||||
static_cast<DeclarativeEnvironment*>(lex_environment.ptr())->shrink_to_fit();
|
||||
if (lex_environment != var_environment && is<DeclarativeEnvironment>(*var_environment))
|
||||
static_cast<DeclarativeEnvironment*>(var_environment.ptr())->shrink_to_fit();
|
||||
|
||||
// 37. Return unused.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 10.2.1.1 PrepareForOrdinaryCall ( F, newTarget ), https://tc39.es/ecma262/#sec-prepareforordinarycall
|
||||
ThrowCompletionOr<void> ECMAScriptFunctionObject::prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target)
|
||||
{
|
||||
|
@ -1120,7 +746,7 @@ void async_block_start(VM& vm, T const& async_body, PromiseCapability const& pro
|
|||
if constexpr (!IsCallableWithArguments<T, Completion>) {
|
||||
// a. Let result be the result of evaluating asyncBody.
|
||||
// FIXME: Cache this executable somewhere.
|
||||
auto maybe_executable = Bytecode::compile(vm, async_body, {}, FunctionKind::Async, "AsyncBlockStart"sv);
|
||||
auto maybe_executable = Bytecode::compile(vm, async_body, FunctionKind::Async, "AsyncBlockStart"sv);
|
||||
if (maybe_executable.is_error())
|
||||
result = maybe_executable.release_error();
|
||||
else
|
||||
|
@ -1204,53 +830,17 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// NOTE: There's a subtle ordering issue here:
|
||||
// - We have to compile the default parameter values before instantiating the function.
|
||||
// - We have to instantiate the function before compiling the function body.
|
||||
// This is why FunctionDeclarationInstantiation is invoked in the middle.
|
||||
// The issue is that FunctionDeclarationInstantiation may mark certain functions as hoisted
|
||||
// per Annex B. This affects code generation for FunctionDeclaration nodes.
|
||||
|
||||
if (!m_bytecode_executable) {
|
||||
size_t default_parameter_index = 0;
|
||||
for (auto& parameter : m_formal_parameters) {
|
||||
if (!parameter.default_value)
|
||||
continue;
|
||||
if (parameter.bytecode_executable.is_null()) {
|
||||
auto executable = TRY(Bytecode::compile(vm, *parameter.default_value, {}, FunctionKind::Normal, ByteString::formatted("default parameter #{} for {}", default_parameter_index++, m_name)));
|
||||
const_cast<FunctionParameter&>(parameter).bytecode_executable = executable;
|
||||
m_default_parameter_bytecode_executables.append(move(executable));
|
||||
if (!m_ecmascript_code->bytecode_executable()) {
|
||||
if (is_module_wrapper()) {
|
||||
const_cast<Statement&>(*m_ecmascript_code).set_bytecode_executable(TRY(Bytecode::compile(vm, *m_ecmascript_code, m_kind, m_name)));
|
||||
} else {
|
||||
m_default_parameter_bytecode_executables.append(*parameter.bytecode_executable);
|
||||
const_cast<Statement&>(*m_ecmascript_code).set_bytecode_executable(TRY(Bytecode::compile(vm, *this)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto declaration_result = [&]() -> ThrowCompletionOr<void> {
|
||||
if (is_module_wrapper())
|
||||
return {};
|
||||
return function_declaration_instantiation();
|
||||
}();
|
||||
|
||||
if (m_kind == FunctionKind::Normal || m_kind == FunctionKind::Generator || m_kind == FunctionKind::AsyncGenerator) {
|
||||
if (declaration_result.is_error())
|
||||
return declaration_result.release_error();
|
||||
}
|
||||
|
||||
if (!m_bytecode_executable) {
|
||||
if (!m_ecmascript_code->bytecode_executable())
|
||||
const_cast<Statement&>(*m_ecmascript_code).set_bytecode_executable(TRY(Bytecode::compile(vm, *m_ecmascript_code, m_formal_parameters, m_kind, m_name)));
|
||||
m_bytecode_executable = m_ecmascript_code->bytecode_executable();
|
||||
}
|
||||
|
||||
if (m_kind == FunctionKind::Async) {
|
||||
if (declaration_result.is_throw_completion()) {
|
||||
auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
|
||||
MUST(call(vm, *promise_capability->reject(), js_undefined(), *declaration_result.throw_completion().value()));
|
||||
return Completion { Completion::Type::Return, promise_capability->promise() };
|
||||
}
|
||||
}
|
||||
|
||||
auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, {});
|
||||
|
||||
if (result_and_frame.value.is_error())
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibJS/Bytecode/Generator.h>
|
||||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibJS/Runtime/ClassFieldDefinition.h>
|
||||
#include <LibJS/Runtime/ExecutionContext.h>
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
void set_is_module_wrapper(bool b) { m_is_module_wrapper = b; }
|
||||
|
||||
Statement const& ecmascript_code() const { return m_ecmascript_code; }
|
||||
Vector<FunctionParameter> const& formal_parameters() const { return m_formal_parameters; }
|
||||
Vector<FunctionParameter> const& formal_parameters() const override { return m_formal_parameters; }
|
||||
|
||||
virtual DeprecatedFlyString const& name() const override { return m_name; }
|
||||
void set_name(DeprecatedFlyString const& name);
|
||||
|
@ -98,6 +99,8 @@ public:
|
|||
|
||||
Variant<PropertyKey, PrivateName, Empty> const& class_field_initializer_name() const { return m_class_field_initializer_name; }
|
||||
|
||||
friend class Bytecode::Generator;
|
||||
|
||||
protected:
|
||||
virtual bool is_strict_mode() const final { return m_strict; }
|
||||
|
||||
|
@ -112,13 +115,10 @@ private:
|
|||
ThrowCompletionOr<void> prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target);
|
||||
void ordinary_call_bind_this(ExecutionContext&, Value this_argument);
|
||||
|
||||
ThrowCompletionOr<void> function_declaration_instantiation();
|
||||
|
||||
DeprecatedFlyString m_name;
|
||||
GCPtr<PrimitiveString> m_name_string;
|
||||
|
||||
GCPtr<Bytecode::Executable> m_bytecode_executable;
|
||||
Vector<NonnullGCPtr<Bytecode::Executable>> m_default_parameter_bytecode_executables;
|
||||
i32 m_function_length { 0 };
|
||||
Vector<DeprecatedFlyString> m_local_variables_names;
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
|
|||
copy->is_strict_mode = is_strict_mode;
|
||||
copy->executable = executable;
|
||||
copy->arguments = arguments;
|
||||
copy->passed_argument_count = passed_argument_count;
|
||||
copy->locals = locals;
|
||||
copy->registers = registers;
|
||||
copy->unwind_contexts = unwind_contexts;
|
||||
|
|
|
@ -73,6 +73,8 @@ public:
|
|||
return locals[index];
|
||||
}
|
||||
|
||||
u32 passed_argument_count { 0 };
|
||||
|
||||
Vector<Value> arguments;
|
||||
Vector<Value> locals;
|
||||
Vector<Value> registers;
|
||||
|
|
|
@ -40,6 +40,8 @@ public:
|
|||
|
||||
virtual Vector<DeprecatedFlyString> const& local_variables_names() const { VERIFY_NOT_REACHED(); }
|
||||
|
||||
virtual Vector<FunctionParameter> const& formal_parameters() const { VERIFY_NOT_REACHED(); }
|
||||
|
||||
protected:
|
||||
explicit FunctionObject(Realm&, Object* prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);
|
||||
explicit FunctionObject(Object& prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);
|
||||
|
|
|
@ -174,7 +174,7 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, StringView source_tex
|
|||
// 17. If result.[[Type]] is normal, then
|
||||
if (!eval_result.is_throw_completion()) {
|
||||
// a. Set result to the result of evaluating body.
|
||||
auto maybe_executable = Bytecode::compile(vm, program, {}, FunctionKind::Normal, "ShadowRealmEval"sv);
|
||||
auto maybe_executable = Bytecode::compile(vm, program, FunctionKind::Normal, "ShadowRealmEval"sv);
|
||||
if (maybe_executable.is_error())
|
||||
result = maybe_executable.release_error();
|
||||
else {
|
||||
|
|
|
@ -291,7 +291,7 @@ ThrowCompletionOr<Value> VM::execute_ast_node(ASTNode const& node)
|
|||
{
|
||||
// FIXME: This function should be gone once we will emit bytecode for everything before executing instructions.
|
||||
|
||||
auto executable = TRY(Bytecode::compile(*this, node, {}, FunctionKind::Normal, ""sv));
|
||||
auto executable = TRY(Bytecode::compile(*this, node, FunctionKind::Normal, ""sv));
|
||||
auto& running_execution_context = this->running_execution_context();
|
||||
|
||||
// Registers have to be saved and restored because executable for compiled ASTNode does not have its own execution context
|
||||
|
|
|
@ -711,7 +711,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GCPtr<PromiseCa
|
|||
// c. Let result be the result of evaluating module.[[ECMAScriptCode]].
|
||||
Completion result;
|
||||
|
||||
auto maybe_executable = Bytecode::compile(vm, m_ecmascript_code, {}, FunctionKind::Normal, "ShadowRealmEval"sv);
|
||||
auto maybe_executable = Bytecode::compile(vm, m_ecmascript_code, FunctionKind::Normal, "ShadowRealmEval"sv);
|
||||
if (maybe_executable.is_error())
|
||||
result = maybe_executable.release_error();
|
||||
else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue