LibJS: Merge CallFrame into ExecutionContext

Before this change both ExecutionContext and CallFrame were created
before executing function/module/script with a couple exceptions:
- executable created for default function argument evaluation has to
  run in function's execution context.
- `execute_ast_node()` where executable compiled for ASTNode has to be
  executed in running execution context.

This change moves all members previously owned by CallFrame into
ExecutionContext, and makes two exceptions where an executable that does
not have a corresponding execution context saves and restores registers
before running.

Now, all execution state lives in a single entity, which makes it a bit
easier to reason about and opens opportunities for optimizations, such
as moving registers and local variables into a single array.
This commit is contained in:
Aliaksandr Kalenik 2024-05-01 19:33:49 +02:00 committed by Andreas Kling
commit 865e651a7d
Notes: sideshowbarker 2024-07-17 10:39:39 +09:00
15 changed files with 121 additions and 187 deletions

View file

@ -647,7 +647,7 @@ inline ThrowCompletionOr<ECMAScriptFunctionObject*> new_class(VM& vm, Value supe
// NOTE: NewClass expects classEnv to be active lexical environment
auto* class_environment = vm.lexical_environment();
vm.running_execution_context().lexical_environment = interpreter.saved_lexical_environment_stack().take_last();
vm.running_execution_context().lexical_environment = vm.running_execution_context().saved_lexical_environments.take_last();
Optional<DeprecatedFlyString> binding_name;
DeprecatedFlyString class_name;

View file

@ -106,18 +106,6 @@ static ByteString format_value_list(StringView name, ReadonlySpan<Value> values)
return builder.to_byte_string();
}
NonnullOwnPtr<CallFrame> CallFrame::create(size_t register_count)
{
size_t allocation_size = sizeof(CallFrame) + sizeof(Value) * register_count;
auto* memory = malloc(allocation_size);
VERIFY(memory);
auto call_frame = adopt_own(*new (memory) CallFrame);
call_frame->register_count = register_count;
for (auto i = 0u; i < register_count; ++i)
new (&call_frame->register_values[i]) Value();
return call_frame;
}
ALWAYS_INLINE static ThrowCompletionOr<Value> loosely_inequals(VM& vm, Value src1, Value src2)
{
if (src1.tag() == src2.tag()) {
@ -163,13 +151,6 @@ Interpreter::~Interpreter()
{
}
void Interpreter::visit_edges(Cell::Visitor& visitor)
{
for (auto& frame : m_call_frames) {
frame.visit([&](auto& value) { value->visit_edges(visitor); });
}
}
ALWAYS_INLINE Value Interpreter::get(Operand op) const
{
switch (op.type()) {
@ -263,11 +244,11 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Envir
executable->dump();
// a. Set result to the result of evaluating script.
auto result_or_error = run_and_return_frame(*executable, nullptr);
auto result_or_error = run_executable(*executable, nullptr);
if (result_or_error.value.is_error())
result = result_or_error.value.release_error();
else
result = result_or_error.frame->registers()[0];
result = result_or_error.return_register_value;
}
}
@ -384,7 +365,8 @@ void Interpreter::run_bytecode()
do_return(saved_return_value());
break;
}
auto const* old_scheduled_jump = call_frame().previously_scheduled_jumps.take_last();
auto& running_execution_context = vm().running_execution_context();
auto const* old_scheduled_jump = running_execution_context.previously_scheduled_jumps.take_last();
if (m_scheduled_jump) {
// FIXME: If we `break` or `continue` in the finally, we need to clear
// this field
@ -418,7 +400,8 @@ void Interpreter::run_bytecode()
if (!handler && !finalizer)
return;
auto& unwind_context = unwind_contexts().last();
auto& running_execution_context = vm().running_execution_context();
auto& unwind_context = running_execution_context.unwind_contexts.last();
VERIFY(unwind_context.executable == m_current_executable);
if (handler) {
@ -453,7 +436,8 @@ void Interpreter::run_bytecode()
}
if (auto const* finalizer = m_current_block->finalizer(); finalizer && !will_yield) {
auto& unwind_context = unwind_contexts().last();
auto& running_execution_context = vm().running_execution_context();
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()) = {};
@ -470,7 +454,7 @@ void Interpreter::run_bytecode()
}
}
Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executable, BasicBlock const* entry_point, CallFrame* in_frame)
Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& executable, BasicBlock const* entry_point)
{
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable);
@ -484,12 +468,13 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa
TemporaryChange restore_current_block { m_current_block, entry_point ?: executable.basic_blocks.first() };
if (in_frame)
push_call_frame(in_frame);
else
push_call_frame(CallFrame::create(executable.number_of_registers));
auto& running_execution_context = vm().running_execution_context();
if (running_execution_context.registers.size() < executable.number_of_registers)
running_execution_context.registers.resize(executable.number_of_registers);
vm().execution_context_stack().last()->executable = &executable;
reg(Register::return_value()) = {};
running_execution_context.executable = &executable;
run_bytecode();
@ -513,48 +498,39 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa
return_value = reg(Register::saved_return_value());
auto exception = reg(Register::exception());
auto frame = pop_call_frame();
// NOTE: The return value from a called function is put into $0 in the caller context.
if (!m_call_frames.is_empty())
call_frame().registers()[0] = return_value;
// At this point we may have already run any queued promise jobs via on_call_stack_emptied,
// in which case this is a no-op.
vm().run_queued_promise_jobs();
vm().finish_execution_generation();
if (!exception.is_empty()) {
if (auto* call_frame = frame.get_pointer<NonnullOwnPtr<CallFrame>>())
return { throw_completion(exception), move(*call_frame) };
return { throw_completion(exception), nullptr };
}
if (auto* call_frame = frame.get_pointer<NonnullOwnPtr<CallFrame>>())
return { return_value, move(*call_frame) };
return { return_value, nullptr };
if (!exception.is_empty())
return { throw_completion(exception), vm().running_execution_context().registers[0] };
return { return_value, vm().running_execution_context().registers[0] };
}
void Interpreter::enter_unwind_context()
{
unwind_contexts().empend(
auto& running_execution_context = vm().running_execution_context();
running_execution_context.unwind_contexts.empend(
m_current_executable,
vm().running_execution_context().lexical_environment);
call_frame().previously_scheduled_jumps.append(m_scheduled_jump);
running_execution_context.previously_scheduled_jumps.append(m_scheduled_jump);
m_scheduled_jump = nullptr;
}
void Interpreter::leave_unwind_context()
{
unwind_contexts().take_last();
auto& running_execution_context = vm().running_execution_context();
running_execution_context.unwind_contexts.take_last();
}
void Interpreter::catch_exception(Operand dst)
{
set(dst, reg(Register::exception()));
reg(Register::exception()) = {};
auto& context = unwind_contexts().last();
auto& running_execution_context = vm().running_execution_context();
auto& context = running_execution_context.unwind_contexts.last();
VERIFY(!context.handler_called);
VERIFY(context.executable == &current_executable());
context.handler_called = true;
@ -563,9 +539,10 @@ void Interpreter::catch_exception(Operand dst)
void Interpreter::enter_object_environment(Object& object)
{
auto& old_environment = vm().running_execution_context().lexical_environment;
saved_lexical_environment_stack().append(old_environment);
vm().running_execution_context().lexical_environment = new_object_environment(object, true, old_environment);
auto& running_execution_context = vm().running_execution_context();
auto& old_environment = running_execution_context.lexical_environment;
running_execution_context.saved_lexical_environments.append(old_environment);
running_execution_context.lexical_environment = new_object_environment(object, true, old_environment);
}
ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ASTNode const& node, ReadonlySpan<FunctionParameter> parameters, FunctionKind kind, DeprecatedFlyString const& name)
@ -583,20 +560,6 @@ ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ASTNode co
return bytecode_executable;
}
void Interpreter::push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*> frame)
{
m_call_frames.append(move(frame));
m_current_call_frame = this->call_frame().registers();
reg(Register::return_value()) = {};
}
Variant<NonnullOwnPtr<CallFrame>, CallFrame*> Interpreter::pop_call_frame()
{
auto frame = m_call_frames.take_last();
m_current_call_frame = m_call_frames.is_empty() ? Span<Value> {} : this->call_frame().registers();
return frame;
}
}
namespace JS::Bytecode {
@ -1039,7 +1002,8 @@ ThrowCompletionOr<void> CreateLexicalEnvironment::execute_impl(Bytecode::Interpr
swap(old_environment, environment);
return environment;
};
interpreter.saved_lexical_environment_stack().append(make_and_swap_envs(interpreter.vm().running_execution_context().lexical_environment));
auto& running_execution_context = interpreter.vm().running_execution_context();
running_execution_context.saved_lexical_environments.append(make_and_swap_envs(running_execution_context.lexical_environment));
return {};
}
@ -1453,7 +1417,8 @@ ThrowCompletionOr<void> ScheduleJump::execute_impl(Bytecode::Interpreter&) const
ThrowCompletionOr<void> LeaveLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.vm().running_execution_context().lexical_environment = interpreter.saved_lexical_environment_stack().take_last();
auto& running_execution_context = interpreter.vm().running_execution_context();
running_execution_context.lexical_environment = running_execution_context.saved_lexical_environments.take_last();
return {};
}
@ -1643,9 +1608,10 @@ ThrowCompletionOr<void> BlockDeclarationInstantiation::execute_impl(Bytecode::In
{
auto& vm = interpreter.vm();
auto old_environment = vm.running_execution_context().lexical_environment;
interpreter.saved_lexical_environment_stack().append(old_environment);
vm.running_execution_context().lexical_environment = new_declarative_environment(*old_environment);
m_scope_node.block_declaration_instantiation(vm, vm.running_execution_context().lexical_environment);
auto& running_execution_context = vm.running_execution_context();
running_execution_context.saved_lexical_environments.append(old_environment);
running_execution_context.lexical_environment = new_declarative_environment(*old_environment);
m_scope_node.block_declaration_instantiation(vm, running_execution_context.lexical_environment);
return {};
}

View file

@ -19,31 +19,6 @@ namespace JS::Bytecode {
class InstructionStreamIterator;
struct CallFrame {
static NonnullOwnPtr<CallFrame> create(size_t register_count);
void operator delete(void* ptr) { free(ptr); }
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(registers());
visitor.visit(saved_lexical_environments);
for (auto& context : unwind_contexts) {
visitor.visit(context.lexical_environment);
}
}
Vector<GCPtr<Environment>> saved_lexical_environments;
Vector<UnwindInfo> unwind_contexts;
Vector<BasicBlock const*> previously_scheduled_jumps;
Span<Value> registers() { return { register_values, register_count }; }
ReadonlySpan<Value> registers() const { return { register_values, register_count }; }
size_t register_count { 0 };
Value register_values[];
};
class Interpreter {
public:
explicit Interpreter(VM&);
@ -60,27 +35,30 @@ public:
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Bytecode::BasicBlock const* entry_point = nullptr)
{
auto value_and_frame = run_and_return_frame(executable, entry_point);
return move(value_and_frame.value);
auto result_and_return_register = run_executable(executable, entry_point);
return move(result_and_return_register.value);
}
struct ValueAndFrame {
struct ResultAndReturnRegister {
ThrowCompletionOr<Value> value;
OwnPtr<CallFrame> frame;
Value return_register_value;
};
ValueAndFrame run_and_return_frame(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point, CallFrame* = nullptr);
ResultAndReturnRegister run_executable(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point);
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
ALWAYS_INLINE Value& saved_return_value() { return reg(Register::saved_return_value()); }
Value& reg(Register const& r) { return registers()[r.index()]; }
Value reg(Register const& r) const { return registers()[r.index()]; }
Value& reg(Register const& r)
{
return vm().running_execution_context().registers[r.index()];
}
Value reg(Register const& r) const
{
return vm().running_execution_context().registers[r.index()];
}
[[nodiscard]] Value get(Operand) const;
void set(Operand, Value);
auto& saved_lexical_environment_stack() { return call_frame().saved_lexical_environments; }
auto& unwind_contexts() { return call_frame().unwind_contexts; }
void do_return(Value value)
{
reg(Register::return_value()) = value;
@ -98,30 +76,13 @@ public:
BasicBlock const& current_block() const { return *m_current_block; }
Optional<InstructionStreamIterator const&> instruction_stream_iterator() const { return m_pc; }
void visit_edges(Cell::Visitor&);
Span<Value> registers() { return m_current_call_frame; }
ReadonlySpan<Value> registers() const { return m_current_call_frame; }
Vector<Value>& registers() { return vm().running_execution_context().registers; }
Vector<Value> const& registers() const { return vm().running_execution_context().registers; }
private:
void run_bytecode();
CallFrame& call_frame()
{
return m_call_frames.last().visit([](auto& x) -> CallFrame& { return *x; });
}
CallFrame const& call_frame() const
{
return const_cast<Interpreter*>(this)->call_frame();
}
void push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*>);
[[nodiscard]] Variant<NonnullOwnPtr<CallFrame>, CallFrame*> pop_call_frame();
VM& m_vm;
Vector<Variant<NonnullOwnPtr<CallFrame>, CallFrame*>> m_call_frames;
Span<Value> m_current_call_frame;
BasicBlock const* m_scheduled_jump { nullptr };
GCPtr<Executable> m_current_executable { nullptr };
BasicBlock const* m_current_block { nullptr };

View file

@ -259,7 +259,6 @@ AK::JsonObject Heap::dump_graph()
HashMap<Cell*, HeapRoot> roots;
gather_roots(roots);
GraphConstructorVisitor visitor(*this, roots);
vm().bytecode_interpreter().visit_edges(visitor);
visitor.visit_all_cells();
return visitor.dump();
}
@ -457,8 +456,6 @@ void Heap::mark_live_cells(HashMap<Cell*, HeapRoot> const& roots)
MarkingVisitor visitor(*this, roots);
vm().bytecode_interpreter().visit_edges(visitor);
visitor.mark_all_live_cells();
for (auto& inverse_root : m_uprooted_cells)

View file

@ -693,11 +693,11 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
executable->name = "eval"sv;
if (Bytecode::g_dump_bytecode)
executable->dump();
auto result_or_error = vm.bytecode_interpreter().run_and_return_frame(*executable, nullptr);
auto result_or_error = vm.bytecode_interpreter().run_executable(*executable, nullptr);
if (result_or_error.value.is_error())
return result_or_error.value.release_error();
auto& result = result_or_error.frame->registers()[0];
auto& result = result_or_error.return_register_value;
if (!result.is_empty())
eval_result = result;

View file

@ -16,7 +16,7 @@ namespace JS {
JS_DEFINE_ALLOCATOR(AsyncGenerator);
ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context, NonnullOwnPtr<Bytecode::CallFrame> frame)
ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
{
auto& vm = realm.vm();
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
@ -24,7 +24,6 @@ ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& re
auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
auto object = realm.heap().allocate<AsyncGenerator>(realm, realm, generating_function_prototype_object, move(execution_context));
object->m_generating_function = generating_function;
object->m_frame = move(frame);
object->m_previous_value = initial_value;
return object;
}
@ -45,8 +44,6 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor)
}
visitor.visit(m_generating_function);
visitor.visit(m_previous_value);
if (m_frame)
m_frame->visit_edges(visitor);
visitor.visit(m_current_promise);
m_async_generator_context->visit_edges(visitor);
}
@ -192,19 +189,9 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
Bytecode::CallFrame* frame = nullptr;
if (m_frame)
frame = m_frame.ptr();
bytecode_interpreter.accumulator() = completion_object;
if (frame)
frame->registers()[0] = completion_object;
else
bytecode_interpreter.accumulator() = completion_object;
auto next_result = bytecode_interpreter.run_and_return_frame(*m_generating_function->bytecode_executable(), next_block, frame);
if (!m_frame)
m_frame = move(next_result.frame);
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block);
auto result_value = move(next_result.value);
if (!result_value.is_throw_completion()) {

View file

@ -28,7 +28,7 @@ public:
Completed,
};
static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>, NonnullOwnPtr<Bytecode::CallFrame>);
static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>);
virtual ~AsyncGenerator() override = default;
@ -60,7 +60,6 @@ private:
GCPtr<ECMAScriptFunctionObject> m_generating_function;
Value m_previous_value;
OwnPtr<Bytecode::CallFrame> m_frame;
GCPtr<Promise> m_current_promise;
};

View file

@ -713,11 +713,23 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
} else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) {
argument_value = execution_context_arguments[i];
} else if (parameter.default_value) {
auto value_and_frame = vm.bytecode_interpreter().run_and_return_frame(*m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr);
if (value_and_frame.value.is_error())
return value_and_frame.value.release_error();
// Resulting value is in the accumulator.
argument_value = value_and_frame.frame->registers()[0];
auto& running_execution_context = vm.running_execution_context();
// NOTE: Registers have to be saved and restored because executable created for default parameter uses
// running execution context.
// FIXME: This is a hack and instead instructions for default parameters should be a part of the function's bytecode.
auto saved_registers = running_execution_context.registers;
for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index)
saved_registers[register_index] = {};
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr);
for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index)
running_execution_context.registers[register_index] = saved_registers[register_index];
if (result_and_return_register.value.is_error())
return result_and_return_register.value.release_error();
argument_value = result_and_return_register.return_register_value;
} else {
argument_value = js_undefined();
}
@ -1103,7 +1115,7 @@ void async_block_start(VM& vm, T const& async_body, PromiseCapability const& pro
if (maybe_executable.is_error())
result = maybe_executable.release_error();
else
result = vm.bytecode_interpreter().run_and_return_frame(*maybe_executable.value(), nullptr).value;
result = vm.bytecode_interpreter().run_executable(*maybe_executable.value(), nullptr).value;
}
// b. Else,
else {
@ -1230,9 +1242,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
}
}
auto result_and_frame = vm.bytecode_interpreter().run_and_return_frame(*m_bytecode_executable, nullptr);
auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, nullptr);
VERIFY(result_and_frame.frame != nullptr);
if (result_and_frame.value.is_error())
return result_and_frame.value.release_error();
@ -1244,11 +1255,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
return { Completion::Type::Return, result.value_or(js_undefined()), {} };
if (m_kind == FunctionKind::AsyncGenerator) {
auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull()));
auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy()));
return { Completion::Type::Return, async_generator_object, {} };
}
auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull()));
auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy()));
// NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise
// See AwaitExpression::generate_bytecode() for the transformation.

View file

@ -43,6 +43,10 @@ NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
copy->executable = executable;
copy->arguments = arguments;
copy->locals = locals;
copy->registers = registers;
copy->unwind_contexts = unwind_contexts;
copy->saved_lexical_environments = saved_lexical_environments;
copy->previously_scheduled_jumps = previously_scheduled_jumps;
return copy;
}
@ -61,6 +65,11 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
visitor.visit(function_name);
visitor.visit(arguments);
visitor.visit(locals);
visitor.visit(registers);
for (auto& context : unwind_contexts) {
visitor.visit(context.lexical_environment);
}
visitor.visit(saved_lexical_environments);
script_or_module.visit(
[](Empty) {},
[&](auto& script_or_module) {

View file

@ -10,6 +10,7 @@
#include <AK/DeprecatedFlyString.h>
#include <AK/WeakPtr.h>
#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Forward.h>
#include <LibJS/Module.h>
@ -75,6 +76,10 @@ public:
Vector<Value> arguments;
Vector<Value> locals;
Vector<Value> registers;
Vector<Bytecode::UnwindInfo> unwind_contexts;
Vector<Bytecode::BasicBlock const*> previously_scheduled_jumps;
Vector<GCPtr<Environment>> saved_lexical_environments;
};
struct StackTraceElement {

View file

@ -16,7 +16,7 @@ namespace JS {
JS_DEFINE_ALLOCATOR(GeneratorObject);
ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context, NonnullOwnPtr<Bytecode::CallFrame> frame)
ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
{
auto& vm = realm.vm();
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
@ -32,7 +32,6 @@ ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm&
auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
auto object = realm.heap().allocate<GeneratorObject>(realm, realm, generating_function_prototype_object, move(execution_context));
object->m_generating_function = generating_function;
object->m_frame = move(frame);
object->m_previous_value = initial_value;
return object;
}
@ -49,8 +48,6 @@ void GeneratorObject::visit_edges(Cell::Visitor& visitor)
Base::visit_edges(visitor);
visitor.visit(m_generating_function);
visitor.visit(m_previous_value);
if (m_frame)
m_frame->visit_edges(visitor);
m_execution_context->visit_edges(visitor);
}
@ -113,22 +110,12 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
Bytecode::CallFrame* frame = nullptr;
if (m_frame)
frame = m_frame;
bytecode_interpreter.registers()[0] = completion_object;
if (frame)
frame->registers()[0] = completion_object;
else
bytecode_interpreter.accumulator() = completion_object;
auto next_result = bytecode_interpreter.run_and_return_frame(*m_generating_function->bytecode_executable(), next_block, frame);
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block);
vm.pop_execution_context();
if (!m_frame)
m_frame = move(next_result.frame);
auto result_value = move(next_result.value);
if (result_value.is_throw_completion()) {
// Uncaught exceptions disable the generator.

View file

@ -17,7 +17,7 @@ class GeneratorObject : public Object {
JS_DECLARE_ALLOCATOR(GeneratorObject);
public:
static ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>, NonnullOwnPtr<Bytecode::CallFrame>);
static ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>);
virtual ~GeneratorObject() override = default;
void visit_edges(Cell::Visitor&) override;
@ -43,7 +43,6 @@ private:
NonnullOwnPtr<ExecutionContext> m_execution_context;
GCPtr<ECMAScriptFunctionObject> m_generating_function;
Value m_previous_value;
OwnPtr<Bytecode::CallFrame> m_frame;
GeneratorState m_generator_state { GeneratorState::SuspendedStart };
Optional<StringView> m_generator_brand;
};

View file

@ -180,12 +180,12 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, StringView source_tex
else {
auto executable = maybe_executable.release_value();
auto value_and_frame = vm.bytecode_interpreter().run_and_return_frame(*executable, nullptr);
if (value_and_frame.value.is_error()) {
result = value_and_frame.value.release_error();
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, nullptr);
if (result_and_return_register.value.is_error()) {
result = result_and_return_register.value.release_error();
} else {
// Resulting value is in the accumulator.
result = value_and_frame.frame->registers()[0].value_or(js_undefined());
result = result_and_return_register.return_register_value.value_or(js_undefined());
}
}
}

View file

@ -289,11 +289,24 @@ ThrowCompletionOr<void> VM::binding_initialization(NonnullRefPtr<BindingPattern
ThrowCompletionOr<Value> VM::execute_ast_node(ASTNode const& node)
{
// FIXME: This function should be gone once we will emit bytecode for everything before executing instructions.
auto executable = TRY(Bytecode::compile(*this, node, {}, FunctionKind::Normal, ""sv));
auto result_or_error = bytecode_interpreter().run_and_return_frame(*executable, nullptr);
auto& running_execution_context = this->running_execution_context();
// Registers have to be saved and restored because executable for compiled ASTNode does not have its own execution context
auto saved_registers = running_execution_context.registers;
for (size_t i = 0; i < saved_registers.size(); ++i)
running_execution_context.registers[i] = {};
auto result_or_error = bytecode_interpreter().run_executable(*executable, nullptr);
for (size_t i = 0; i < saved_registers.size(); ++i)
running_execution_context.registers[i] = saved_registers[i];
if (result_or_error.value.is_error())
return result_or_error.value.release_error();
return result_or_error.frame->registers()[0];
return result_or_error.return_register_value;
}
// 13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation

View file

@ -717,12 +717,12 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GCPtr<PromiseCa
else {
auto executable = maybe_executable.release_value();
auto value_and_frame = vm.bytecode_interpreter().run_and_return_frame(*executable, nullptr);
if (value_and_frame.value.is_error()) {
result = value_and_frame.value.release_error();
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, nullptr);
if (result_and_return_register.value.is_error()) {
result = result_and_return_register.value.release_error();
} else {
// Resulting value is in the accumulator.
result = value_and_frame.frame->registers()[0].value_or(js_undefined());
result = result_and_return_register.return_register_value.value_or(js_undefined());
}
}