Browse Source

LibJS: Make return control flow more static

With this only `ContinuePendingUnwind` needs to dynamically check if a
scheduled return needs to go through a `finally` block, making the
interpreter loop a bit nicer
Hendiadyoin1 1 năm trước cách đây
mục cha
commit
c8e4499b08

+ 7 - 13
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -1766,13 +1766,10 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ReturnStatement::genera
         return_value = generator.add_constant(js_undefined());
     }
 
-    if (generator.is_in_generator_or_async_function()) {
-        generator.perform_needed_unwinds<Bytecode::Op::Yield>();
-        generator.emit<Bytecode::Op::Yield>(nullptr, *return_value);
-    } else {
-        generator.perform_needed_unwinds<Bytecode::Op::Return>();
-        generator.emit<Bytecode::Op::Return>(return_value.has_value() ? return_value->operand() : Optional<Operand> {});
-    }
+    if (generator.is_in_generator_or_async_function())
+        generator.emit_return<Bytecode::Op::Yield>(return_value.value());
+    else
+        generator.emit_return<Bytecode::Op::Return>(return_value.value());
 
     return return_value;
 }
@@ -2117,8 +2114,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
 
         // 2. Return ? received.
         // NOTE: This will always be a return completion.
-        generator.perform_needed_unwinds<Bytecode::Op::Yield>();
-        generator.emit<Bytecode::Op::Yield>(nullptr, received_completion_value);
+        generator.emit_return<Bytecode::Op::Yield>(received_completion_value);
 
         generator.switch_to_basic_block(return_is_defined_block);
 
@@ -2155,8 +2151,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
         generator.emit_iterator_value(inner_return_result_value, inner_return_result);
 
         // 2. Return Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
-        generator.perform_needed_unwinds<Bytecode::Op::Yield>();
-        generator.emit<Bytecode::Op::Yield>(nullptr, inner_return_result_value);
+        generator.emit_return<Bytecode::Op::Yield>(inner_return_result_value);
 
         generator.switch_to_basic_block(type_is_return_not_done_block);
 
@@ -2239,8 +2234,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
     generator.emit<Bytecode::Op::Throw>(received_completion_value);
 
     generator.switch_to_basic_block(return_value_block);
-    generator.perform_needed_unwinds<Bytecode::Op::Yield>();
-    generator.emit<Bytecode::Op::Yield>(nullptr, received_completion_value);
+    generator.emit_return<Bytecode::Op::Yield>(received_completion_value);
 
     generator.switch_to_basic_block(normal_completion_continuation_block);
     return received_completion_value;

+ 1 - 1
Userland/Libraries/LibJS/Bytecode/Generator.cpp

@@ -232,7 +232,7 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::emit_function_body_by
             if (block->is_terminated())
                 continue;
             generator.switch_to_basic_block(*block);
-            generator.emit<Bytecode::Op::Yield>(nullptr, generator.add_constant(js_undefined()));
+            generator.emit_return<Bytecode::Op::Yield>(generator.add_constant(js_undefined()));
         }
     }
 

+ 30 - 0
Userland/Libraries/LibJS/Bytecode/Generator.h

@@ -261,6 +261,7 @@ public:
     }
 
     bool is_in_finalizer() const { return m_boundaries.contains_slow(BlockBoundaryType::LeaveFinally); }
+    bool must_enter_finalizer() const { return m_boundaries.contains_slow(BlockBoundaryType::ReturnToFinally); }
 
     void generate_break();
     void generate_break(DeprecatedFlyString const& break_label);
@@ -268,6 +269,35 @@ public:
     void generate_continue();
     void generate_continue(DeprecatedFlyString const& continue_label);
 
+    template<typename OpType>
+    void emit_return(ScopedOperand value)
+    requires(IsOneOf<OpType, Op::Return, Op::Yield>)
+    {
+        // FIXME: Tell the call sites about the `saved_return_value` destination
+        //        And take that into account in the movs below.
+        perform_needed_unwinds<OpType>();
+        if (must_enter_finalizer()) {
+            VERIFY(m_current_basic_block->finalizer() != nullptr);
+            // Compare to:
+            // *  Interpreter::do_return
+            // *  Interpreter::run_bytecode::handle_ContinuePendingUnwind
+            // *  Return::execute_impl
+            // *  Yield::execute_impl
+            emit<Bytecode::Op::Mov>(Operand(Register::saved_return_value()), value);
+            emit<Bytecode::Op::Mov>(Operand(Register::exception()), add_constant(Value {}));
+            // FIXME: Do we really need to clear the return value register here?
+            emit<Bytecode::Op::Mov>(Operand(Register::return_value()), add_constant(Value {}));
+
+            emit<Bytecode::Op::Jump>(Label { *m_current_basic_block->finalizer() });
+            return;
+        }
+
+        if constexpr (IsSame<OpType, Op::Return>)
+            emit<Op::Return>(value);
+        else
+            emit<Op::Yield>(nullptr, value);
+    }
+
     void start_boundary(BlockBoundaryType type) { m_boundaries.append(type); }
     void end_boundary(BlockBoundaryType type)
     {

+ 16 - 24
Userland/Libraries/LibJS/Bytecode/Interpreter.cpp

@@ -356,8 +356,6 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
         goto* bytecode_dispatch_table[static_cast<size_t>(next_instruction.type())];                \
     } while (0)
 
-    bool will_yield = false;
-
     for (;;) {
     start:
         for (;;) {
@@ -486,7 +484,19 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
             }
             if (!saved_return_value().is_empty()) {
                 do_return(saved_return_value());
-                goto run_finalizer_and_return;
+                if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
+                    if (auto finalizer = handlers.value().finalizer_offset; finalizer.has_value()) {
+                        VERIFY(!running_execution_context.unwind_contexts.is_empty());
+                        auto& unwind_context = running_execution_context.unwind_contexts.last();
+                        VERIFY(unwind_context.executable == m_current_executable);
+                        reg(Register::saved_return_value()) = reg(Register::return_value());
+                        reg(Register::return_value()) = {};
+                        program_counter = finalizer.value();
+                        // the unwind_context will be pop'ed when entering the finally block
+                        goto start;
+                    }
+                }
+                return;
             }
             auto const old_scheduled_jump = running_execution_context.previously_scheduled_jumps.take_last();
             if (m_scheduled_jump.has_value()) {
@@ -639,14 +649,13 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
         handle_Await: {
             auto& instruction = *reinterpret_cast<Op::Await const*>(&bytecode[program_counter]);
             instruction.execute_impl(*this);
-            will_yield = true;
-            goto run_finalizer_and_return;
+            return;
         }
 
         handle_Return: {
             auto& instruction = *reinterpret_cast<Op::Return const*>(&bytecode[program_counter]);
             instruction.execute_impl(*this);
-            goto run_finalizer_and_return;
+            return;
         }
 
         handle_Yield: {
@@ -657,25 +666,8 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
             //       but we generate a Yield Operation in the case of returns in
             //       generators as well, so we need to check if it will actually
             //       continue or is a `return` in disguise
-            will_yield = instruction.continuation().has_value();
-            goto run_finalizer_and_return;
-        }
+            return;
         }
-    }
-
-run_finalizer_and_return:
-    if (!will_yield) {
-        if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
-            if (auto finalizer = handlers.value().finalizer_offset; finalizer.has_value()) {
-                VERIFY(!running_execution_context.unwind_contexts.is_empty());
-                auto& unwind_context = running_execution_context.unwind_contexts.last();
-                VERIFY(unwind_context.executable == m_current_executable);
-                reg(Register::saved_return_value()) = reg(Register::return_value());
-                reg(Register::return_value()) = {};
-                program_counter = finalizer.value();
-                // the unwind_context will be pop'ed when entering the finally block
-                goto start;
-            }
         }
     }
 }