From 67cc31a74f0b575779c19f457e67501fc4e47f7b Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 10 Jun 2021 15:04:38 +0200 Subject: [PATCH] LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x --- Userland/Libraries/LibJS/AST.h | 1 + .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 54 +++++++++++++++++++ .../Libraries/LibJS/Bytecode/BasicBlock.h | 5 ++ Userland/Libraries/LibJS/Bytecode/Generator.h | 2 + .../Libraries/LibJS/Bytecode/Instruction.h | 5 +- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 40 +++++++++++++- .../Libraries/LibJS/Bytecode/Interpreter.h | 8 +++ Userland/Libraries/LibJS/Bytecode/Op.cpp | 32 +++++++++++ Userland/Libraries/LibJS/Bytecode/Op.h | 44 +++++++++++++++ 9 files changed, 188 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 24cb119328..af4e1ca842 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1227,6 +1227,7 @@ public: virtual void dump(int indent) const override; virtual Value execute(Interpreter&, GlobalObject&) const override; + virtual void generate_bytecode(Bytecode::Generator&) const override; private: NonnullRefPtr m_block; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 5ceaefd069..3910cd8e31 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -798,4 +798,58 @@ void BreakStatement::generate_bytecode(Bytecode::Generator& generator) const {}); } +void TryStatement::generate_bytecode(Bytecode::Generator& generator) const +{ + auto& saved_block = generator.current_block(); + + Optional handler_target; + Optional finalizer_target; + + Bytecode::BasicBlock* next_block { nullptr }; + + if (m_finalizer) { + auto& finalizer_block = generator.make_block(); + generator.switch_to_basic_block(finalizer_block); + m_finalizer->generate_bytecode(generator); + if (!generator.is_current_block_terminated()) { + next_block = &generator.make_block(); + auto next_target = Bytecode::Label { *next_block }; + generator.emit(next_target); + } + finalizer_target = Bytecode::Label { finalizer_block }; + } + + if (m_handler) { + auto& handler_block = generator.make_block(); + generator.switch_to_basic_block(handler_block); + if (!m_finalizer) + generator.emit(); + if (!m_handler->parameter().is_empty()) { + // FIXME: We need a separate LexicalEnvironment here + generator.emit(generator.intern_string(m_handler->parameter())); + } + m_handler->body().generate_bytecode(generator); + handler_target = Bytecode::Label { handler_block }; + if (!generator.is_current_block_terminated()) { + if (m_finalizer) { + generator.emit(); + generator.emit(finalizer_target); + } else { + VERIFY(!next_block); + next_block = &generator.make_block(); + auto next_target = Bytecode::Label { *next_block }; + generator.emit(next_target); + } + } + } + + generator.switch_to_basic_block(saved_block); + generator.emit(handler_target, finalizer_target); + m_block->generate_bytecode(generator); + if (m_finalizer && !generator.is_current_block_terminated()) + generator.emit(finalizer_target); + + generator.switch_to_basic_block(next_block ? *next_block : saved_block); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/BasicBlock.h b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h index 17f0e8f73b..384e373d0c 100644 --- a/Userland/Libraries/LibJS/Bytecode/BasicBlock.h +++ b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h @@ -38,6 +38,11 @@ private: size_t m_offset { 0 }; }; +struct UnwindInfo { + BasicBlock const* handler; + BasicBlock const* finalizer; +}; + class BasicBlock { public: static NonnullOwnPtr create(String name); diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index ed425e2c22..f3d8fabf6e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -68,6 +68,8 @@ public: m_current_basic_block = █ } + [[nodiscard]] BasicBlock& current_block() { return *m_current_basic_block; } + BasicBlock& make_block(String name = {}) { if (name.is_empty()) diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index b52a712123..61aa3aac3b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -57,7 +57,10 @@ O(ConcatString) \ O(Increment) \ O(Decrement) \ - O(Throw) + O(Throw) \ + O(EnterUnwindContext) \ + O(LeaveUnwindContext) \ + O(ContinuePendingUnwind) namespace JS::Bytecode { diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index c553fbcca5..0d36b5ac46 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -67,8 +67,25 @@ Value Interpreter::run(Executable const& executable) while (!pc.at_end()) { auto& instruction = *pc; instruction.execute(*this); - if (vm().exception()) - break; + if (vm().exception()) { + m_saved_exception = {}; + if (m_unwind_contexts.is_empty()) + break; + auto& unwind_context = m_unwind_contexts.last(); + if (unwind_context.handler) { + block = unwind_context.handler; + unwind_context.handler = nullptr; + accumulator() = vm().exception()->value(); + vm().clear_exception(); + will_jump = true; + } else if (unwind_context.finalizer) { + block = unwind_context.finalizer; + m_unwind_contexts.take_last(); + will_jump = true; + m_saved_exception = Handle::create(vm().exception()); + vm().clear_exception(); + } + } if (m_pending_jump.has_value()) { block = m_pending_jump.release_value(); will_jump = true; @@ -121,4 +138,23 @@ Value Interpreter::run(Executable const& executable) return return_value; } +void Interpreter::enter_unwind_context(Optional