mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 14:27:35 +00:00
LibJS: Lazily collect stack trace information
The previous implementation was calling `backtrace()` for every function call, which is quite slow. Instead, this implementation provides VM::stack_trace() which unwinds the native stack, maps it through NativeExecutable::get_source_range and combines it with source ranges from interpreted call frames.
This commit is contained in:
parent
77dc7c4d36
commit
68f4d21de2
11 changed files with 96 additions and 46 deletions
|
@ -75,13 +75,13 @@ ThrowCompletionOr<void> Error::install_error_cause(Value options)
|
|||
|
||||
void Error::populate_stack()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
m_traceback.ensure_capacity(vm.execution_context_stack().size());
|
||||
for (ssize_t i = vm.execution_context_stack().size() - 1; i >= 0; i--) {
|
||||
auto context = vm.execution_context_stack()[i];
|
||||
auto stack_trace = vm().stack_trace();
|
||||
m_traceback.ensure_capacity(stack_trace.size());
|
||||
for (auto& element : stack_trace) {
|
||||
auto* context = element.execution_context;
|
||||
UnrealizedSourceRange range = {};
|
||||
if (context->instruction_stream_iterator.has_value())
|
||||
range = context->instruction_stream_iterator->source_range();
|
||||
if (element.source_range.has_value())
|
||||
range = element.source_range.value();
|
||||
TracebackFrame frame {
|
||||
.function_name = context->function_name,
|
||||
.source_range_storage = range,
|
||||
|
|
|
@ -51,9 +51,16 @@ public:
|
|||
MarkedVector<Value> local_variables;
|
||||
bool is_strict_mode { false };
|
||||
|
||||
RefPtr<Bytecode::Executable> executable;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#skip-when-determining-incumbent-counter
|
||||
// FIXME: Move this out of LibJS (e.g. by using the CustomData concept), as it's used exclusively by LibWeb.
|
||||
size_t skip_when_determining_incumbent_counter { 0 };
|
||||
};
|
||||
|
||||
struct StackTraceElement {
|
||||
ExecutionContext* execution_context;
|
||||
Optional<UnrealizedSourceRange> source_range;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibJS/AST.h>
|
||||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibJS/JIT/NativeExecutable.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/BoundFunction.h>
|
||||
|
@ -1135,4 +1136,66 @@ void VM::pop_execution_context()
|
|||
on_call_stack_emptied();
|
||||
}
|
||||
|
||||
#if ARCH(X86_64)
|
||||
struct [[gnu::packed]] NativeStackFrame {
|
||||
NativeStackFrame* prev;
|
||||
FlatPtr return_address;
|
||||
};
|
||||
#endif
|
||||
|
||||
Vector<FlatPtr> VM::get_native_stack_trace() const
|
||||
{
|
||||
Vector<FlatPtr> buffer;
|
||||
#if ARCH(X86_64)
|
||||
// Manually walk the stack, because backtrace() does not traverse through JIT frames.
|
||||
auto* frame = bit_cast<NativeStackFrame*>(__builtin_frame_address(0));
|
||||
while (bit_cast<FlatPtr>(frame) < m_stack_info.top() && bit_cast<FlatPtr>(frame) >= m_stack_info.base()) {
|
||||
buffer.append(frame->return_address);
|
||||
frame = frame->prev;
|
||||
}
|
||||
#endif
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const* context, Vector<FlatPtr> const& native_stack)
|
||||
{
|
||||
// native function
|
||||
if (!context->executable)
|
||||
return {};
|
||||
|
||||
auto const* native_executable = context->executable->native_executable();
|
||||
if (!native_executable) {
|
||||
// Interpreter frame
|
||||
if (context->instruction_stream_iterator.has_value())
|
||||
return context->instruction_stream_iterator->source_range();
|
||||
return {};
|
||||
}
|
||||
|
||||
// JIT frame
|
||||
for (auto address : native_stack) {
|
||||
auto range = native_executable->get_source_range(*context->executable, address);
|
||||
if (range.has_value()) {
|
||||
auto realized = range->realize();
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<StackTraceElement> VM::stack_trace() const
|
||||
{
|
||||
auto native_stack = get_native_stack_trace();
|
||||
Vector<StackTraceElement> stack_trace;
|
||||
for (ssize_t i = m_execution_context_stack.size() - 1; i >= 0; i--) {
|
||||
auto* context = m_execution_context_stack[i];
|
||||
stack_trace.append({
|
||||
.execution_context = context,
|
||||
.source_range = get_source_range(context, native_stack).value_or({}),
|
||||
});
|
||||
}
|
||||
|
||||
return stack_trace;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -254,6 +254,8 @@ public:
|
|||
// NOTE: This is meant as a temporary stopgap until everything is bytecode.
|
||||
ThrowCompletionOr<Value> execute_ast_node(ASTNode const&);
|
||||
|
||||
Vector<StackTraceElement> stack_trace() const;
|
||||
|
||||
private:
|
||||
using ErrorMessages = AK::Array<String, to_underlying(ErrorMessage::__Count)>;
|
||||
|
||||
|
@ -277,6 +279,8 @@ private:
|
|||
|
||||
void set_well_known_symbols(WellKnownSymbols well_known_symbols) { m_well_known_symbols = move(well_known_symbols); }
|
||||
|
||||
Vector<FlatPtr> get_native_stack_trace() const;
|
||||
|
||||
HashMap<String, GCPtr<PrimitiveString>> m_string_cache;
|
||||
HashMap<DeprecatedString, GCPtr<PrimitiveString>> m_deprecated_string_cache;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue