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:
Ali Mohammad Pur 2021-10-07 20:13:22 +03:30 committed by Andreas Kling
parent da296ffd56
commit 12b283f32f
Notes: sideshowbarker 2024-07-18 02:55:45 +09:00
5 changed files with 207 additions and 4 deletions

View file

@ -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();

View file

@ -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;
};

View file

@ -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));
}
}

View file

@ -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());
}

View file

@ -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 };
};
}