ladybird/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp
Andreas Kling 694112d31d LibJS: Move declarative state into rare data
Store the environment serial number and catch-environment flag in
DeclarativeEnvironment::RareData instead of on every environment.
The serial is only used by global variable caches, and the catch flag
is only needed for catch clause environments, so shaped function
environments should not retain dedicated fields for either value.

Teach the asm global cache fast path to load the serial through rare
data and treat a missing sidecar as serial 0.
2026-05-15 14:04:47 +02:00

495 lines
17 KiB
C++

/*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NeverDestroyed.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
GC_DEFINE_ALLOCATOR(DeclarativeEnvironment);
DeclarativeEnvironment::DeclarativeEnvironment()
: Environment(nullptr, IsDeclarative::Yes)
{
}
DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment)
: Environment(parent_environment, IsDeclarative::Yes)
{
}
DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment, ReadonlySpan<Binding> bindings)
: Environment(parent_environment, IsDeclarative::Yes)
{
ensure_capacity(bindings.size());
for (auto binding : bindings)
append_binding(move(binding));
}
void DeclarativeEnvironment::RareData::visit_edges(Visitor& visitor) const
{
m_dispose_capability.visit_edges(visitor);
}
size_t DeclarativeEnvironment::RareData::external_memory_size() const
{
auto size = sizeof(RareData);
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_binding_names));
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_binding_flags));
size = saturating_add_external_memory_size(size, m_deleted_bindings.size_in_bytes());
size = saturating_add_external_memory_size(size, hash_map_external_memory_size(m_bindings_assoc));
if (m_dispose_capability.disposable_resource_stack) {
size = saturating_add_external_memory_size(size, sizeof(Vector<DisposableResource>));
size = saturating_add_external_memory_size(size, vector_external_memory_size(*m_dispose_capability.disposable_resource_stack));
}
return size;
}
bool DeclarativeEnvironment::RareData::is_empty() const
{
return m_binding_names.is_empty()
&& m_binding_flags.is_empty()
&& m_deleted_bindings.is_null()
&& m_bindings_assoc.is_empty()
&& !m_dispose_capability.disposable_resource_stack
&& !m_environment_shape_cache
&& m_expected_binding_count == 0
&& m_environment_serial_number == 0
&& !m_is_catch_environment;
}
DeclarativeEnvironment::RareData& DeclarativeEnvironment::ensure_rare_data()
{
if (!m_rare_data)
m_rare_data = make<RareData>();
return *m_rare_data;
}
void DeclarativeEnvironment::drop_rare_data_if_empty()
{
if (m_rare_data && m_rare_data->is_empty())
m_rare_data.clear();
}
void DeclarativeEnvironment::increment_environment_serial_number()
{
if (m_shape)
return;
if (m_rare_data && m_rare_data->m_environment_shape_cache)
return;
++ensure_rare_data().m_environment_serial_number;
}
bool DeclarativeEnvironment::is_catch_environment() const
{
if (!m_rare_data)
return false;
return m_rare_data->m_is_catch_environment;
}
void DeclarativeEnvironment::set_is_catch_environment(bool value)
{
if (!value && !m_rare_data)
return;
ensure_rare_data().m_is_catch_environment = value;
drop_rare_data_if_empty();
}
void DeclarativeEnvironment::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
if (m_rare_data)
m_rare_data->visit_edges(visitor);
visitor.visit(m_shape);
for (auto& value : m_binding_values)
visitor.visit(value);
}
size_t DeclarativeEnvironment::external_memory_size() const
{
auto size = vector_external_memory_size(m_binding_values);
if (m_rare_data)
size = saturating_add_external_memory_size(size, m_rare_data->external_memory_size());
return size;
}
DisposeCapability const& DeclarativeEnvironment::dispose_capability() const
{
static NeverDestroyed<DisposeCapability> empty_dispose_capability;
if (!m_rare_data)
return *empty_dispose_capability;
return m_rare_data->m_dispose_capability;
}
DisposeCapability& DeclarativeEnvironment::dispose_capability()
{
return ensure_rare_data().m_dispose_capability;
}
DisposeCapability* DeclarativeEnvironment::dispose_capability_if_exists()
{
if (!m_rare_data || !m_rare_data->m_dispose_capability.disposable_resource_stack)
return nullptr;
return &m_rare_data->m_dispose_capability;
}
u64 DeclarativeEnvironment::environment_serial_number() const
{
if (!m_rare_data)
return 0;
return m_rare_data->m_environment_serial_number;
}
void DeclarativeEnvironment::append_binding(Binding binding)
{
auto index = m_binding_values.size();
u8 flags = 0;
if (binding.strict)
flags |= BindingFlagStrict;
if (binding.mutable_)
flags |= BindingFlagMutable;
if (binding.can_be_deleted)
flags |= BindingFlagCanBeDeleted;
if (m_shape && index < shape_binding_count()) {
VERIFY(m_shape->binding_name(index) == binding.name);
VERIFY(m_shape->binding_flags(index) == flags);
} else {
auto& rare_data = ensure_rare_data();
rare_data.m_bindings_assoc.set(binding.name, index);
rare_data.m_binding_names.append(move(binding.name));
rare_data.m_binding_flags.append(flags);
}
m_binding_values.append(binding.initialized ? binding.value : js_special_empty_value());
}
void DeclarativeEnvironment::clear_binding(Utf16FlyString const& name, size_t index)
{
if (index < shape_binding_count()) {
ensure_deleted_bindings_capacity(index + 1);
m_rare_data->m_deleted_bindings.set(index, true);
m_binding_values[index] = js_special_empty_value();
return;
}
VERIFY(m_rare_data);
m_rare_data->m_bindings_assoc.remove(name);
auto local_index = local_binding_index(index);
m_rare_data->m_binding_names[local_index] = Utf16FlyString {};
m_binding_values[index] = js_special_empty_value();
m_rare_data->m_binding_flags[local_index] = 0;
}
DeclarativeEnvironment::Binding DeclarativeEnvironment::binding_at(size_t index) const
{
return Binding {
.name = binding_name(index),
.value = m_binding_values[index],
.strict = binding_is_strict(index),
.mutable_ = binding_is_mutable(index),
.can_be_deleted = binding_can_be_deleted(index),
.initialized = binding_is_initialized(index),
};
}
void DeclarativeEnvironment::set_environment_shape_cache(GC::Ptr<EnvironmentShape>& cache, size_t expected_binding_count)
{
if (expected_binding_count == 0)
return;
if (cache) {
VERIFY(cache->size() == expected_binding_count);
set_environment_shape(GC::Ref { *cache });
return;
}
auto& rare_data = ensure_rare_data();
rare_data.m_environment_shape_cache = &cache;
rare_data.m_expected_binding_count = expected_binding_count;
}
void DeclarativeEnvironment::set_environment_shape(GC::Ref<EnvironmentShape> shape)
{
VERIFY(!m_shape);
VERIFY(m_binding_values.size() <= shape->size());
m_shape = shape;
if (m_rare_data) {
m_rare_data->m_binding_names.clear();
m_rare_data->m_binding_flags.clear();
m_rare_data->m_bindings_assoc.clear();
m_rare_data->m_environment_shape_cache = nullptr;
m_rare_data->m_expected_binding_count = 0;
drop_rare_data_if_empty();
}
}
void DeclarativeEnvironment::maybe_finalize_environment_shape(VM& vm)
{
if (!m_rare_data || !m_rare_data->m_environment_shape_cache || m_shape || m_rare_data->m_expected_binding_count == 0 || m_binding_values.size() != m_rare_data->m_expected_binding_count)
return;
if (*m_rare_data->m_environment_shape_cache) {
auto shape = GC::Ref { **m_rare_data->m_environment_shape_cache };
VERIFY(shape->size() == m_binding_values.size());
for (size_t i = 0; i < m_binding_values.size(); ++i) {
VERIFY(shape->binding_name(i) == m_rare_data->m_binding_names[i]);
VERIFY(shape->binding_flags(i) == m_rare_data->m_binding_flags[i]);
}
set_environment_shape(shape);
return;
}
auto shape = EnvironmentShape::create(vm, m_rare_data->m_binding_names, m_rare_data->m_binding_flags);
*m_rare_data->m_environment_shape_cache = shape;
set_environment_shape(shape);
}
// 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n
ThrowCompletionOr<bool> DeclarativeEnvironment::has_binding(Utf16FlyString const& name, Optional<size_t>* out_index) const
{
auto binding_and_index = find_binding_and_index(name);
if (!binding_and_index.has_value())
return false;
if (!is_permanently_screwed_by_eval() && out_index && binding_and_index->index().has_value())
*out_index = *(binding_and_index->index());
return true;
}
// 9.1.1.1.2 CreateMutableBinding ( N, D ), https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d
ThrowCompletionOr<void> DeclarativeEnvironment::create_mutable_binding(VM& vm, Utf16FlyString const& name, bool can_be_deleted)
{
// 1. Assert: envRec does not already have a binding for N.
// NOTE: We skip this to avoid O(n) traversal of m_binding_names.
// 2. Create a mutable binding in envRec for N and record that it is uninitialized. If D is true, record that the newly created binding may be deleted by a subsequent DeleteBinding call.
append_binding(Binding {
.name = name,
.value = {},
.strict = false,
.mutable_ = true,
.can_be_deleted = can_be_deleted,
.initialized = false,
});
maybe_finalize_environment_shape(vm);
increment_environment_serial_number();
// 3. Return unused.
return {};
}
// 9.1.1.1.3 CreateImmutableBinding ( N, S ), https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s
ThrowCompletionOr<void> DeclarativeEnvironment::create_immutable_binding(VM& vm, Utf16FlyString const& name, bool strict)
{
// 1. Assert: envRec does not already have a binding for N.
// NOTE: We skip this to avoid O(n) traversal of m_binding_names.
// 2. Create an immutable binding in envRec for N and record that it is uninitialized. If S is true, record that the newly created binding is a strict binding.
append_binding(Binding {
.name = name,
.value = {},
.strict = strict,
.mutable_ = false,
.can_be_deleted = false,
.initialized = false,
});
maybe_finalize_environment_shape(vm);
increment_environment_serial_number();
// 3. Return unused.
return {};
}
// 9.1.1.1.4 InitializeBinding ( N, V ), https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v
// 4.1.1.1.1 InitializeBinding ( N, V, hint ), https://tc39.es/proposal-explicit-resource-management/#sec-declarative-environment-records
ThrowCompletionOr<void> DeclarativeEnvironment::initialize_binding(VM& vm, Utf16FlyString const& name, Value value, Environment::InitializeBindingHint hint)
{
return initialize_binding_direct(vm, find_binding_and_index(name)->index().value(), value, hint);
}
ThrowCompletionOr<void> DeclarativeEnvironment::initialize_binding_direct(VM& vm, size_t index, Value value, Environment::InitializeBindingHint hint)
{
// 1. Assert: envRec must have an uninitialized binding for N.
VERIFY(!binding_is_initialized(index));
VERIFY(!value.is_special_empty_value());
// 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint).
if (hint != Environment::InitializeBindingHint::Normal)
TRY(add_disposable_resource(vm, ensure_rare_data().m_dispose_capability, value, hint));
// 3. Set the bound value for N in envRec to V.
m_binding_values[index] = value;
// 5. Return unused.
return {};
}
// 9.1.1.1.5 SetMutableBinding ( N, V, S ), https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s
ThrowCompletionOr<void> DeclarativeEnvironment::set_mutable_binding(VM& vm, Utf16FlyString const& name, Value value, bool strict)
{
// 1. If envRec does not have a binding for N, then
auto binding_and_index = find_binding_and_index(name);
if (!binding_and_index.has_value()) {
// a. If S is true, throw a ReferenceError exception.
if (strict)
return vm.throw_completion<ReferenceError>(ErrorType::UnknownIdentifier, name);
// b. Perform ! envRec.CreateMutableBinding(N, true).
MUST(create_mutable_binding(vm, name, true));
// c. Perform ! envRec.InitializeBinding(N, V, normal).
MUST(initialize_binding(vm, name, value, Environment::InitializeBindingHint::Normal));
// d. Return unused.
return {};
}
// 2-5. (extracted into a non-standard function below)
if (binding_and_index->index().has_value()) {
TRY(set_mutable_binding_direct(vm, *binding_and_index->index(), value, strict));
} else {
auto binding = binding_and_index->binding();
TRY(set_mutable_binding_direct(vm, binding, value, strict));
}
// 6. Return unused.
return {};
}
ThrowCompletionOr<void> DeclarativeEnvironment::set_mutable_binding_direct(VM& vm, size_t index, Value value, bool strict)
{
if (binding_is_strict(index))
strict = true;
if (!binding_is_initialized(index))
return vm.throw_completion<ReferenceError>(ErrorType::BindingNotInitialized, binding_name(index));
if (binding_is_mutable(index)) {
m_binding_values[index] = value;
} else {
if (strict)
return vm.throw_completion<TypeError>(ErrorType::InvalidAssignToConst);
}
return {};
}
ThrowCompletionOr<void> DeclarativeEnvironment::set_mutable_binding_direct(VM& vm, Binding& binding, Value value, bool strict)
{
if (binding.strict)
strict = true;
if (!binding.initialized)
return vm.throw_completion<ReferenceError>(ErrorType::BindingNotInitialized, binding.name);
if (binding.mutable_) {
binding.value = value;
} else {
if (strict)
return vm.throw_completion<TypeError>(ErrorType::InvalidAssignToConst);
}
return {};
}
// 9.1.1.1.6 GetBindingValue ( N, S ), https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s
ThrowCompletionOr<Value> DeclarativeEnvironment::get_binding_value(VM& vm, Utf16FlyString const& name, [[maybe_unused]] bool strict)
{
// 1. Assert: envRec has a binding for N.
auto binding_and_index = find_binding_and_index(name);
VERIFY(binding_and_index.has_value());
// 2-3. (extracted into a non-standard function below)
if (binding_and_index->index().has_value())
return get_binding_value_direct(vm, *binding_and_index->index());
auto binding = binding_and_index->binding();
return get_binding_value_direct(vm, binding);
}
// 9.1.1.1.7 DeleteBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-deletebinding-n
ThrowCompletionOr<bool> DeclarativeEnvironment::delete_binding(VM&, Utf16FlyString const& name)
{
// 1. Assert: envRec has a binding for the name that is the value of N.
auto binding_and_index = find_binding_and_index(name);
VERIFY(binding_and_index.has_value());
// 2. If the binding for N in envRec cannot be deleted, return false.
if (!binding_and_index->index().has_value()) {
if (!binding_and_index->binding().can_be_deleted)
return false;
VERIFY_NOT_REACHED();
}
auto index = *binding_and_index->index();
if (!binding_can_be_deleted(index))
return false;
// 3. Remove the binding for N from envRec.
// NOTE: We keep the entry in the parallel vectors to avoid disturbing indices.
clear_binding(name, index);
increment_environment_serial_number();
// 4. Return true.
return true;
}
ThrowCompletionOr<void> DeclarativeEnvironment::initialize_or_set_mutable_binding(VM& vm, Utf16FlyString const& name, Value value)
{
auto binding_and_index = find_binding_and_index(name);
VERIFY(binding_and_index.has_value());
VERIFY(binding_and_index->index().has_value());
auto index = *binding_and_index->index();
if (!binding_is_initialized(index))
TRY(initialize_binding_direct(vm, index, value, Environment::InitializeBindingHint::Normal));
else
TRY(set_mutable_binding_direct(vm, index, value, false));
return {};
}
void DeclarativeEnvironment::shrink_to_fit()
{
m_binding_values.shrink_to_fit();
if (!m_rare_data)
return;
m_rare_data->m_binding_names.shrink_to_fit();
m_rare_data->m_binding_flags.shrink_to_fit();
if (m_binding_values.is_empty()) {
m_rare_data->m_deleted_bindings = {};
drop_rare_data_if_empty();
return;
}
if (m_rare_data->m_deleted_bindings.is_null()) {
drop_rare_data_if_empty();
return;
}
auto deleted_bindings = MUST(Bitmap::create(m_binding_values.size(), false));
for (size_t i = 0; i < m_binding_values.size(); ++i)
deleted_bindings.set(i, binding_is_deleted(i));
m_rare_data->m_deleted_bindings = move(deleted_bindings);
drop_rare_data_if_empty();
}
}