LibJS: Move ExecutionContext members with destructors to "rare data"

This makes ExecutionContext trivially destructible, which means less
work to do on function exit.
This commit is contained in:
Andreas Kling 2025-10-31 14:56:25 +01:00 committed by Andreas Kling
parent 9ded35f98f
commit 1e0b56586b
3 changed files with 73 additions and 45 deletions

View File

@ -311,8 +311,8 @@ NEVER_INLINE Interpreter::HandleExceptionResponse Interpreter::handle_exception(
auto& handler = handlers->handler_offset;
auto& finalizer = handlers->finalizer_offset;
VERIFY(!running_execution_context().unwind_contexts.is_empty());
auto& unwind_context = running_execution_context().unwind_contexts.last();
auto& unwind_contexts = running_execution_context().ensure_rare_data()->unwind_contexts;
auto& unwind_context = unwind_contexts.last();
VERIFY(unwind_context.executable == &current_executable());
if (handler.has_value()) {
@ -485,8 +485,8 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
do_return(saved_return_value());
if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
if (auto finalizer = handlers.value().finalizer_offset; finalizer.has_value()) {
VERIFY(!running_execution_context.unwind_contexts.is_empty());
auto& unwind_context = running_execution_context.unwind_contexts.last();
auto& unwind_contexts = running_execution_context.ensure_rare_data()->unwind_contexts;
auto& unwind_context = unwind_contexts.last();
VERIFY(unwind_context.executable == &current_executable());
reg(Register::saved_return_value()) = reg(Register::return_value());
reg(Register::return_value()) = js_special_empty_value();
@ -497,7 +497,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
}
return;
}
auto const old_scheduled_jump = running_execution_context.previously_scheduled_jumps.take_last();
auto const old_scheduled_jump = running_execution_context.ensure_rare_data()->previously_scheduled_jumps.take_last();
if (m_running_execution_context->scheduled_jump.has_value()) {
program_counter = m_running_execution_context->scheduled_jump.value();
m_running_execution_context->scheduled_jump = {};
@ -762,23 +762,23 @@ ThrowCompletionOr<Value> Interpreter::run_executable(ExecutionContext& context,
void Interpreter::enter_unwind_context()
{
running_execution_context().unwind_contexts.empend(
running_execution_context().ensure_rare_data()->unwind_contexts.empend(
current_executable(),
running_execution_context().lexical_environment);
running_execution_context().previously_scheduled_jumps.append(m_running_execution_context->scheduled_jump);
running_execution_context().rare_data()->previously_scheduled_jumps.append(m_running_execution_context->scheduled_jump);
m_running_execution_context->scheduled_jump = {};
}
void Interpreter::leave_unwind_context()
{
running_execution_context().unwind_contexts.take_last();
running_execution_context().rare_data()->unwind_contexts.take_last();
}
void Interpreter::catch_exception(Operand dst)
{
set(dst, reg(Register::exception()));
reg(Register::exception()) = js_special_empty_value();
auto& context = running_execution_context().unwind_contexts.last();
auto& context = running_execution_context().rare_data()->unwind_contexts.last();
VERIFY(!context.handler_called);
VERIFY(context.executable == &current_executable());
context.handler_called = true;
@ -787,19 +787,19 @@ void Interpreter::catch_exception(Operand dst)
void Interpreter::restore_scheduled_jump()
{
m_running_execution_context->scheduled_jump = running_execution_context().previously_scheduled_jumps.take_last();
m_running_execution_context->scheduled_jump = running_execution_context().rare_data()->previously_scheduled_jumps.take_last();
}
void Interpreter::leave_finally()
{
reg(Register::exception()) = js_special_empty_value();
m_running_execution_context->scheduled_jump = running_execution_context().previously_scheduled_jumps.take_last();
m_running_execution_context->scheduled_jump = running_execution_context().rare_data()->previously_scheduled_jumps.take_last();
}
void Interpreter::enter_object_environment(Object& object)
{
auto& old_environment = running_execution_context().lexical_environment;
running_execution_context().saved_lexical_environments.append(old_environment);
running_execution_context().ensure_rare_data()->saved_lexical_environments.append(old_environment);
running_execution_context().lexical_environment = new_object_environment(object, true, old_environment);
}
@ -1593,7 +1593,7 @@ inline ThrowCompletionOr<ECMAScriptFunctionObject*> new_class(VM& vm, Value supe
// NOTE: NewClass expects classEnv to be active lexical environment
auto* class_environment = vm.lexical_environment();
vm.running_execution_context().lexical_environment = vm.running_execution_context().saved_lexical_environments.take_last();
vm.running_execution_context().lexical_environment = vm.running_execution_context().rare_data()->saved_lexical_environments.take_last();
Optional<Utf16FlyString> binding_name;
Utf16FlyString class_name;
@ -2416,7 +2416,7 @@ void CreateLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter)
return environment;
};
auto& running_execution_context = interpreter.running_execution_context();
running_execution_context.saved_lexical_environments.append(make_and_swap_envs(running_execution_context.lexical_environment));
running_execution_context.ensure_rare_data()->saved_lexical_environments.append(make_and_swap_envs(running_execution_context.lexical_environment));
if (m_dst.has_value())
interpreter.set(*m_dst, running_execution_context.lexical_environment);
}
@ -3161,7 +3161,7 @@ ThrowCompletionOr<void> ThrowIfTDZ::execute_impl(Bytecode::Interpreter& interpre
void LeaveLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& running_execution_context = interpreter.running_execution_context();
running_execution_context.lexical_environment = running_execution_context.saved_lexical_environments.take_last();
running_execution_context.lexical_environment = running_execution_context.rare_data()->saved_lexical_environments.take_last();
}
void LeavePrivateEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const

View File

@ -15,6 +15,7 @@
namespace JS {
GC_DEFINE_ALLOCATOR(CachedSourceRange);
GC_DEFINE_ALLOCATOR(ExecutionContextRareData);
class ExecutionContextAllocator {
public:
@ -110,10 +111,6 @@ ExecutionContext::ExecutionContext(u32 registers_and_constants_and_locals_count,
arguments = { registers_and_constants_and_locals_and_arguments + registers_and_constants_and_locals_count, arguments_count };
}
ExecutionContext::~ExecutionContext()
{
}
NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
{
auto copy = create(registers_and_constants_and_locals_and_arguments_count, arguments.size());
@ -127,9 +124,12 @@ NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
copy->this_value = this_value;
copy->executable = executable;
copy->passed_argument_count = passed_argument_count;
copy->unwind_contexts = unwind_contexts;
copy->saved_lexical_environments = saved_lexical_environments;
copy->previously_scheduled_jumps = previously_scheduled_jumps;
if (m_rare_data) {
auto copy_rare_data = copy->ensure_rare_data();
copy_rare_data->unwind_contexts = m_rare_data->unwind_contexts;
copy_rare_data->saved_lexical_environments = m_rare_data->saved_lexical_environments;
copy_rare_data->previously_scheduled_jumps = m_rare_data->previously_scheduled_jumps;
}
copy->registers_and_constants_and_locals_and_arguments_count = registers_and_constants_and_locals_and_arguments_count;
for (size_t i = 0; i < registers_and_constants_and_locals_and_arguments_count; ++i)
copy->registers_and_constants_and_locals_and_arguments()[i] = registers_and_constants_and_locals_and_arguments()[i];
@ -146,14 +146,11 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
visitor.visit(private_environment);
visitor.visit(context_owner);
visitor.visit(cached_source_range);
visitor.visit(m_rare_data);
if (this_value.has_value())
visitor.visit(*this_value);
visitor.visit(executable);
visitor.visit(registers_and_constants_and_locals_and_arguments_span());
for (auto& context : unwind_contexts) {
visitor.visit(context.lexical_environment);
}
visitor.visit(saved_lexical_environments);
script_or_module.visit(
[](Empty) {},
[&](auto& script_or_module) {
@ -161,4 +158,21 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
});
}
void ExecutionContextRareData::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto& context : unwind_contexts) {
visitor.visit(context.lexical_environment);
}
visitor.visit(saved_lexical_environments);
}
GC::Ref<ExecutionContextRareData> ExecutionContext::ensure_rare_data()
{
if (!m_rare_data) {
m_rare_data = executable->heap().allocate<ExecutionContextRareData>();
}
return *m_rare_data;
}
}

View File

@ -36,12 +36,25 @@ public:
Variant<UnrealizedSourceRange, SourceRange> source_range;
};
class ExecutionContextRareData final : public GC::Cell {
GC_CELL(ExecutionContextRareData, GC::Cell);
GC_DECLARE_ALLOCATOR(ExecutionContextRareData);
public:
Vector<Bytecode::UnwindInfo> unwind_contexts;
Vector<Optional<size_t>> previously_scheduled_jumps;
Vector<GC::Ptr<Environment>> saved_lexical_environments;
private:
virtual void visit_edges(Cell::Visitor&) override;
};
// 9.4 Execution Contexts, https://tc39.es/ecma262/#sec-execution-contexts
struct JS_API ExecutionContext {
static NonnullOwnPtr<ExecutionContext> create(u32 registers_and_constants_and_locals_count, u32 arguments_count);
[[nodiscard]] NonnullOwnPtr<ExecutionContext> copy() const;
~ExecutionContext();
~ExecutionContext() = default;
void visit_edges(Cell::Visitor&);
@ -51,6 +64,9 @@ private:
public:
ExecutionContext(u32 registers_and_constants_and_locals_count, u32 arguments_count);
GC::Ptr<ExecutionContextRareData> rare_data() const { return m_rare_data; }
GC::Ref<ExecutionContextRareData> ensure_rare_data();
void operator delete(void* ptr);
GC::Ptr<FunctionObject> function; // [[Function]]
@ -105,9 +121,9 @@ public:
Span<Value> arguments;
Vector<Bytecode::UnwindInfo> unwind_contexts;
Vector<Optional<size_t>> previously_scheduled_jumps;
Vector<GC::Ptr<Environment>> saved_lexical_environments;
// NOTE: Rarely used data members go here to keep the size of ExecutionContext down,
// and to avoid needing an ExecutionContext destructor in the common case.
GC::Ptr<ExecutionContextRareData> m_rare_data;
u32 passed_argument_count { 0 };
@ -122,21 +138,19 @@ private:
u32 registers_and_constants_and_locals_and_arguments_count { 0 };
};
#define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(execution_context, \
registers_and_constants_and_locals_count, \
arguments_count) \
auto execution_context_size = sizeof(JS::ExecutionContext) \
+ (((registers_and_constants_and_locals_count) + (arguments_count)) \
* sizeof(JS::Value)); \
\
void* execution_context_memory = alloca(execution_context_size); \
\
execution_context = new (execution_context_memory) \
JS::ExecutionContext((registers_and_constants_and_locals_count), (arguments_count)); \
\
ScopeGuard run_execution_context_destructor([execution_context] { \
execution_context->~ExecutionContext(); \
})
static_assert(IsTriviallyDestructible<ExecutionContext>);
#define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(execution_context, \
registers_and_constants_and_locals_count, \
arguments_count) \
auto execution_context_size = sizeof(JS::ExecutionContext) \
+ (((registers_and_constants_and_locals_count) + (arguments_count)) \
* sizeof(JS::Value)); \
\
void* execution_context_memory = alloca(execution_context_size); \
\
execution_context = new (execution_context_memory) \
JS::ExecutionContext((registers_and_constants_and_locals_count), (arguments_count));
#define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(execution_context, registers_and_constants_and_locals_count, \
arguments_count) \