LibJS: Cache symbolicated stack frames on ExecutionContext

Instead of re-symbolicating entire stacks from scratch every time
we want a JS VM backtrace, we now use the ExecutionContext object as
cache storage via a new CachedSourceRange object.

This means that once a stack frame has been symbolicated, we don't
have to resymbolicate it again (unless the program counter moves
within that stack frame).

This drastically reduces time spent in symbolication in some WPT tests.
This commit is contained in:
Andreas Kling 2024-10-14 07:49:12 +02:00 committed by Andreas Kling
commit 5b060da4f4
Notes: github-actions[bot] 2024-10-14 07:52:03 +00:00
5 changed files with 36 additions and 16 deletions

View file

@ -156,6 +156,7 @@ class Accessor;
struct AsyncGeneratorRequest;
class BigInt;
class BoundFunction;
struct CachedSourceRange;
class Cell;
class CellAllocator;
class ClassExpression;

View file

@ -17,19 +17,21 @@ namespace JS {
JS_DEFINE_ALLOCATOR(Error);
static SourceRange dummy_source_range { SourceCode::create(String {}, String {}), {}, {} };
SourceRange const& TracebackFrame::source_range() const
{
if (auto* unrealized = source_range_storage.get_pointer<UnrealizedSourceRange>()) {
if (!cached_source_range)
return dummy_source_range;
if (auto* unrealized = cached_source_range->source_range.get_pointer<UnrealizedSourceRange>()) {
auto source_range = [&] {
if (!unrealized->source_code) {
static auto dummy_source_code = SourceCode::create(String {}, String {});
return SourceRange { dummy_source_code, {}, {} };
}
if (!unrealized->source_code)
return dummy_source_range;
return unrealized->realize();
}();
source_range_storage = move(source_range);
cached_source_range->source_range = move(source_range);
}
return source_range_storage.get<SourceRange>();
return cached_source_range->source_range.get<SourceRange>();
}
NonnullGCPtr<Error> Error::create(Realm& realm)
@ -81,12 +83,9 @@ void Error::populate_stack()
m_traceback.ensure_capacity(stack_trace.size());
for (auto& element : stack_trace) {
auto* context = element.execution_context;
UnrealizedSourceRange range = {};
if (element.source_range.has_value())
range = element.source_range.value();
TracebackFrame frame {
.function_name = context->function_name ? context->function_name->byte_string() : "",
.source_range_storage = range,
.cached_source_range = element.source_range,
};
m_traceback.append(move(frame));

View file

@ -19,7 +19,7 @@ struct TracebackFrame {
DeprecatedFlyString function_name;
[[nodiscard]] SourceRange const& source_range() const;
mutable Variant<SourceRange, UnrealizedSourceRange> source_range_storage;
RefPtr<CachedSourceRange> cached_source_range;
};
enum CompactTraceback {

View file

@ -21,6 +21,16 @@ namespace JS {
using ScriptOrModule = Variant<Empty, NonnullGCPtr<Script>, NonnullGCPtr<Module>>;
struct CachedSourceRange : public RefCounted<CachedSourceRange> {
CachedSourceRange(size_t program_counter, Variant<UnrealizedSourceRange, SourceRange> source_range)
: program_counter(program_counter)
, source_range(move(source_range))
{
}
size_t program_counter { 0 };
Variant<UnrealizedSourceRange, SourceRange> source_range;
};
// 9.4 Execution Contexts, https://tc39.es/ecma262/#sec-execution-contexts
struct ExecutionContext {
static NonnullOwnPtr<ExecutionContext> create();
@ -49,6 +59,9 @@ public:
GCPtr<Cell> context_owner;
Optional<size_t> program_counter;
mutable RefPtr<CachedSourceRange> cached_source_range;
GCPtr<PrimitiveString> function_name;
Value this_value;
@ -82,7 +95,7 @@ public:
struct StackTraceElement {
ExecutionContext* execution_context;
Optional<UnrealizedSourceRange> source_range;
RefPtr<CachedSourceRange> source_range;
};
}

View file

@ -731,7 +731,7 @@ struct [[gnu::packed]] NativeStackFrame {
};
#endif
static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const* context)
static RefPtr<CachedSourceRange> get_source_range(ExecutionContext const* context)
{
// native function
if (!context->executable)
@ -740,7 +740,14 @@ static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const*
if (!context->program_counter.has_value())
return {};
return context->executable->source_range_at(context->program_counter.value());
if (!context->cached_source_range
|| context->cached_source_range->program_counter != context->program_counter.value()) {
auto unrealized_source_range = context->executable->source_range_at(context->program_counter.value());
context->cached_source_range = adopt_ref(*new CachedSourceRange(
context->program_counter.value(),
move(unrealized_source_range)));
}
return context->cached_source_range;
}
Vector<StackTraceElement> VM::stack_trace() const
@ -750,7 +757,7 @@ Vector<StackTraceElement> VM::stack_trace() const
auto* context = m_execution_context_stack[i];
stack_trace.append({
.execution_context = context,
.source_range = get_source_range(context).value_or({}),
.source_range = get_source_range(context),
});
}