Skip to content

Commit

Permalink
LibJS: Get this from execution context for non-arrow functions
Browse files Browse the repository at this point in the history
Allows to skip function environment allocation for non-arrow functions
if the only reason it is needed is to hold `this` binding.

The parser is changed to do following:
- If a function is an arrow function and uses `this` then all functions
  in a scope chain are marked to allocate function environment for
  `this` binding.
- If a function uses `new.target` then all functions in a scope chain
  are marked to allocate function environment.

`ordinary_call_bind_this()` is changed to put `this` value in execution
context when function environment allocation is skipped.

35% improvement in Octane/typescript.js
50% improvement in Octane/deltablue.js
19% improvement in Octane/raytrace.js
  • Loading branch information
kalenikaliaksandr authored and awesomekling committed May 22, 2024
1 parent 7ae990d commit ebb3d80
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 59 deletions.
18 changes: 11 additions & 7 deletions Userland/Libraries/LibJS/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ Value FunctionExpression::instantiate_ordinary_function_expression(VM& vm, Depre

auto private_environment = vm.running_execution_context().private_environment;

auto closure = ECMAScriptFunctionObject::create(realm, used_name, source_text(), body(), parameters(), function_length(), local_variables_names(), environment, private_environment, kind(), is_strict_mode(), uses_this(), might_need_arguments_object(), contains_direct_call_to_eval(), is_arrow_function());
auto closure = ECMAScriptFunctionObject::create(realm, used_name, source_text(), body(), parameters(), function_length(), local_variables_names(), environment, private_environment, kind(), is_strict_mode(),
uses_this_from_environment(), might_need_arguments_object(), contains_direct_call_to_eval(), is_arrow_function());

// FIXME: 6. Perform SetFunctionName(closure, name).
// FIXME: 7. Perform MakeConstructor(closure).
Expand Down Expand Up @@ -151,7 +152,8 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassMethod::class_element_evaluatio
{
auto property_key_or_private_name = TRY(class_key_to_property_name(vm, *m_key, property_key));

auto& method_function = *ECMAScriptFunctionObject::create(*vm.current_realm(), m_function->name(), m_function->source_text(), m_function->body(), m_function->parameters(), m_function->function_length(), m_function->local_variables_names(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function->kind(), m_function->is_strict_mode(), m_function->uses_this(), m_function->might_need_arguments_object(), m_function->contains_direct_call_to_eval(), m_function->is_arrow_function());
auto& method_function = *ECMAScriptFunctionObject::create(*vm.current_realm(), m_function->name(), m_function->source_text(), m_function->body(), m_function->parameters(), m_function->function_length(), m_function->local_variables_names(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function->kind(), m_function->is_strict_mode(),
m_function->uses_this_from_environment(), m_function->might_need_arguments_object(), m_function->contains_direct_call_to_eval(), m_function->is_arrow_function());

auto method_value = Value(&method_function);
method_function.make_method(target);
Expand Down Expand Up @@ -238,7 +240,7 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation

// FIXME: A potential optimization is not creating the functions here since these are never directly accessible.
auto function_code = create_ast_node<ClassFieldInitializerStatement>(m_initializer->source_range(), copy_initializer.release_nonnull(), name);
initializer = make_handle(*ECMAScriptFunctionObject::create(realm, "field", ByteString::empty(), *function_code, {}, 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, UsesThis::Yes, false, m_contains_direct_call_to_eval, false, property_key_or_private_name));
initializer = make_handle(*ECMAScriptFunctionObject::create(realm, "field", ByteString::empty(), *function_code, {}, 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, UsesThisFromEnvironment::Yes, false, m_contains_direct_call_to_eval, false, property_key_or_private_name));
initializer->make_method(target);
}

Expand Down Expand Up @@ -282,7 +284,7 @@ ThrowCompletionOr<ClassElement::ClassValue> StaticInitializer::class_element_eva
// 4. Let formalParameters be an instance of the production FormalParameters : [empty] .
// 5. Let bodyFunction be OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameters, ClassStaticBlockBody, non-lexical-this, lex, privateEnv).
// Note: The function bodyFunction is never directly accessible to ECMAScript code.
auto body_function = ECMAScriptFunctionObject::create(realm, ByteString::empty(), ByteString::empty(), *m_function_body, {}, 0, m_function_body->local_variables_names(), lexical_environment, private_environment, FunctionKind::Normal, true, UsesThis::Yes, false, m_contains_direct_call_to_eval, false);
auto body_function = ECMAScriptFunctionObject::create(realm, ByteString::empty(), ByteString::empty(), *m_function_body, {}, 0, m_function_body->local_variables_names(), lexical_environment, private_environment, FunctionKind::Normal, true, UsesThisFromEnvironment::Yes, false, m_contains_direct_call_to_eval, false);

// 6. Perform MakeMethod(bodyFunction, homeObject).
body_function->make_method(home_object);
Expand Down Expand Up @@ -341,7 +343,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> ClassExpression::create_class_const
vm.running_execution_context().private_environment,
constructor.kind(),
constructor.is_strict_mode(),
UsesThis::Yes,
UsesThisFromEnvironment::Yes,
constructor.might_need_arguments_object(),
constructor.contains_direct_call_to_eval(),
constructor.is_arrow_function());
Expand Down Expand Up @@ -1620,7 +1622,8 @@ void ScopeNode::block_declaration_instantiation(VM& vm, Environment* environment
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);

// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
auto function = ECMAScriptFunctionObject::create(realm, function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), function_declaration.local_variables_names(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.uses_this(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());
auto function = ECMAScriptFunctionObject::create(realm, function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), function_declaration.local_variables_names(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(),
function_declaration.uses_this_from_environment(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());

// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
if (function_declaration.name_identifier()->is_local()) {
Expand Down Expand Up @@ -1827,7 +1830,8 @@ ThrowCompletionOr<void> Program::global_declaration_instantiation(VM& vm, Global
for (auto& declaration : functions_to_initialize.in_reverse()) {
// a. Let fn be the sole element of the BoundNames of f.
// b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), &global_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.uses_this(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval());
auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), &global_environment, private_environment, declaration.kind(), declaration.is_strict_mode(),
declaration.uses_this_from_environment(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval());

// c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
TRY(global_environment.create_global_function_binding(declaration.name(), function, false));
Expand Down
18 changes: 9 additions & 9 deletions Userland/Libraries/LibJS/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ struct FunctionParameter {
Handle<Bytecode::Executable> bytecode_executable {};
};

enum class UsesThis {
enum class UsesThisFromEnvironment {
Yes,
No
};
Expand All @@ -712,15 +712,15 @@ class FunctionNode {
bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; }
bool is_arrow_function() const { return m_is_arrow_function; }
FunctionKind kind() const { return m_kind; }
UsesThis uses_this() const { return m_uses_this; }
UsesThisFromEnvironment uses_this_from_environment() const { return m_uses_this_from_environment; }

virtual bool has_name() const = 0;
virtual Value instantiate_ordinary_function_expression(VM&, DeprecatedFlyString given_name) const = 0;

virtual ~FunctionNode() {};

protected:
FunctionNode(RefPtr<Identifier const> name, ByteString source_text, NonnullRefPtr<Statement const> body, Vector<FunctionParameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Vector<DeprecatedFlyString> local_variables_names, UsesThis uses_this)
FunctionNode(RefPtr<Identifier const> name, ByteString source_text, NonnullRefPtr<Statement const> body, Vector<FunctionParameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Vector<DeprecatedFlyString> local_variables_names, UsesThisFromEnvironment uses_this_from_environment)
: m_name(move(name))
, m_source_text(move(source_text))
, m_body(move(body))
Expand All @@ -731,7 +731,7 @@ class FunctionNode {
, m_might_need_arguments_object(might_need_arguments_object)
, m_contains_direct_call_to_eval(contains_direct_call_to_eval)
, m_is_arrow_function(is_arrow_function)
, m_uses_this(uses_this)
, m_uses_this_from_environment(uses_this_from_environment)
, m_local_variables_names(move(local_variables_names))
{
if (m_is_arrow_function)
Expand All @@ -752,7 +752,7 @@ class FunctionNode {
bool m_might_need_arguments_object : 1 { false };
bool m_contains_direct_call_to_eval : 1 { false };
bool m_is_arrow_function : 1 { false };
UsesThis m_uses_this : 1 { UsesThis::No };
UsesThisFromEnvironment m_uses_this_from_environment : 1 { UsesThisFromEnvironment::No };

Vector<DeprecatedFlyString> m_local_variables_names;
};
Expand All @@ -763,9 +763,9 @@ class FunctionDeclaration final
public:
static bool must_have_name() { return true; }

FunctionDeclaration(SourceRange source_range, RefPtr<Identifier const> name, ByteString source_text, NonnullRefPtr<Statement const> body, Vector<FunctionParameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector<DeprecatedFlyString> local_variables_names, UsesThis uses_this)
FunctionDeclaration(SourceRange source_range, RefPtr<Identifier const> name, ByteString source_text, NonnullRefPtr<Statement const> body, Vector<FunctionParameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector<DeprecatedFlyString> local_variables_names, UsesThisFromEnvironment uses_this_from_environment)
: Declaration(move(source_range))
, FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false, move(local_variables_names), uses_this)
, FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false, move(local_variables_names), uses_this_from_environment)
{
}

Expand Down Expand Up @@ -793,9 +793,9 @@ class FunctionExpression final
public:
static bool must_have_name() { return false; }

FunctionExpression(SourceRange source_range, RefPtr<Identifier const> name, ByteString source_text, NonnullRefPtr<Statement const> body, Vector<FunctionParameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector<DeprecatedFlyString> local_variables_names, UsesThis uses_this = UsesThis::No, bool is_arrow_function = false)
FunctionExpression(SourceRange source_range, RefPtr<Identifier const> name, ByteString source_text, NonnullRefPtr<Statement const> body, Vector<FunctionParameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector<DeprecatedFlyString> local_variables_names, UsesThisFromEnvironment uses_this_from_environment = UsesThisFromEnvironment::No, bool is_arrow_function = false)
: Expression(move(source_range))
, FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(local_variables_names), uses_this)
, FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(local_variables_names), uses_this_from_environment)
{
}

Expand Down
3 changes: 2 additions & 1 deletion Userland/Libraries/LibJS/Bytecode/CommonImplementations.h
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ inline Value new_function(VM& vm, FunctionNode const& function_node, Optional<Id
name = vm.bytecode_interpreter().current_executable().get_identifier(lhs_name.value());
value = function_node.instantiate_ordinary_function_expression(vm, name);
} else {
value = ECMAScriptFunctionObject::create(*vm.current_realm(), function_node.name(), function_node.source_text(), function_node.body(), function_node.parameters(), function_node.function_length(), function_node.local_variables_names(), vm.lexical_environment(), vm.running_execution_context().private_environment, function_node.kind(), function_node.is_strict_mode(), function_node.uses_this(), function_node.might_need_arguments_object(), function_node.contains_direct_call_to_eval(), function_node.is_arrow_function());
value = ECMAScriptFunctionObject::create(*vm.current_realm(), function_node.name(), function_node.source_text(), function_node.body(), function_node.parameters(), function_node.function_length(), function_node.local_variables_names(), vm.lexical_environment(), vm.running_execution_context().private_environment, function_node.kind(), function_node.is_strict_mode(),
function_node.uses_this_from_environment(), function_node.might_need_arguments_object(), function_node.contains_direct_call_to_eval(), function_node.is_arrow_function());
}

if (home_object.has_value()) {
Expand Down
9 changes: 7 additions & 2 deletions Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1566,8 +1566,13 @@ ThrowCompletionOr<void> ResolveThisBinding::execute_impl(Bytecode::Interpreter&
if (cached_this_value.is_empty()) {
// OPTIMIZATION: Because the value of 'this' cannot be reassigned during a function execution, it's
// resolved once and then saved for subsequent use.
auto& vm = interpreter.vm();
cached_this_value = TRY(vm.resolve_this_binding());
auto& running_execution_context = interpreter.running_execution_context();
if (auto function = running_execution_context.function; function && is<ECMAScriptFunctionObject>(*function) && !static_cast<ECMAScriptFunctionObject&>(*function).allocates_function_environment()) {
cached_this_value = running_execution_context.this_value;
} else {
auto& vm = interpreter.vm();
cached_this_value = TRY(vm.resolve_this_binding());
}
}
interpreter.set(dst(), cached_this_value);
return {};
Expand Down
Loading

0 comments on commit ebb3d80

Please sign in to comment.