LibJS: Inline ExecutionContextRareData fields into ExecutionContext

After removing the unwind context stack, ExecutionContextRareData only
held two GC::Ptr fields — both trivially destructible. The indirection
cost more than it saved: a GC cell allocation per EC, an extra pointer
chase on every source range lookup, and unnecessary complexity.

Replace the rare data cell with two inline fields on ExecutionContext:
cached_source_range and context_owner.
This commit is contained in:
Andreas Kling 2026-02-09 11:41:22 +01:00 committed by Andreas Kling
parent 4e1d6faf0e
commit 4fa4ecf31b
4 changed files with 13 additions and 45 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2026, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
@ -16,7 +16,6 @@
namespace JS {
GC_DEFINE_ALLOCATOR(CachedSourceRange);
GC_DEFINE_ALLOCATOR(ExecutionContextRareData);
class ExecutionContextAllocator {
public:
@ -132,7 +131,8 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
visitor.visit(variable_environment);
visitor.visit(lexical_environment);
visitor.visit(private_environment);
visitor.visit(m_rare_data);
visitor.visit(cached_source_range);
visitor.visit(context_owner);
visitor.visit(this_value);
visitor.visit(executable);
visitor.visit(registers_and_constants_and_locals_and_arguments_span());
@ -146,19 +146,4 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
});
}
void ExecutionContextRareData::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(context_owner);
visitor.visit(cached_source_range);
}
GC::Ref<ExecutionContextRareData> ExecutionContext::ensure_rare_data()
{
if (!m_rare_data) {
m_rare_data = GC::Heap::the().allocate<ExecutionContextRareData>();
}
return *m_rare_data;
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2026, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
@ -51,20 +51,6 @@ public:
Variant<UnrealizedSourceRange, SourceRange> source_range;
};
class JS_API ExecutionContextRareData final : public GC::Cell {
GC_CELL(ExecutionContextRareData, GC::Cell);
GC_DECLARE_ALLOCATOR(ExecutionContextRareData);
public:
mutable GC::Ptr<CachedSourceRange> cached_source_range;
// Non-standard: This points at something that owns this ExecutionContext, in case it needs to be protected from GC.
GC::Ptr<Cell> context_owner;
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_locals_count, u32 constants_count, u32 arguments_count);
@ -91,9 +77,6 @@ public:
arguments = { values + registers_and_locals_and_constants_count, 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]]
@ -137,9 +120,10 @@ public:
Span<Value> arguments;
// 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;
mutable GC::Ptr<CachedSourceRange> cached_source_range;
// Non-standard: This points at something that owns this ExecutionContext, in case it needs to be protected from GC.
GC::Ptr<GC::Cell> context_owner;
u32 passed_argument_count { 0 };

View file

@ -799,15 +799,14 @@ static GC::Ptr<CachedSourceRange> get_source_range(ExecutionContext* context)
if (!context->executable)
return {};
if (!context->rare_data()
|| !context->rare_data()->cached_source_range
|| context->rare_data()->cached_source_range->program_counter != context->program_counter) {
if (!context->cached_source_range
|| context->cached_source_range->program_counter != context->program_counter) {
auto unrealized_source_range = context->executable->source_range_at(context->program_counter);
context->ensure_rare_data()->cached_source_range = context->executable->heap().allocate<CachedSourceRange>(
context->cached_source_range = context->executable->heap().allocate<CachedSourceRange>(
context->program_counter,
move(unrealized_source_range));
}
return context->rare_data()->cached_source_range;
return context->cached_source_range;
}
GC::ConservativeVector<StackTraceElement> VM::stack_trace() const

View file

@ -40,7 +40,7 @@ void Environment::visit_edges(Cell::Visitor& visitor)
EnvironmentSettingsObject::EnvironmentSettingsObject(NonnullOwnPtr<JS::ExecutionContext> realm_execution_context)
: m_realm_execution_context(move(realm_execution_context))
{
m_realm_execution_context->ensure_rare_data()->context_owner = this;
m_realm_execution_context->context_owner = this;
// Register with the responsible event loop so we can perform step 4 of "perform a microtask checkpoint".
responsible_event_loop().register_environment_settings_object({}, *this);