Skip to content

Commit

Permalink
LibJS: Make return control flow more static
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Hendiadyoin1 authored and awesomekling committed May 15, 2024
1 parent 73fdd31 commit c8e4499
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 38 deletions.
20 changes: 7 additions & 13 deletions Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibJS/Bytecode/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
}

Expand Down
30 changes: 30 additions & 0 deletions Userland/Libraries/LibJS/Bytecode/Generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,43 @@ class Generator {
}

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);

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)
{
Expand Down
40 changes: 16 additions & 24 deletions Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 (;;) {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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: {
Expand All @@ -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;
}
}
}
}
Expand Down

0 comments on commit c8e4499

Please sign in to comment.