mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
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:
parent
cac11ac891
commit
1d29f9081f
Notes:
sideshowbarker
2024-07-16 22:54:10 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/1d29f9081f Pull-request: https://github.com/SerenityOS/serenity/pull/23259 Reviewed-by: https://github.com/trflynn89 ✅
12 changed files with 8 additions and 4347 deletions
9
.github/workflows/libjs-test262.yml
vendored
9
.github/workflows/libjs-test262.yml
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
@ -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 = █
|
||||
}
|
||||
|
||||
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
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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({}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue