mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 20:57:35 +00:00
LibJS/JIT: Propagate exceptions in the simplest case :^)
We now establish a stack of "unwind contexts" similar to what the bytecode interpreter does, but here, it's a stack of structs with addresses to the catch and finally blocks. Unwind contexts also have a "valid" flag, and the root unwind context (always present, pushed on JIT code entry) has valid=false, which we interpret in check_exception() as "return and let our caller deal with the exception". Anything in Compiler that may generate an exception should now also call check_exception() ASAP to emit the code for handling this.
This commit is contained in:
parent
3523f9f722
commit
e3560c2545
2 changed files with 100 additions and 2 deletions
|
@ -6,11 +6,27 @@
|
|||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibJS/Bytecode/Instruction.h>
|
||||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibJS/JIT/Compiler.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TRY_OR_SET_EXCEPTION(expression) \
|
||||
({ \
|
||||
/* Ignore -Wshadow to allow nesting the macro. */ \
|
||||
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
|
||||
auto&& _temporary_result = (expression)); \
|
||||
static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
|
||||
"Do not return a reference from a fallible expression"); \
|
||||
if (_temporary_result.is_error()) [[unlikely]] { \
|
||||
vm.bytecode_interpreter().reg(Bytecode::Register::exception()) = _temporary_result.release_error().value().value(); \
|
||||
return {}; \
|
||||
} \
|
||||
_temporary_result.release_value(); \
|
||||
})
|
||||
|
||||
namespace JS::JIT {
|
||||
|
||||
void Compiler::store_vm_register(Bytecode::Register dst, Assembler::Reg src)
|
||||
|
@ -154,12 +170,12 @@ void Compiler::compile_less_than(Bytecode::Op::LessThan const& op)
|
|||
load_vm_register(ARG2, Bytecode::Register::accumulator());
|
||||
m_assembler.native_call((void*)cxx_less_than);
|
||||
store_vm_register(Bytecode::Register::accumulator(), RET);
|
||||
check_exception();
|
||||
}
|
||||
|
||||
[[maybe_unused]] static Value cxx_increment(VM& vm, Value value)
|
||||
{
|
||||
// FIXME: Handle exceptions!
|
||||
auto old_value = MUST(value.to_numeric(vm));
|
||||
auto old_value = TRY_OR_SET_EXCEPTION(value.to_numeric(vm));
|
||||
if (old_value.is_number())
|
||||
return Value(old_value.as_double() + 1);
|
||||
return BigInt::create(vm, old_value.as_bigint().big_integer().plus(Crypto::SignedBigInteger { 1 }));
|
||||
|
@ -170,6 +186,79 @@ void Compiler::compile_increment(Bytecode::Op::Increment const&)
|
|||
load_vm_register(ARG1, Bytecode::Register::accumulator());
|
||||
m_assembler.native_call((void*)cxx_increment);
|
||||
store_vm_register(Bytecode::Register::accumulator(), RET);
|
||||
check_exception();
|
||||
}
|
||||
|
||||
void Compiler::check_exception()
|
||||
{
|
||||
// if (exception.is_empty()) goto no_exception;
|
||||
load_vm_register(GPR0, Bytecode::Register::exception());
|
||||
m_assembler.mov(Assembler::Operand::Register(GPR1), Assembler::Operand::Imm64(Value().encoded()));
|
||||
auto no_exception = m_assembler.make_label();
|
||||
m_assembler.jump_if_equal(Assembler::Operand::Register(GPR0), Assembler::Operand::Register(GPR1), no_exception);
|
||||
|
||||
// We have an exception!
|
||||
|
||||
// if (!unwind_context.valid) return;
|
||||
auto handle_exception = m_assembler.make_label();
|
||||
m_assembler.mov(
|
||||
Assembler::Operand::Register(GPR0),
|
||||
Assembler::Operand::Mem64BaseAndOffset(UNWIND_CONTEXT_BASE, 0));
|
||||
m_assembler.jump_if_not_equal(
|
||||
Assembler::Operand::Register(GPR0),
|
||||
Assembler::Operand::Imm32(0),
|
||||
handle_exception);
|
||||
|
||||
m_assembler.exit();
|
||||
|
||||
// handle_exception:
|
||||
handle_exception.link(m_assembler);
|
||||
|
||||
// no_exception:
|
||||
no_exception.link(m_assembler);
|
||||
}
|
||||
|
||||
void Compiler::push_unwind_context(bool valid, Optional<Bytecode::Label> const& handler, Optional<Bytecode::Label> const& finalizer)
|
||||
{
|
||||
// Put this on the stack, and then point UNWIND_CONTEXT_BASE at it.
|
||||
// struct {
|
||||
// u64 valid;
|
||||
// u64 handler;
|
||||
// u64 finalizer;
|
||||
// };
|
||||
|
||||
// push finalizer (patched later)
|
||||
m_assembler.mov(
|
||||
Assembler::Operand::Register(GPR0),
|
||||
Assembler::Operand::Imm64(0xdeadbeef));
|
||||
if (finalizer.has_value())
|
||||
const_cast<Bytecode::BasicBlock&>(finalizer.value().block()).absolute_references_to_here.append(m_assembler.m_output.size() - 8);
|
||||
m_assembler.push(Assembler::Operand::Register(GPR0));
|
||||
|
||||
// push handler (patched later)
|
||||
m_assembler.mov(
|
||||
Assembler::Operand::Register(GPR0),
|
||||
Assembler::Operand::Imm64(0xdeadbeef));
|
||||
if (handler.has_value())
|
||||
const_cast<Bytecode::BasicBlock&>(handler.value().block()).absolute_references_to_here.append(m_assembler.m_output.size() - 8);
|
||||
m_assembler.push(Assembler::Operand::Register(GPR0));
|
||||
|
||||
// push valid
|
||||
m_assembler.push(Assembler::Operand::Imm32(valid));
|
||||
|
||||
// UNWIND_CONTEXT_BASE = STACK_POINTER
|
||||
m_assembler.mov(
|
||||
Assembler::Operand::Register(UNWIND_CONTEXT_BASE),
|
||||
Assembler::Operand::Register(STACK_POINTER));
|
||||
|
||||
// align stack pointer
|
||||
m_assembler.sub(Assembler::Operand::Register(STACK_POINTER), Assembler::Operand::Imm8(8));
|
||||
}
|
||||
|
||||
void Compiler::pop_unwind_context()
|
||||
{
|
||||
m_assembler.add(Assembler::Operand::Register(STACK_POINTER), Assembler::Operand::Imm8(32));
|
||||
m_assembler.add(Assembler::Operand::Register(UNWIND_CONTEXT_BASE), Assembler::Operand::Imm8(32));
|
||||
}
|
||||
|
||||
OwnPtr<NativeExecutable> Compiler::compile(Bytecode::Executable const& bytecode_executable)
|
||||
|
@ -189,6 +278,8 @@ OwnPtr<NativeExecutable> Compiler::compile(Bytecode::Executable const& bytecode_
|
|||
Assembler::Operand::Register(LOCALS_ARRAY_BASE),
|
||||
Assembler::Operand::Register(ARG2));
|
||||
|
||||
compiler.push_unwind_context(false, {}, {});
|
||||
|
||||
for (auto& block : bytecode_executable.basic_blocks) {
|
||||
block->offset = compiler.m_output.size();
|
||||
auto it = Bytecode::InstructionStreamIterator(block->instruction_stream());
|
||||
|
|
|
@ -24,8 +24,10 @@ private:
|
|||
static constexpr auto ARG1 = Assembler::Reg::RSI;
|
||||
static constexpr auto ARG2 = Assembler::Reg::RDX;
|
||||
static constexpr auto RET = Assembler::Reg::RAX;
|
||||
static constexpr auto STACK_POINTER = Assembler::Reg::RSP;
|
||||
static constexpr auto REGISTER_ARRAY_BASE = Assembler::Reg::R8;
|
||||
static constexpr auto LOCALS_ARRAY_BASE = Assembler::Reg::R9;
|
||||
static constexpr auto UNWIND_CONTEXT_BASE = Assembler::Reg::R10;
|
||||
|
||||
void compile_load_immediate(Bytecode::Op::LoadImmediate const&);
|
||||
void compile_load(Bytecode::Op::Load const&);
|
||||
|
@ -45,6 +47,11 @@ private:
|
|||
|
||||
void compile_to_boolean(Assembler::Reg dst, Assembler::Reg src);
|
||||
|
||||
void check_exception();
|
||||
|
||||
void push_unwind_context(bool valid, Optional<Bytecode::Label> const& handler, Optional<Bytecode::Label> const& finalizer);
|
||||
void pop_unwind_context();
|
||||
|
||||
Vector<u8> m_output;
|
||||
Assembler m_assembler { m_output };
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue