LibJS: Make accessing the current function's arguments cheaper
Instead of going through an environment record, make arguments of the currently executing function generate references via the argument index, which can later be resolved directly through the ExecutionContext.
This commit is contained in:
parent
da296ffd56
commit
12b283f32f
Notes:
sideshowbarker
2024-07-18 02:55:45 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/12b283f32fe Pull-request: https://github.com/SerenityOS/serenity/pull/10390 Reviewed-by: https://github.com/awesomekling ✅ Reviewed-by: https://github.com/davidot
5 changed files with 207 additions and 4 deletions
|
@ -1011,10 +1011,30 @@ Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) cons
|
|||
environment = environment->outer_environment();
|
||||
VERIFY(environment);
|
||||
VERIFY(environment->is_declarative_environment());
|
||||
if (!environment->is_permanently_screwed_by_eval())
|
||||
if (!environment->is_permanently_screwed_by_eval()) {
|
||||
if (m_lexically_bound_function_argument.has_value()) {
|
||||
return Reference {
|
||||
*environment,
|
||||
string(),
|
||||
*m_lexically_bound_function_argument,
|
||||
interpreter.vm().in_strict_mode(),
|
||||
m_cached_environment_coordinate,
|
||||
&interpreter.vm().running_execution_context(),
|
||||
};
|
||||
}
|
||||
return Reference { *environment, string(), interpreter.vm().in_strict_mode(), m_cached_environment_coordinate };
|
||||
}
|
||||
m_cached_environment_coordinate = {};
|
||||
}
|
||||
if (m_lexically_bound_function_argument.has_value()) {
|
||||
return Reference {
|
||||
string(),
|
||||
*m_lexically_bound_function_argument,
|
||||
interpreter.vm().in_strict_mode(),
|
||||
&interpreter.vm().running_execution_context(),
|
||||
};
|
||||
}
|
||||
|
||||
auto reference = interpreter.vm().resolve_binding(string());
|
||||
if (reference.environment_coordinate().has_value())
|
||||
m_cached_environment_coordinate = reference.environment_coordinate();
|
||||
|
|
|
@ -982,6 +982,7 @@ public:
|
|||
}
|
||||
|
||||
FlyString const& string() const { return m_string; }
|
||||
void set_lexically_bound_function_argument_index(size_t index) { m_lexically_bound_function_argument = index; }
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
@ -992,6 +993,7 @@ private:
|
|||
virtual bool is_identifier() const override { return true; }
|
||||
|
||||
FlyString m_string;
|
||||
Optional<size_t> m_lexically_bound_function_argument;
|
||||
mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -43,6 +44,7 @@ public:
|
|||
static ScopePusher function_scope(Parser& parser, FunctionBody& function_body, Vector<FunctionDeclaration::Parameter> const& parameters)
|
||||
{
|
||||
ScopePusher scope_pusher(parser, &function_body, true);
|
||||
scope_pusher.m_function_parameters = parameters;
|
||||
for (auto& parameter : parameters) {
|
||||
parameter.binding.visit(
|
||||
[&](FlyString const& name) {
|
||||
|
@ -160,10 +162,46 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
ScopePusher const* last_function_scope() const
|
||||
{
|
||||
for (auto scope_ptr = this; scope_ptr; scope_ptr = scope_ptr->m_parent_scope) {
|
||||
if (scope_ptr->m_function_parameters.has_value())
|
||||
return scope_ptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Vector<FunctionDeclaration::Parameter> const& function_parameters() const
|
||||
{
|
||||
return *m_function_parameters;
|
||||
}
|
||||
|
||||
ScopePusher* parent_scope() { return m_parent_scope; }
|
||||
ScopePusher const* parent_scope() const { return m_parent_scope; }
|
||||
|
||||
[[nodiscard]] bool has_declaration(StringView name) const
|
||||
{
|
||||
return m_lexical_names.contains(name) || m_var_names.contains(name) || !m_functions_to_hoist.find_if([&name](auto& function) { return function->name() == name; }).is_end();
|
||||
}
|
||||
|
||||
bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; }
|
||||
bool contains_access_to_arguments_object() const { return m_contains_access_to_arguments_object; }
|
||||
void set_contains_direct_call_to_eval() { m_contains_direct_call_to_eval = true; }
|
||||
void set_contains_access_to_arguments_object() { m_contains_access_to_arguments_object = true; }
|
||||
|
||||
~ScopePusher()
|
||||
{
|
||||
VERIFY(m_is_top_level || m_parent_scope);
|
||||
|
||||
if (!m_contains_access_to_arguments_object) {
|
||||
for (auto& it : m_identifier_and_argument_index_associations) {
|
||||
for (auto& identifier : it.value) {
|
||||
if (!has_declaration(identifier.string()))
|
||||
identifier.set_lexically_bound_function_argument_index(it.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_functions_to_hoist.size(); i++) {
|
||||
auto const& function_declaration = m_functions_to_hoist[i];
|
||||
if (m_lexical_names.contains(function_declaration.name()) || m_forbidden_var_names.contains(function_declaration.name()))
|
||||
|
@ -174,10 +212,20 @@ public:
|
|||
m_parent_scope->m_functions_to_hoist.append(move(m_functions_to_hoist[i]));
|
||||
}
|
||||
|
||||
if (m_parent_scope && !m_function_parameters.has_value()) {
|
||||
m_parent_scope->m_contains_access_to_arguments_object |= m_contains_access_to_arguments_object;
|
||||
m_parent_scope->m_contains_direct_call_to_eval |= m_contains_direct_call_to_eval;
|
||||
}
|
||||
|
||||
VERIFY(m_parser.m_state.current_scope_pusher == this);
|
||||
m_parser.m_state.current_scope_pusher = m_parent_scope;
|
||||
}
|
||||
|
||||
void associate_identifier_with_argument_index(NonnullRefPtr<Identifier> identifier, size_t index)
|
||||
{
|
||||
m_identifier_and_argument_index_associations.ensure(index).append(move(identifier));
|
||||
}
|
||||
|
||||
private:
|
||||
void throw_identifier_declared(FlyString const& name, NonnullRefPtr<Declaration> const& declaration)
|
||||
{
|
||||
|
@ -198,6 +246,12 @@ private:
|
|||
HashTable<FlyString> m_forbidden_lexical_names;
|
||||
HashTable<FlyString> m_forbidden_var_names;
|
||||
NonnullRefPtrVector<FunctionDeclaration> m_functions_to_hoist;
|
||||
|
||||
Optional<Vector<FunctionDeclaration::Parameter>> m_function_parameters;
|
||||
HashMap<size_t, NonnullRefPtrVector<Identifier>> m_identifier_and_argument_index_associations;
|
||||
|
||||
bool m_contains_access_to_arguments_object { false };
|
||||
bool m_contains_direct_call_to_eval { false };
|
||||
};
|
||||
|
||||
class OperatorPrecedenceTable {
|
||||
|
@ -629,8 +683,9 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
// for arrow function bodies which are a single expression.
|
||||
// Esprima generates a single "ArrowFunctionExpression"
|
||||
// with a "body" property.
|
||||
auto return_expression = parse_expression(2);
|
||||
auto return_block = create_ast_node<FunctionBody>({ m_state.current_token.filename(), rule_start.position(), position() });
|
||||
ScopePusher function_scope = ScopePusher::function_scope(*this, return_block, parameters);
|
||||
auto return_expression = parse_expression(2);
|
||||
return_block->append<ReturnStatement>({ m_filename, rule_start.position(), position() }, move(return_expression));
|
||||
if (m_state.strict_mode)
|
||||
return_block->set_strict_mode();
|
||||
|
@ -1460,6 +1515,48 @@ NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associati
|
|||
syntax_error("Invalid property in object literal", range->start);
|
||||
}
|
||||
};
|
||||
if (is<Identifier>(*expression) && m_state.current_scope_pusher) {
|
||||
auto identifier_instance = static_ptr_cast<Identifier>(expression);
|
||||
auto function_scope = m_state.current_scope_pusher->last_function_scope();
|
||||
auto function_parent_scope = function_scope ? function_scope->parent_scope() : nullptr;
|
||||
bool has_not_been_declared_as_variable = true;
|
||||
for (auto scope = m_state.current_scope_pusher; scope != function_parent_scope; scope = scope->parent_scope()) {
|
||||
if (scope->has_declaration(identifier_instance->string())) {
|
||||
has_not_been_declared_as_variable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_not_been_declared_as_variable) {
|
||||
if (identifier_instance->string() == "arguments"sv)
|
||||
m_state.current_scope_pusher->set_contains_access_to_arguments_object();
|
||||
}
|
||||
|
||||
if (function_scope && has_not_been_declared_as_variable) {
|
||||
|
||||
auto& parameters = function_scope->function_parameters();
|
||||
Optional<size_t> argument_index;
|
||||
size_t index = 0;
|
||||
for (auto& parameter : parameters) {
|
||||
auto current_index = index++;
|
||||
if (parameter.is_rest)
|
||||
break;
|
||||
if (auto name_ptr = parameter.binding.get_pointer<FlyString>()) {
|
||||
// Need VM assistance for this, so let's just pretend it's not there.
|
||||
if (parameter.default_value)
|
||||
break;
|
||||
if (identifier_instance->string() == *name_ptr) {
|
||||
argument_index = current_index;
|
||||
if (m_state.strict_mode)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (argument_index.has_value())
|
||||
m_state.current_scope_pusher->associate_identifier_with_argument_index(identifier_instance, *argument_index);
|
||||
}
|
||||
}
|
||||
|
||||
while (match(TokenType::TemplateLiteralStart)) {
|
||||
auto template_literal = parse_template_literal(true);
|
||||
expression = create_ast_node<TaggedTemplateLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(template_literal));
|
||||
|
@ -1487,6 +1584,24 @@ NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associati
|
|||
|
||||
check_for_invalid_object_property(expression);
|
||||
|
||||
if (is<CallExpression>(*expression) && m_state.current_scope_pusher) {
|
||||
auto& callee = static_ptr_cast<CallExpression>(expression)->callee();
|
||||
if (is<Identifier>(callee)) {
|
||||
auto& identifier_instance = static_cast<Identifier const&>(callee);
|
||||
if (identifier_instance.string() == "eval"sv) {
|
||||
bool has_not_been_declared_as_variable = true;
|
||||
for (auto scope = m_state.current_scope_pusher; scope; scope = scope->parent_scope()) {
|
||||
if (scope->has_declaration(identifier_instance.string())) {
|
||||
has_not_been_declared_as_variable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_not_been_declared_as_variable)
|
||||
m_state.current_scope_pusher->set_contains_direct_call_to_eval();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match(TokenType::Comma) && min_precedence <= 1) {
|
||||
NonnullRefPtrVector<Expression> expressions;
|
||||
expressions.append(expression);
|
||||
|
@ -3646,5 +3761,4 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
|
|||
|
||||
return create_ast_node<ExportStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(entries));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,6 +48,31 @@ void Reference::put_value(GlobalObject& global_object, Value value)
|
|||
}
|
||||
|
||||
VERIFY(m_base_type == BaseType::Environment);
|
||||
|
||||
// Note: Optimisation, not from the spec.
|
||||
if (m_function_argument_index.has_value()) {
|
||||
// Note: Modifying this binding requires us to sync with the environment.
|
||||
if (!m_base_environment) {
|
||||
auto real_reference = global_object.vm().resolve_binding(m_name.as_string(), m_referenced_function_context->lexical_environment);
|
||||
m_base_environment = real_reference.m_base_environment;
|
||||
}
|
||||
|
||||
if (!global_object.vm().execution_context_stack().is_empty() && m_referenced_function_context == &global_object.vm().running_execution_context()) {
|
||||
auto& arguments = m_referenced_function_context->arguments;
|
||||
auto index = m_function_argument_index.value();
|
||||
if (arguments.size() > index) {
|
||||
arguments[index] = value;
|
||||
} else {
|
||||
arguments.ensure_capacity(index + 1);
|
||||
for (size_t i = arguments.size(); i < index; ++i)
|
||||
arguments.append(js_undefined());
|
||||
arguments.append(value);
|
||||
}
|
||||
m_base_environment->set_mutable_binding(global_object, name().as_string(), value, is_strict());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY(m_base_environment);
|
||||
if (m_environment_coordinate.has_value())
|
||||
static_cast<DeclarativeEnvironment*>(m_base_environment)->set_mutable_binding_direct(global_object, m_environment_coordinate->index, value, m_strict);
|
||||
|
@ -80,6 +105,17 @@ Value Reference::get_value(GlobalObject& global_object) const
|
|||
}
|
||||
|
||||
VERIFY(m_base_type == BaseType::Environment);
|
||||
|
||||
// Note: Optimisation, not from the spec.
|
||||
if (m_function_argument_index.has_value()) {
|
||||
if (!global_object.vm().execution_context_stack().is_empty() && m_referenced_function_context == &global_object.vm().running_execution_context())
|
||||
return global_object.vm().argument(m_function_argument_index.value());
|
||||
if (!m_base_environment) {
|
||||
auto real_reference = global_object.vm().resolve_binding(m_name.as_string(), m_referenced_function_context->lexical_environment);
|
||||
m_base_environment = real_reference.m_base_environment;
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY(m_base_environment);
|
||||
if (m_environment_coordinate.has_value())
|
||||
return static_cast<DeclarativeEnvironment*>(m_base_environment)->get_binding_value_direct(global_object, m_environment_coordinate->index, m_strict);
|
||||
|
@ -141,6 +177,12 @@ bool Reference::delete_(GlobalObject& global_object)
|
|||
|
||||
VERIFY(m_base_type == BaseType::Environment);
|
||||
|
||||
// Note: Optimisation, not from the spec.
|
||||
if (m_function_argument_index.has_value()) {
|
||||
// This is a direct reference to a function argument.
|
||||
return false;
|
||||
}
|
||||
|
||||
// c. Return ? base.DeleteBinding(ref.[[ReferencedName]]).
|
||||
return m_base_environment->delete_binding(global_object, m_name.as_string());
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <AK/String.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/EnvironmentCoordinate.h>
|
||||
#include <LibJS/Runtime/ExecutionContext.h>
|
||||
#include <LibJS/Runtime/PropertyName.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
|
@ -54,6 +55,28 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
Reference(FlyString referenced_name, size_t function_argument_index, bool strict = false, ExecutionContext* function_context = nullptr)
|
||||
: m_base_type(BaseType::Environment)
|
||||
, m_base_environment(nullptr)
|
||||
, m_name(move(referenced_name))
|
||||
, m_strict(strict)
|
||||
, m_environment_coordinate({})
|
||||
, m_function_argument_index(function_argument_index)
|
||||
, m_referenced_function_context(function_context)
|
||||
{
|
||||
}
|
||||
|
||||
Reference(Environment& base, FlyString referenced_name, size_t function_argument_index, bool strict = false, Optional<EnvironmentCoordinate> environment_coordinate = {}, ExecutionContext* function_context = nullptr)
|
||||
: m_base_type(BaseType::Environment)
|
||||
, m_base_environment(&base)
|
||||
, m_name(move(referenced_name))
|
||||
, m_strict(strict)
|
||||
, m_environment_coordinate(move(environment_coordinate))
|
||||
, m_function_argument_index(function_argument_index)
|
||||
, m_referenced_function_context(function_context)
|
||||
{
|
||||
}
|
||||
|
||||
Value base() const
|
||||
{
|
||||
VERIFY(m_base_type == BaseType::Value);
|
||||
|
@ -128,12 +151,14 @@ private:
|
|||
BaseType m_base_type { BaseType::Unresolvable };
|
||||
union {
|
||||
Value m_base_value {};
|
||||
Environment* m_base_environment;
|
||||
mutable Environment* m_base_environment;
|
||||
};
|
||||
PropertyName m_name;
|
||||
Value m_this_value;
|
||||
bool m_strict { false };
|
||||
Optional<EnvironmentCoordinate> m_environment_coordinate;
|
||||
Optional<size_t> m_function_argument_index;
|
||||
ExecutionContext* m_referenced_function_context { nullptr };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue