LibJS: Remove JIT compiler

The JIT compiler was an interesting experiment, but ultimately the
security & complexity cost of doing arbitrary code generation at runtime
is far too high.

In subsequent commits, the bytecode format will change drastically, and
instead of rewriting the JIT to fit the new bytecode, this patch simply
removes the JIT instead.

Other engines, JavaScriptCore in particular, have already proven that
it's possible to handle the vast majority of contemporary web content
with an interpreter. They are currently ~5x faster than us on benchmarks
when running without a JIT. We need to catch up to them before
considering performance techniques with a heavy security cost.
This commit is contained in:
Andreas Kling 2024-02-19 13:59:58 +01:00
parent cac11ac891
commit 1d29f9081f
Notes: sideshowbarker 2024-07-16 22:54:10 +09:00
12 changed files with 8 additions and 4347 deletions

View file

@ -114,8 +114,7 @@ jobs:
--test262 ../test262 \
--test262-parser-tests ../test262-parser-tests \
--results-json ../libjs-data/test262/results.json \
--per-file-output ../libjs-data/test262/per-file-master.json \
--per-file-jit-output ../libjs-data/test262/per-file-master-jit.json
--per-file-output ../libjs-data/test262/per-file-master.json
- name: Run test-wasm
working-directory: libjs-test262
@ -147,12 +146,6 @@ jobs:
- name: Compare test262 results
run: ./libjs-test262/per_file_result_diff.py -o old-libjs-data/test262/per-file-master.json -n libjs-data/test262/per-file-master.json
- name: Compare test262 JIT results
run: |
if [ -f old-libjs-data/test262/per-file-master-jit.json ]; then
./libjs-test262/per_file_result_diff.py -o old-libjs-data/test262/per-file-master-jit.json -n libjs-data/test262/per-file-master-jit.json
fi
- name: Compare Wasm results
run: ./libjs-test262/per_file_result_diff.py -o old-libjs-data/wasm/per-file-master.json -n libjs-data/wasm/per-file-master.json

View file

@ -48,8 +48,6 @@ shared_library("LibJS") {
"Heap/Heap.cpp",
"Heap/HeapBlock.cpp",
"Heap/MarkedVector.cpp",
"JIT/Compiler.cpp",
"JIT/NativeExecutable.cpp",
"Lexer.cpp",
"MarkupGenerator.cpp",
"Module.cpp",

View file

@ -7,8 +7,6 @@
#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/RegexTable.h>
#include <LibJS/JIT/Compiler.h>
#include <LibJS/JIT/NativeExecutable.h>
#include <LibJS/SourceCode.h>
namespace JS::Bytecode {
@ -56,13 +54,4 @@ void Executable::dump() const
}
}
JIT::NativeExecutable const* Executable::get_or_create_native_executable()
{
if (!m_did_try_jitting) {
m_did_try_jitting = true;
m_native_executable = JIT::Compiler::compile(*this);
}
return m_native_executable;
}
}

View file

@ -19,10 +19,6 @@
#include <LibJS/Heap/CellAllocator.h>
#include <LibJS/Runtime/EnvironmentCoordinate.h>
namespace JS::JIT {
class NativeExecutable;
}
namespace JS::Bytecode {
struct PropertyLookupCache {
@ -82,13 +78,6 @@ public:
DeprecatedFlyString const& get_identifier(IdentifierTableIndex index) const { return identifier_table->get(index); }
void dump() const;
JIT::NativeExecutable const* get_or_create_native_executable();
JIT::NativeExecutable const* native_executable() const { return m_native_executable; }
private:
OwnPtr<JIT::NativeExecutable> m_native_executable;
bool m_did_try_jitting { false };
};
}

View file

@ -15,8 +15,6 @@
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Op.h>
#include <LibJS/JIT/Compiler.h>
#include <LibJS/JIT/NativeExecutable.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/BigInt.h>
@ -373,21 +371,7 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa
vm().execution_context_stack().last()->executable = &executable;
if (auto native_executable = executable.get_or_create_native_executable()) {
auto block_index = 0;
if (entry_point)
block_index = executable.basic_blocks.find_first_index_if([&](auto const& block) { return block.ptr() == entry_point; }).value();
native_executable->run(vm(), block_index);
#if 0
for (size_t i = 0; i < vm().running_execution_context().local_variables.size(); ++i) {
dbgln("%{}: {}", i, vm().running_execution_context().local_variables[i]);
}
#endif
} else {
run_bytecode();
}
run_bytecode();
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &executable);

View file

@ -24,8 +24,6 @@ set(SOURCES
Heap/Heap.cpp
Heap/HeapBlock.cpp
Heap/MarkedVector.cpp
JIT/Compiler.cpp
JIT/NativeExecutable.cpp
Lexer.cpp
MarkupGenerator.cpp
Module.cpp
@ -269,7 +267,7 @@ set(SOURCES
)
serenity_lib(LibJS js)
target_link_libraries(LibJS PRIVATE LibCore LibCrypto LibFileSystem LibRegex LibSyntax LibLocale LibUnicode LibTimeZone LibJIT)
target_link_libraries(LibJS PRIVATE LibCore LibCrypto LibFileSystem LibRegex LibSyntax LibLocale LibUnicode LibTimeZone)
if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64")
target_link_libraries(LibJS PRIVATE LibX86)
endif()

File diff suppressed because it is too large Load diff

View file

@ -1,278 +0,0 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Platform.h>
#include <LibJIT/Assembler.h>
#include <LibJS/Bytecode/Builtins.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Op.h>
#include <LibJS/JIT/NativeExecutable.h>
#ifdef JIT_ARCH_SUPPORTED
namespace JS::JIT {
using ::JIT::Assembler;
class Compiler {
public:
static OwnPtr<NativeExecutable> compile(Bytecode::Executable&);
private:
# if ARCH(X86_64)
static constexpr auto GPR0 = Assembler::Reg::RAX;
static constexpr auto GPR1 = Assembler::Reg::RCX;
static constexpr auto GPR2 = Assembler::Reg::R13;
static constexpr auto ARG0 = Assembler::Reg::RDI;
static constexpr auto ARG1 = Assembler::Reg::RSI;
static constexpr auto ARG2 = Assembler::Reg::RDX;
static constexpr auto ARG3 = Assembler::Reg::RCX;
static constexpr auto ARG4 = Assembler::Reg::R8;
static constexpr auto ARG5 = Assembler::Reg::R9;
static constexpr auto FPR0 = Assembler::Reg::XMM0;
static constexpr auto FPR1 = Assembler::Reg::XMM1;
static constexpr auto RET = Assembler::Reg::RAX;
static constexpr auto STACK_POINTER = Assembler::Reg::RSP;
static constexpr auto REGISTER_ARRAY_BASE = Assembler::Reg::RBX;
static constexpr auto LOCALS_ARRAY_BASE = Assembler::Reg::R14;
static constexpr auto CACHED_ACCUMULATOR = Assembler::Reg::R12;
static constexpr auto RUNNING_EXECUTION_CONTEXT_BASE = Assembler::Reg::R15;
# endif
static Assembler::Reg argument_register(u32);
# define JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(O) \
O(Div, div) \
O(Exp, exp) \
O(Mod, mod) \
O(In, in) \
O(InstanceOf, instance_of)
# define JS_ENUMERATE_COMMON_UNARY_OPS_WITHOUT_FAST_PATH(O) \
O(BitwiseNot, bitwise_not) \
O(Not, not_) \
O(UnaryPlus, unary_plus) \
O(Typeof, typeof_)
# define JS_ENUMERATE_COMPARISON_OPS(O) \
O(LessThan, less_than, SignedLessThan, Below) \
O(LessThanEquals, less_than_equals, SignedLessThanOrEqualTo, BelowOrEqual) \
O(GreaterThan, greater_than, SignedGreaterThan, Above) \
O(GreaterThanEquals, greater_than_equals, SignedGreaterThanOrEqualTo, AboveOrEqual)
# define JS_ENUMERATE_NEW_BUILTIN_ERROR_BYTECODE_OPS(O) \
O(NewTypeError, new_type_error, TypeError)
# define JS_ENUMERATE_IMPLEMENTED_JIT_OPS(O) \
JS_ENUMERATE_COMMON_BINARY_OPS(O) \
JS_ENUMERATE_COMMON_UNARY_OPS(O) \
JS_ENUMERATE_NEW_BUILTIN_ERROR_BYTECODE_OPS(O) \
O(LoadImmediate, load_immediate) \
O(Load, load) \
O(Store, store) \
O(GetLocal, get_local) \
O(SetLocal, set_local) \
O(TypeofLocal, typeof_local) \
O(Jump, jump) \
O(JumpConditional, jump_conditional) \
O(JumpNullish, jump_nullish) \
O(JumpUndefined, jump_undefined) \
O(Increment, increment) \
O(Decrement, decrement) \
O(EnterUnwindContext, enter_unwind_context) \
O(LeaveUnwindContext, leave_unwind_context) \
O(Throw, throw) \
O(Catch, catch) \
O(CreateLexicalEnvironment, create_lexical_environment) \
O(LeaveLexicalEnvironment, leave_lexical_environment) \
O(EnterObjectEnvironment, enter_object_environment) \
O(ToNumeric, to_numeric) \
O(ResolveThisBinding, resolve_this_binding) \
O(Return, return) \
O(NewString, new_string) \
O(NewObject, new_object) \
O(NewArray, new_array) \
O(NewPrimitiveArray, new_primitive_array) \
O(NewFunction, new_function) \
O(NewRegExp, new_regexp) \
O(NewBigInt, new_bigint) \
O(NewClass, new_class) \
O(CreateVariable, create_variable) \
O(GetById, get_by_id) \
O(GetByValue, get_by_value) \
O(GetGlobal, get_global) \
O(GetVariable, get_variable) \
O(GetCalleeAndThisFromEnvironment, get_callee_and_this_from_environment) \
O(PutById, put_by_id) \
O(PutByValue, put_by_value) \
O(Call, call) \
O(CallWithArgumentArray, call_with_argument_array) \
O(TypeofVariable, typeof_variable) \
O(SetVariable, set_variable) \
O(ContinuePendingUnwind, continue_pending_unwind) \
O(ConcatString, concat_string) \
O(BlockDeclarationInstantiation, block_declaration_instantiation) \
O(SuperCallWithArgumentArray, super_call_with_argument_array) \
O(GetIterator, get_iterator) \
O(GetObjectFromIteratorRecord, get_object_from_iterator_record) \
O(GetNextMethodFromIteratorRecord, get_next_method_from_iterator_record) \
O(IteratorNext, iterator_next) \
O(ThrowIfNotObject, throw_if_not_object) \
O(ThrowIfNullish, throw_if_nullish) \
O(IteratorClose, iterator_close) \
O(IteratorToArray, iterator_to_array) \
O(Append, append) \
O(DeleteById, delete_by_id) \
O(DeleteByValue, delete_by_value) \
O(DeleteByValueWithThis, delete_by_value_with_this) \
O(GetObjectPropertyIterator, get_object_property_iterator) \
O(GetPrivateById, get_private_by_id) \
O(ResolveSuperBase, resolve_super_base) \
O(GetByIdWithThis, get_by_id_with_this) \
O(GetByValueWithThis, get_by_value_with_this) \
O(DeleteByIdWithThis, delete_by_id_with_this) \
O(PutByIdWithThis, put_by_id_with_this) \
O(PutPrivateById, put_private_by_id) \
O(ImportCall, import_call) \
O(GetImportMeta, get_import_meta) \
O(DeleteVariable, delete_variable) \
O(GetMethod, get_method) \
O(GetNewTarget, get_new_target) \
O(HasPrivateId, has_private_id) \
O(PutByValueWithThis, put_by_value_with_this) \
O(CopyObjectExcludingProperties, copy_object_excluding_properties) \
O(AsyncIteratorClose, async_iterator_close) \
O(Yield, yield) \
O(Await, await)
# define DECLARE_COMPILE_OP(OpTitleCase, op_snake_case, ...) \
void compile_##op_snake_case(Bytecode::Op::OpTitleCase const&);
JS_ENUMERATE_IMPLEMENTED_JIT_OPS(DECLARE_COMPILE_OP)
# undef DECLARE_COMPILE_OP
void compile_builtin(Bytecode::Builtin, Assembler::Label& slow_case, Assembler::Label& end);
# define DECLARE_COMPILE_BUILTIN(name, snake_case_name, ...) \
void compile_builtin_##snake_case_name(Assembler::Label& slow_case, Assembler::Label& end);
JS_ENUMERATE_BUILTINS(DECLARE_COMPILE_BUILTIN)
# undef DECLARE_COMPILE_BUILTIN
void store_vm_register(Bytecode::Register, Assembler::Reg);
void load_vm_register(Assembler::Reg, Bytecode::Register);
void store_vm_local(size_t, Assembler::Reg);
void load_vm_local(Assembler::Reg, size_t);
void reload_cached_accumulator();
void flush_cached_accumulator();
void load_accumulator(Assembler::Reg);
void store_accumulator(Assembler::Reg);
void compile_continuation(Optional<Bytecode::Label>, bool is_await);
template<typename Codegen>
void branch_if_same_type_for_equality(Assembler::Reg, Assembler::Reg, Codegen);
void compile_is_strictly_equal(Assembler::Reg, Assembler::Reg, Assembler::Label& slow_case);
void check_exception();
void handle_exception();
void jump_to_exit();
void native_call(void* function_address, Vector<Assembler::Operand> const& stack_arguments = {});
void jump_if_int32(Assembler::Reg, Assembler::Label&);
template<typename Codegen>
void branch_if_type(Assembler::Reg, u16 type_tag, Codegen);
template<typename Codegen>
void branch_if_int32(Assembler::Reg reg, Codegen codegen)
{
branch_if_type(reg, INT32_TAG, codegen);
}
template<typename Codegen>
void branch_if_boolean(Assembler::Reg reg, Codegen codegen)
{
branch_if_type(reg, BOOLEAN_TAG, codegen);
}
template<typename Codegen>
void branch_if_object(Assembler::Reg reg, Codegen codegen)
{
branch_if_type(reg, OBJECT_TAG, codegen);
}
void extract_object_pointer(Assembler::Reg dst_object, Assembler::Reg src_value);
void convert_to_double(Assembler::Reg dst, Assembler::Reg src, Assembler::Reg nan, Assembler::Reg temp, Assembler::Label& not_number);
template<typename Codegen>
void branch_if_both_int32(Assembler::Reg, Assembler::Reg, Codegen);
void jump_if_not_double(Assembler::Reg reg, Assembler::Reg nan, Assembler::Reg temp, Assembler::Label&);
template<typename CodegenI32, typename CodegenDouble, typename CodegenValue>
void compile_binary_op_fastpaths(Assembler::Reg lhs, Assembler::Reg rhs, CodegenI32, CodegenDouble, CodegenValue);
template<typename CodegenI32, typename CodegenDouble, typename CodegenValue>
void compiler_comparison_fastpaths(Assembler::Reg lhs, Assembler::Reg rhs, CodegenI32, CodegenDouble, CodegenValue);
explicit Compiler(Bytecode::Executable& bytecode_executable)
: m_bytecode_executable(bytecode_executable)
{
}
Assembler::Label& label_for(Bytecode::BasicBlock const& block)
{
return block_data_for(block).label;
}
struct BasicBlockData {
size_t start_offset { 0 };
Assembler::Label label;
};
BasicBlockData& block_data_for(Bytecode::BasicBlock const& block)
{
return *m_basic_block_data.ensure(&block, [] {
return make<BasicBlockData>();
});
}
void set_current_block(Bytecode::BasicBlock const& block)
{
m_current_block = &block;
}
Bytecode::BasicBlock const& current_block()
{
return *m_current_block;
}
HashMap<Bytecode::BasicBlock const*, NonnullOwnPtr<BasicBlockData>> m_basic_block_data;
Vector<u8> m_output;
Assembler m_assembler { m_output };
Assembler::Label m_exit_label;
Bytecode::Executable& m_bytecode_executable;
Bytecode::BasicBlock const* m_current_block;
};
}
#else
namespace JS::JIT {
class Compiler {
public:
static OwnPtr<NativeExecutable> compile(Bytecode::Executable&) { return nullptr; }
};
}
#endif

View file

@ -1,199 +0,0 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BinarySearch.h>
#include <LibJIT/GDB.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/JIT/NativeExecutable.h>
#include <LibJS/Runtime/VM.h>
#include <LibX86/Disassembler.h>
#include <sys/mman.h>
namespace JS::JIT {
NativeExecutable::NativeExecutable(void* code, size_t size, Vector<BytecodeMapping> mapping, Optional<FixedArray<u8>> gdb_object)
: m_code(code)
, m_size(size)
, m_mapping(move(mapping))
, m_gdb_object(move(gdb_object))
{
// Translate block index to instruction address, so the native code can just jump to it.
for (auto const& entry : m_mapping) {
if (entry.block_index == BytecodeMapping::EXECUTABLE)
continue;
if (entry.bytecode_offset == 0) {
VERIFY(entry.block_index == m_block_entry_points.size());
m_block_entry_points.append(bit_cast<FlatPtr>(m_code) + entry.native_offset);
}
}
if (m_gdb_object.has_value())
::JIT::GDB::register_into_gdb(m_gdb_object.value().span());
}
NativeExecutable::~NativeExecutable()
{
if (m_gdb_object.has_value())
::JIT::GDB::unregister_from_gdb(m_gdb_object.value().span());
munmap(m_code, m_size);
}
void NativeExecutable::run(VM& vm, size_t entry_point) const
{
FlatPtr entry_point_address = 0;
if (entry_point != 0) {
entry_point_address = m_block_entry_points[entry_point];
VERIFY(entry_point_address != 0);
}
typedef void (*JITCode)(VM&, Value* registers, Value* locals, FlatPtr entry_point_address, ExecutionContext&);
((JITCode)m_code)(vm,
vm.bytecode_interpreter().registers().data(),
vm.running_execution_context().locals.data(),
entry_point_address,
vm.running_execution_context());
}
#if ARCH(X86_64)
class JITSymbolProvider : public X86::SymbolProvider {
public:
JITSymbolProvider(NativeExecutable const& executable)
: m_executable(executable)
{
}
virtual ~JITSymbolProvider() override = default;
virtual ByteString symbolicate(FlatPtr address, u32* offset = nullptr) const override
{
auto base = bit_cast<FlatPtr>(m_executable.code_bytes().data());
auto native_offset = static_cast<u32>(address - base);
if (native_offset >= m_executable.code_bytes().size())
return {};
auto const& entry = m_executable.find_mapping_entry(native_offset);
if (offset)
*offset = native_offset - entry.native_offset;
if (entry.block_index == BytecodeMapping::EXECUTABLE)
return BytecodeMapping::EXECUTABLE_LABELS[entry.bytecode_offset];
if (entry.bytecode_offset == 0)
return ByteString::formatted("Block {}", entry.block_index + 1);
return ByteString::formatted("{}:{:x}", entry.block_index + 1, entry.bytecode_offset);
}
private:
NativeExecutable const& m_executable;
};
#endif
void NativeExecutable::dump_disassembly([[maybe_unused]] Bytecode::Executable const& executable) const
{
#if ARCH(X86_64)
auto const* code_bytes = static_cast<u8 const*>(m_code);
auto stream = X86::SimpleInstructionStream { code_bytes, m_size };
auto disassembler = X86::Disassembler(stream);
auto symbol_provider = JITSymbolProvider(*this);
auto mapping = m_mapping.begin();
if (!executable.basic_blocks.is_empty() && executable.basic_blocks[0]->size() != 0) {
auto first_instruction = Bytecode::InstructionStreamIterator { executable.basic_blocks[0]->instruction_stream(), &executable };
auto source_range = first_instruction.source_range().realize();
dbgln("Disassembly of '{}' ({}:{}:{}):", executable.name, source_range.filename(), source_range.start.line, source_range.start.column);
} else {
dbgln("Disassembly of '{}':", executable.name);
}
while (true) {
auto offset = stream.offset();
auto virtual_offset = bit_cast<size_t>(m_code) + offset;
while (!mapping.is_end() && offset > mapping->native_offset)
++mapping;
if (!mapping.is_end() && offset == mapping->native_offset) {
if (mapping->block_index == BytecodeMapping::EXECUTABLE) {
dbgln("{}:", BytecodeMapping::EXECUTABLE_LABELS[mapping->bytecode_offset]);
} else {
auto const& block = *executable.basic_blocks[mapping->block_index];
if (mapping->bytecode_offset == 0)
dbgln("\nBlock {}:", mapping->block_index + 1);
if (block.size() != 0) {
VERIFY(mapping->bytecode_offset < block.size());
auto const& instruction = *reinterpret_cast<Bytecode::Instruction const*>(block.data() + mapping->bytecode_offset);
dbgln("{}:{:x} {}:", mapping->block_index + 1, mapping->bytecode_offset, instruction.to_byte_string(executable));
}
}
}
auto insn = disassembler.next();
if (!insn.has_value())
break;
StringBuilder builder;
builder.appendff("{:p} ", virtual_offset);
auto length = insn.value().length();
for (size_t i = 0; i < 7; i++) {
if (i < length)
builder.appendff("{:02x} ", code_bytes[offset + i]);
else
builder.append(" "sv);
}
builder.append(" "sv);
builder.append(insn.value().to_byte_string(virtual_offset, &symbol_provider));
dbgln("{}", builder.string_view());
for (size_t bytes_printed = 7; bytes_printed < length; bytes_printed += 7) {
builder.clear();
builder.appendff("{:p} ", virtual_offset + bytes_printed);
for (size_t i = bytes_printed; i < bytes_printed + 7 && i < length; i++)
builder.appendff(" {:02x}", code_bytes[offset + i]);
dbgln("{}", builder.string_view());
}
}
dbgln();
#endif
}
BytecodeMapping const& NativeExecutable::find_mapping_entry(size_t native_offset) const
{
size_t nearby_index = 0;
AK::binary_search(
m_mapping,
native_offset,
&nearby_index,
[](FlatPtr needle, BytecodeMapping const& mapping_entry) {
if (needle > mapping_entry.native_offset)
return 1;
if (needle == mapping_entry.native_offset)
return 0;
return -1;
});
return m_mapping[nearby_index];
}
Optional<UnrealizedSourceRange> NativeExecutable::get_source_range(Bytecode::Executable const& executable, FlatPtr address) const
{
auto start = bit_cast<FlatPtr>(m_code);
auto end = start + m_size;
if (address < start || address >= end)
return {};
auto const& entry = find_mapping_entry(address - start - 1);
if (entry.block_index < executable.basic_blocks.size()) {
auto const& block = *executable.basic_blocks[entry.block_index];
if (entry.bytecode_offset < block.size()) {
auto iterator = Bytecode::InstructionStreamIterator { block.instruction_stream(), &executable, entry.bytecode_offset };
return iterator.source_range();
}
}
return {};
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FixedArray.h>
#include <AK/Noncopyable.h>
#include <AK/Types.h>
#include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Runtime/Completion.h>
namespace JS::JIT {
struct BytecodeMapping {
size_t native_offset;
size_t block_index;
size_t bytecode_offset;
// Special block index for labels outside any blocks.
static constexpr auto EXECUTABLE = NumericLimits<size_t>::max();
static constexpr auto EXECUTABLE_LABELS = AK::Array { "entry"sv, "common_exit"sv };
};
class NativeExecutable {
AK_MAKE_NONCOPYABLE(NativeExecutable);
AK_MAKE_NONMOVABLE(NativeExecutable);
public:
NativeExecutable(void* code, size_t size, Vector<BytecodeMapping>, Optional<FixedArray<u8>> gdb_object = {});
~NativeExecutable();
void run(VM&, size_t entry_point) const;
void dump_disassembly(Bytecode::Executable const& executable) const;
BytecodeMapping const& find_mapping_entry(size_t native_offset) const;
Optional<UnrealizedSourceRange> get_source_range(Bytecode::Executable const& executable, FlatPtr address) const;
ReadonlyBytes code_bytes() const { return { m_code, m_size }; }
private:
void* m_code { nullptr };
size_t m_size { 0 };
Vector<BytecodeMapping> m_mapping;
Vector<FlatPtr> m_block_entry_points;
mutable OwnPtr<Bytecode::InstructionStreamIterator> m_instruction_stream_iterator;
Optional<FixedArray<u8>> m_gdb_object;
};
}

View file

@ -15,7 +15,6 @@
#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/ArrayBuffer.h>
@ -974,53 +973,26 @@ struct [[gnu::packed]] NativeStackFrame {
};
#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)
static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const* context)
{
// 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())
return range;
}
// Interpreter frame
if (context->instruction_stream_iterator.has_value())
return context->instruction_stream_iterator->source_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({}),
.source_range = get_source_range(context).value_or({}),
});
}

View file

@ -286,8 +286,6 @@ 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<ByteString, GCPtr<PrimitiveString>> m_byte_string_cache;