From e3560c25457f181664108f193d7d7e932a33d7cf Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 17 Oct 2023 18:06:25 +0200 Subject: [PATCH] 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. --- Userland/Libraries/LibJS/JIT/Compiler.cpp | 95 ++++++++++++++++++++++- Userland/Libraries/LibJS/JIT/Compiler.h | 7 ++ 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibJS/JIT/Compiler.cpp b/Userland/Libraries/LibJS/JIT/Compiler.cpp index c873798fb8..6ad6a18f6a 100644 --- a/Userland/Libraries/LibJS/JIT/Compiler.cpp +++ b/Userland/Libraries/LibJS/JIT/Compiler.cpp @@ -6,11 +6,27 @@ #include #include +#include #include +#include #include #include #include +#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, \ + "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 const& handler, Optional 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(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(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 Compiler::compile(Bytecode::Executable const& bytecode_executable) @@ -189,6 +278,8 @@ OwnPtr 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()); diff --git a/Userland/Libraries/LibJS/JIT/Compiler.h b/Userland/Libraries/LibJS/JIT/Compiler.h index d57f113aeb..805047f76e 100644 --- a/Userland/Libraries/LibJS/JIT/Compiler.h +++ b/Userland/Libraries/LibJS/JIT/Compiler.h @@ -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 const& handler, Optional const& finalizer); + void pop_unwind_context(); + Vector m_output; Assembler m_assembler { m_output }; };