LibJS: Keep parsed function parameters in a shared data structure

Instead of making a copy of the Vector<FunctionParameter> from the AST
every time we instantiate an ECMAScriptFunctionObject, we now keep the
parameters in a ref-counted FunctionParameters object.

This reduces memory usage, and also allows us to cache the bytecode
executables for default parameter expressions without recompiling them
for every instantiation. :^)
This commit is contained in:
Andreas Kling 2025-03-27 12:59:50 +00:00 committed by Jelle Raaijmakers
commit 7477002e46
Notes: github-actions[bot] 2025-03-27 15:02:02 +00:00
11 changed files with 68 additions and 38 deletions

View file

@ -1074,7 +1074,7 @@ Object* create_unmapped_arguments_object(VM& vm, ReadonlySpan<Value> arguments)
}
// 10.4.4.7 CreateMappedArgumentsObject ( func, formals, argumentsList, env ), https://tc39.es/ecma262/#sec-createmappedargumentsobject
Object* create_mapped_arguments_object(VM& vm, FunctionObject& function, Vector<FunctionParameter> const& formals, ReadonlySpan<Value> arguments, Environment& environment)
Object* create_mapped_arguments_object(VM& vm, FunctionObject& function, NonnullRefPtr<FunctionParameters const> const& formals, ReadonlySpan<Value> arguments, Environment& environment)
{
auto& realm = *vm.current_realm();
@ -1113,10 +1113,10 @@ Object* create_mapped_arguments_object(VM& vm, FunctionObject& function, Vector<
// 18. Set index to numberOfParameters - 1.
// 19. Repeat, while index ≥ 0,
VERIFY(formals.size() <= NumericLimits<i32>::max());
for (i32 index = static_cast<i32>(formals.size()) - 1; index >= 0; --index) {
VERIFY(formals->size() <= NumericLimits<i32>::max());
for (i32 index = static_cast<i32>(formals->size()) - 1; index >= 0; --index) {
// a. Let name be parameterNames[index].
auto const& name = formals[index].binding.get<NonnullRefPtr<Identifier const>>()->string();
auto const& name = formals->parameters()[index].binding.get<NonnullRefPtr<Identifier const>>()->string();
// b. If name is not an element of mappedNames, then
if (mapped_names.contains(name))

View file

@ -43,7 +43,7 @@ bool is_compatible_property_descriptor(bool extensible, PropertyDescriptor const
bool validate_and_apply_property_descriptor(Object*, PropertyKey const&, bool extensible, PropertyDescriptor const&, Optional<PropertyDescriptor> const& current);
ThrowCompletionOr<Object*> get_prototype_from_constructor(VM&, FunctionObject const& constructor, GC::Ref<Object> (Intrinsics::*intrinsic_default_prototype)());
Object* create_unmapped_arguments_object(VM&, ReadonlySpan<Value> arguments);
Object* create_mapped_arguments_object(VM&, FunctionObject&, Vector<FunctionParameter> const&, ReadonlySpan<Value> arguments, Environment&);
Object* create_mapped_arguments_object(VM&, FunctionObject&, NonnullRefPtr<FunctionParameters const> const&, ReadonlySpan<Value> arguments, Environment&);
// 2.1.1 DisposeCapability Records, https://tc39.es/proposal-explicit-resource-management/#sec-disposecapability-records
struct DisposeCapability {

View file

@ -33,7 +33,7 @@ namespace JS {
GC_DEFINE_ALLOCATOR(ECMAScriptFunctionObject);
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm, FlyString name, ByteString source_text, Statement const& ecmascript_code, Vector<FunctionParameter> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm, FlyString name, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
{
Object* prototype = nullptr;
switch (kind) {
@ -53,12 +53,12 @@ GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm,
return realm.create<ECMAScriptFunctionObject>(move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, move(local_variables_names), parent_environment, private_environment, *prototype, kind, is_strict, parsing_insights, is_arrow_function, move(class_field_initializer_name));
}
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm, FlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, Vector<FunctionParameter> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm, FlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
{
return realm.create<ECMAScriptFunctionObject>(move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, move(local_variables_names), parent_environment, private_environment, prototype, kind, is_strict, parsing_insights, is_arrow_function, move(class_field_initializer_name));
}
ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, ByteString source_text, Statement const& ecmascript_code, Vector<FunctionParameter> formal_parameters, i32 function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind kind, bool strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> formal_parameters, i32 function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind kind, bool strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
: FunctionObject(prototype)
, m_name(move(name))
, m_function_length(function_length)
@ -92,7 +92,7 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, ByteString so
m_script_or_module = vm().get_active_script_or_module();
// 15.1.3 Static Semantics: IsSimpleParameterList, https://tc39.es/ecma262/#sec-static-semantics-issimpleparameterlist
m_has_simple_parameter_list = all_of(m_formal_parameters, [&](auto& parameter) {
m_has_simple_parameter_list = all_of(m_formal_parameters->parameters(), [&](auto& parameter) {
if (parameter.is_rest)
return false;
if (parameter.default_value)
@ -121,7 +121,7 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, ByteString so
size_t parameters_in_environment = 0;
// NOTE: This loop performs step 5, 6, and 8.
for (auto const& parameter : formals) {
for (auto const& parameter : formals->parameters()) {
if (parameter.default_value)
m_has_parameter_expressions = true;
@ -385,11 +385,11 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu
auto callee_context = ExecutionContext::create();
// Non-standard
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
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->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)
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());
}
@ -442,11 +442,11 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
auto callee_context = ExecutionContext::create();
// Non-standard
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
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->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)
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());
}

View file

@ -39,8 +39,8 @@ public:
Global,
};
static GC::Ref<ECMAScriptFunctionObject> create(Realm&, FlyString name, ByteString source_text, Statement const& ecmascript_code, Vector<FunctionParameter> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, FunctionParsingInsights, bool is_arrow_function = false, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name = {});
static GC::Ref<ECMAScriptFunctionObject> create(Realm&, FlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, Vector<FunctionParameter> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, FunctionParsingInsights, bool is_arrow_function = false, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name = {});
static GC::Ref<ECMAScriptFunctionObject> create(Realm&, FlyString name, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, FunctionParsingInsights, bool is_arrow_function = false, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name = {});
static GC::Ref<ECMAScriptFunctionObject> create(Realm&, FlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, FunctionParsingInsights, bool is_arrow_function = false, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name = {});
virtual void initialize(Realm&) override;
virtual ~ECMAScriptFunctionObject() override = default;
@ -54,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 override { return m_formal_parameters; }
virtual NonnullRefPtr<FunctionParameters const> const& formal_parameters() const override { return m_formal_parameters; }
virtual FlyString const& name() const override { return m_name; }
void set_name(FlyString const& name);
@ -109,7 +109,7 @@ protected:
virtual Completion ordinary_call_evaluate_body();
private:
ECMAScriptFunctionObject(FlyString name, ByteString source_text, Statement const& ecmascript_code, Vector<FunctionParameter> parameters, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind, bool is_strict, FunctionParsingInsights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name);
ECMAScriptFunctionObject(FlyString name, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const>, i32 m_function_length, Vector<FlyString> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind, bool is_strict, FunctionParsingInsights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name);
virtual bool is_ecmascript_function_object() const override { return true; }
virtual void visit_edges(Visitor&) override;
@ -127,7 +127,7 @@ private:
// Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects
GC::Ptr<Environment> m_environment; // [[Environment]]
GC::Ptr<PrivateEnvironment> m_private_environment; // [[PrivateEnvironment]]
Vector<FunctionParameter> const m_formal_parameters; // [[FormalParameters]]
NonnullRefPtr<FunctionParameters const> m_formal_parameters; // [[FormalParameters]]
NonnullRefPtr<Statement const> m_ecmascript_code; // [[ECMAScriptCode]]
GC::Ptr<Realm> m_realm; // [[Realm]]
ScriptOrModule m_script_or_module; // [[ScriptOrModule]]

View file

@ -40,7 +40,7 @@ public:
virtual Vector<FlyString> const& local_variables_names() const { VERIFY_NOT_REACHED(); }
virtual Vector<FunctionParameter> const& formal_parameters() const { VERIFY_NOT_REACHED(); }
virtual NonnullRefPtr<FunctionParameters const> const& formal_parameters() const { VERIFY_NOT_REACHED(); }
protected:
explicit FunctionObject(Realm&, Object* prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);