LibJS: Eliminate GeneratorResult GC cell allocation on yield/await
Store yield_continuation and yield_is_await directly in ExecutionContext instead of allocating a GeneratorResult GC cell. This removes a heap allocation per yield/await and fixes a latent bug where continuation addresses stored as doubles could lose precision.
This commit is contained in:
parent
9a34fb59aa
commit
1179e40d3f
14 changed files with 90 additions and 176 deletions
|
|
@ -30,7 +30,6 @@
|
|||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/FunctionEnvironment.h>
|
||||
#include <LibJS/Runtime/GeneratorResult.h>
|
||||
#include <LibJS/Runtime/GlobalEnvironment.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
|
|
@ -93,10 +92,13 @@ Interpreter::~Interpreter() = default;
|
|||
|
||||
ALWAYS_INLINE Value Interpreter::do_yield(Value value, Optional<Label> continuation)
|
||||
{
|
||||
// FIXME: If we get a pointer, which is not accurately representable as a double
|
||||
// will cause this to explode
|
||||
auto continuation_value = continuation.has_value() ? Value(continuation->address()) : js_null();
|
||||
return vm().heap().allocate<GeneratorResult>(value, continuation_value, false).ptr();
|
||||
auto& context = running_execution_context();
|
||||
if (continuation.has_value())
|
||||
context.yield_continuation = continuation->address();
|
||||
else
|
||||
context.yield_continuation = ExecutionContext::no_yield_continuation;
|
||||
context.yield_is_await = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
// 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
|
||||
|
|
@ -161,7 +163,7 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, GC::Ptr<Environ
|
|||
// 13. If result.[[Type]] is normal, then
|
||||
if (executable && result.type() == Completion::Type::Normal) {
|
||||
// a. Set result to Completion(Evaluation of script).
|
||||
result = run_executable(*script_context, *executable, {}, {});
|
||||
result = run_executable(*script_context, *executable, 0, {});
|
||||
|
||||
// b. If result is a normal completion and result.[[Value]] is empty, then
|
||||
if (result.type() == Completion::Type::Normal && result.value().is_special_empty_value()) {
|
||||
|
|
@ -821,7 +823,7 @@ DeclarativeEnvironment& Interpreter::global_declarative_environment()
|
|||
return realm().global_declarative_environment();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<Value> Interpreter::run_executable(ExecutionContext& context, Executable& executable, Optional<size_t> entry_point)
|
||||
ThrowCompletionOr<Value> Interpreter::run_executable(ExecutionContext& context, Executable& executable, u32 entry_point)
|
||||
{
|
||||
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {}", &executable);
|
||||
|
||||
|
|
@ -847,7 +849,7 @@ ThrowCompletionOr<Value> Interpreter::run_executable(ExecutionContext& context,
|
|||
executable.constants.data(),
|
||||
count * sizeof(Value));
|
||||
|
||||
run_bytecode(entry_point.value_or(0));
|
||||
run_bytecode(entry_point);
|
||||
|
||||
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {}", context.executable);
|
||||
|
||||
|
|
@ -3089,11 +3091,10 @@ void Yield::execute_impl(Bytecode::Interpreter& interpreter) const
|
|||
void Await::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto yielded_value = interpreter.get(m_argument).is_special_empty_value() ? js_undefined() : interpreter.get(m_argument);
|
||||
// FIXME: If we get a pointer, which is not accurately representable as a double
|
||||
// will cause this to explode
|
||||
auto continuation_value = Value(m_continuation_label.address());
|
||||
auto result = interpreter.vm().heap().allocate<GeneratorResult>(yielded_value, continuation_value, true);
|
||||
interpreter.do_return(result);
|
||||
auto& context = interpreter.running_execution_context();
|
||||
context.yield_continuation = m_continuation_label.address();
|
||||
context.yield_is_await = true;
|
||||
interpreter.do_return(yielded_value);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> GetByValue::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ public:
|
|||
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
|
||||
ThrowCompletionOr<Value> run(SourceTextModule&);
|
||||
|
||||
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Executable&, Optional<size_t> entry_point);
|
||||
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Executable&, u32 entry_point = 0);
|
||||
|
||||
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Executable& executable, Optional<size_t> entry_point, Value initial_accumulator_value)
|
||||
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Executable& executable, u32 entry_point, Value initial_accumulator_value)
|
||||
{
|
||||
context.registers_and_constants_and_locals_and_arguments_span()[0] = initial_accumulator_value;
|
||||
return run_executable(context, executable, entry_point);
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ set(SOURCES
|
|||
Runtime/GeneratorFunctionPrototype.cpp
|
||||
Runtime/GeneratorObject.cpp
|
||||
Runtime/GeneratorPrototype.cpp
|
||||
Runtime/GeneratorResult.cpp
|
||||
Runtime/GlobalEnvironment.cpp
|
||||
Runtime/GlobalObject.cpp
|
||||
Runtime/IndexedProperties.cpp
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ class JS_API Cell : public GC::Cell {
|
|||
public:
|
||||
MUST_UPCALL virtual void initialize(Realm&);
|
||||
|
||||
virtual bool is_generator_result() const { return false; }
|
||||
virtual bool is_environment() const { return false; }
|
||||
|
||||
ALWAYS_INLINE VM& vm() const;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include <LibJS/Runtime/AsyncGeneratorRequest.h>
|
||||
#include <LibJS/Runtime/CompletionCell.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/GeneratorResult.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
|
||||
#include <LibJS/Runtime/PromiseConstructor.h>
|
||||
|
|
@ -19,7 +18,7 @@ namespace JS {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(AsyncGenerator);
|
||||
|
||||
GC::Ref<AsyncGenerator> AsyncGenerator::create(Realm& realm, Value initial_value, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>> generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
|
||||
GC::Ref<AsyncGenerator> AsyncGenerator::create(Realm& realm, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>> 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)
|
||||
|
|
@ -39,14 +38,14 @@ GC::Ref<AsyncGenerator> AsyncGenerator::create(Realm& realm, Value initial_value
|
|||
return function->bytecode_executable();
|
||||
});
|
||||
|
||||
return realm.create<AsyncGenerator>(realm, generating_function_prototype_object, move(execution_context), generating_executable, initial_value);
|
||||
return realm.create<AsyncGenerator>(realm, generating_function_prototype_object, move(execution_context), generating_executable);
|
||||
}
|
||||
|
||||
AsyncGenerator::AsyncGenerator(Realm& realm, Object* prototype, NonnullOwnPtr<ExecutionContext> context, GC::Ref<Bytecode::Executable> bytecode_executable, Value initial_value)
|
||||
AsyncGenerator::AsyncGenerator(Realm& realm, Object* prototype, NonnullOwnPtr<ExecutionContext> context, GC::Ref<Bytecode::Executable> bytecode_executable)
|
||||
: Object(realm, prototype)
|
||||
, m_async_generator_context(move(context))
|
||||
, m_generating_executable(bytecode_executable)
|
||||
, m_previous_value(initial_value)
|
||||
, m_yield_continuation(m_async_generator_context->yield_continuation)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +59,6 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(request.capability);
|
||||
}
|
||||
visitor.visit(m_generating_executable);
|
||||
visitor.visit(m_previous_value);
|
||||
visitor.visit(m_current_promise);
|
||||
m_async_generator_context->visit_edges(visitor);
|
||||
}
|
||||
|
|
@ -167,56 +165,49 @@ ThrowCompletionOr<void> AsyncGenerator::await(Value value)
|
|||
void AsyncGenerator::execute(VM& vm, Completion completion)
|
||||
{
|
||||
while (true) {
|
||||
// Loosely based on step 4 of https://tc39.es/ecma262/#sec-asyncgeneratorstart
|
||||
auto generated_value = [](Value value) -> Value {
|
||||
if (value.is_cell() && value.as_cell().is_generator_result())
|
||||
return static_cast<GeneratorResult const&>(value.as_cell()).result();
|
||||
return value.is_special_empty_value() ? js_undefined() : value;
|
||||
};
|
||||
|
||||
auto generated_continuation = [&](Value value) -> Optional<size_t> {
|
||||
if (value.is_cell() && value.as_cell().is_generator_result()) {
|
||||
auto number_value = static_cast<GeneratorResult const&>(value.as_cell()).continuation();
|
||||
if (number_value.is_null())
|
||||
return {};
|
||||
return static_cast<size_t>(number_value.as_double());
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
auto generated_is_await = [](Value value) -> bool {
|
||||
if (value.is_cell() && value.as_cell().is_generator_result())
|
||||
return static_cast<GeneratorResult const&>(value.as_cell()).is_await();
|
||||
return false;
|
||||
};
|
||||
|
||||
auto completion_cell = heap().allocate<CompletionCell>(completion);
|
||||
|
||||
auto& bytecode_interpreter = vm.bytecode_interpreter();
|
||||
|
||||
auto const continuation_address = generated_continuation(m_previous_value);
|
||||
|
||||
// We should never enter `execute` again after the generator is complete.
|
||||
VERIFY(continuation_address.has_value());
|
||||
VERIFY(m_yield_continuation != ExecutionContext::no_yield_continuation);
|
||||
|
||||
auto result_value = bytecode_interpreter.run_executable(vm.running_execution_context(), m_generating_executable, continuation_address, completion_cell);
|
||||
// Clear yield state so that a normal return (no yield) is detected as done.
|
||||
m_async_generator_context->yield_continuation = ExecutionContext::no_yield_continuation;
|
||||
|
||||
if (!result_value.is_throw_completion()) {
|
||||
m_previous_value = result_value.release_value();
|
||||
auto value = generated_value(m_previous_value);
|
||||
bool is_await = generated_is_await(m_previous_value);
|
||||
auto result_value = bytecode_interpreter.run_executable(vm.running_execution_context(), m_generating_executable, m_yield_continuation, completion_cell);
|
||||
|
||||
if (is_await) {
|
||||
auto await_result = this->await(value);
|
||||
if (await_result.is_throw_completion()) {
|
||||
completion = await_result.release_error();
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (result_value.is_throw_completion()) {
|
||||
m_yield_continuation = ExecutionContext::no_yield_continuation;
|
||||
|
||||
// 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ), https://tc39.es/ecma262/#sec-asyncgeneratorstart
|
||||
// 4.e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return.
|
||||
// 4.f. Remove acGenContext from the execution context stack and restore the execution context
|
||||
// that is at the top of the execution context stack as the running execution context.
|
||||
vm.pop_execution_context();
|
||||
m_async_generator_state = State::Completed;
|
||||
complete_step(result_value.release_error(), true);
|
||||
drain_queue();
|
||||
return;
|
||||
}
|
||||
|
||||
bool done = result_value.is_throw_completion() || !generated_continuation(m_previous_value).has_value();
|
||||
auto value = result_value.release_value();
|
||||
if (value.is_special_empty_value())
|
||||
value = js_undefined();
|
||||
|
||||
m_yield_continuation = m_async_generator_context->yield_continuation;
|
||||
bool is_await = m_async_generator_context->yield_is_await;
|
||||
|
||||
if (is_await) {
|
||||
auto await_result = this->await(value);
|
||||
if (await_result.is_throw_completion()) {
|
||||
completion = await_result.release_error();
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool done = m_yield_continuation == ExecutionContext::no_yield_continuation;
|
||||
if (!done) {
|
||||
// 27.6.3.8 AsyncGeneratorYield ( value ), https://tc39.es/ecma262/#sec-asyncgeneratoryield
|
||||
// 1. Let genContext be the running execution context.
|
||||
|
|
@ -226,7 +217,6 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
|
|||
// NOTE: genContext is `m_async_generator_context`, generator is `this`.
|
||||
|
||||
// 5. Let completion be NormalCompletion(value).
|
||||
auto value = generated_value(m_previous_value);
|
||||
auto yield_completion = normal_completion(value);
|
||||
|
||||
// 6. Assert: The execution context stack has at least two elements.
|
||||
|
|
@ -286,15 +276,8 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
|
|||
|
||||
// 4.h. If result.[[Type]] is normal, set result to NormalCompletion(undefined).
|
||||
// 4.i. If result.[[Type]] is return, set result to NormalCompletion(result.[[Value]]).
|
||||
Completion result;
|
||||
if (!result_value.is_throw_completion()) {
|
||||
result = normal_completion(generated_value(m_previous_value));
|
||||
} else {
|
||||
result = result_value.release_error();
|
||||
}
|
||||
|
||||
// 4.j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true).
|
||||
complete_step(result, true);
|
||||
complete_step(normal_completion(value), true);
|
||||
|
||||
// 4.k. Perform AsyncGeneratorDrainQueue(acGenerator).
|
||||
drain_queue();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public:
|
|||
Completed,
|
||||
};
|
||||
|
||||
static GC::Ref<AsyncGenerator> create(Realm&, Value, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>>, NonnullOwnPtr<ExecutionContext>);
|
||||
static GC::Ref<AsyncGenerator> create(Realm&, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>>, NonnullOwnPtr<ExecutionContext>);
|
||||
|
||||
virtual ~AsyncGenerator() override;
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ public:
|
|||
Optional<String> const& generator_brand() const { return m_generator_brand; }
|
||||
|
||||
private:
|
||||
AsyncGenerator(Realm&, Object* prototype, NonnullOwnPtr<ExecutionContext>, GC::Ref<Bytecode::Executable>, Value);
|
||||
AsyncGenerator(Realm&, Object* prototype, NonnullOwnPtr<ExecutionContext>, GC::Ref<Bytecode::Executable>);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ private:
|
|||
Optional<String> m_generator_brand; // [[GeneratorBrand]]
|
||||
|
||||
GC::Ref<Bytecode::Executable> m_generating_executable;
|
||||
Value m_previous_value;
|
||||
u32 m_yield_continuation { ExecutionContext::no_yield_continuation };
|
||||
GC::Ptr<Promise> m_current_promise;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -523,9 +523,9 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::ordinary_call_evaluate_body(V
|
|||
return result;
|
||||
|
||||
if (kind() == FunctionKind::AsyncGenerator)
|
||||
return AsyncGenerator::create(*context.realm, result, GC::Ref { *this }, context.copy());
|
||||
return AsyncGenerator::create(*context.realm, GC::Ref { *this }, context.copy());
|
||||
|
||||
auto generator_object = GeneratorObject::create(*context.realm, result, GC::Ref { *this }, context.copy());
|
||||
auto generator_object = GeneratorObject::create(*context.realm, GC::Ref { *this }, context.copy());
|
||||
|
||||
// NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise.
|
||||
if (kind() == FunctionKind::Async)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,9 @@ NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
|
|||
copy->variable_environment = variable_environment;
|
||||
copy->private_environment = private_environment;
|
||||
copy->program_counter = program_counter;
|
||||
copy->yield_continuation = yield_continuation;
|
||||
copy->yield_is_await = yield_is_await;
|
||||
copy->caller_is_construct = caller_is_construct;
|
||||
copy->this_value = this_value;
|
||||
copy->executable = executable;
|
||||
copy->passed_argument_count = passed_argument_count;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,15 @@ public:
|
|||
// FIXME: Move this out of LibJS (e.g. by using the CustomData concept), as it's used exclusively by LibWeb.
|
||||
u32 skip_when_determining_incumbent_counter { 0 };
|
||||
|
||||
// Non-standard: Used by generators/async generators to communicate yield/await
|
||||
// state back to the caller without allocating a GC cell.
|
||||
// UINT32_MAX means "no continuation" (generator is done).
|
||||
static constexpr u32 no_yield_continuation = UINT32_MAX;
|
||||
u32 yield_continuation { no_yield_continuation };
|
||||
|
||||
bool yield_is_await { false };
|
||||
bool caller_is_construct { false };
|
||||
|
||||
Optional<Value> this_value;
|
||||
|
||||
GC::Ptr<Bytecode::Executable> executable;
|
||||
|
|
@ -110,7 +119,6 @@ public:
|
|||
u32 passed_argument_count { 0 };
|
||||
u32 caller_return_pc { 0 };
|
||||
u32 caller_dst_raw { 0 };
|
||||
bool caller_is_construct { false };
|
||||
|
||||
private:
|
||||
friend class Bytecode::Interpreter;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <LibJS/Runtime/CompletionCell.h>
|
||||
#include <LibJS/Runtime/GeneratorObject.h>
|
||||
#include <LibJS/Runtime/GeneratorPrototype.h>
|
||||
#include <LibJS/Runtime/GeneratorResult.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
|
||||
|
|
@ -18,7 +17,7 @@ namespace JS {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(GeneratorObject);
|
||||
|
||||
GC::Ref<GeneratorObject> GeneratorObject::create(Realm& realm, Value initial_value, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>> generating_function, NonnullOwnPtr<ExecutionContext> execution_context)
|
||||
GC::Ref<GeneratorObject> GeneratorObject::create(Realm& realm, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>> 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)
|
||||
|
|
@ -56,7 +55,7 @@ GC::Ref<GeneratorObject> GeneratorObject::create(Realm& realm, Value initial_val
|
|||
|
||||
auto object = realm.create<GeneratorObject>(realm, generating_function_prototype_object, move(execution_context));
|
||||
object->m_generating_executable = generating_executable;
|
||||
object->m_previous_value = initial_value;
|
||||
object->m_yield_continuation = object->m_execution_context->yield_continuation;
|
||||
return object;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +70,6 @@ void GeneratorObject::visit_edges(Cell::Visitor& visitor)
|
|||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_generating_executable);
|
||||
visitor.visit(m_previous_value);
|
||||
m_execution_context->visit_edges(visitor);
|
||||
}
|
||||
|
||||
|
|
@ -104,32 +102,17 @@ ThrowCompletionOr<GeneratorObject::IterationResult> GeneratorObject::execute(VM&
|
|||
{
|
||||
// Loosely based on step 4 of https://tc39.es/ecma262/#sec-generatorstart mixed with https://tc39.es/ecma262/#sec-generatoryield at the end.
|
||||
|
||||
auto generated_value = [](Value value) -> Value {
|
||||
if (value.is_cell() && value.as_cell().is_generator_result())
|
||||
return static_cast<GeneratorResult const&>(value.as_cell()).result();
|
||||
return value.is_special_empty_value() ? js_undefined() : value;
|
||||
};
|
||||
|
||||
auto generated_continuation = [&](Value value) -> Optional<size_t> {
|
||||
if (value.is_cell() && value.as_cell().is_generator_result()) {
|
||||
auto number_value = static_cast<GeneratorResult const&>(value.as_cell()).continuation();
|
||||
if (number_value.is_null())
|
||||
return {};
|
||||
return static_cast<u64>(number_value.as_double());
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
auto completion_cell = heap().allocate<CompletionCell>(completion);
|
||||
|
||||
auto& bytecode_interpreter = vm.bytecode_interpreter();
|
||||
|
||||
auto const next_block = generated_continuation(m_previous_value);
|
||||
|
||||
// We should never enter `execute` again after the generator is complete.
|
||||
VERIFY(next_block.has_value());
|
||||
VERIFY(m_yield_continuation != ExecutionContext::no_yield_continuation);
|
||||
|
||||
auto result_value = bytecode_interpreter.run_executable(vm.running_execution_context(), *m_generating_executable, next_block, completion_cell);
|
||||
// Clear yield state so that a normal return (no yield) is detected as done.
|
||||
m_execution_context->yield_continuation = ExecutionContext::no_yield_continuation;
|
||||
|
||||
auto result_value = bytecode_interpreter.run_executable(vm.running_execution_context(), *m_generating_executable, m_yield_continuation, completion_cell);
|
||||
|
||||
vm.pop_execution_context();
|
||||
|
||||
|
|
@ -138,12 +121,17 @@ ThrowCompletionOr<GeneratorObject::IterationResult> GeneratorObject::execute(VM&
|
|||
m_generator_state = GeneratorState::Completed;
|
||||
return result_value.throw_completion();
|
||||
}
|
||||
m_previous_value = result_value.release_value();
|
||||
bool done = !generated_continuation(m_previous_value).has_value();
|
||||
|
||||
auto value = result_value.release_value();
|
||||
if (value.is_special_empty_value())
|
||||
value = js_undefined();
|
||||
|
||||
m_yield_continuation = m_execution_context->yield_continuation;
|
||||
bool done = m_yield_continuation == ExecutionContext::no_yield_continuation;
|
||||
|
||||
m_generator_state = done ? GeneratorState::Completed : GeneratorState::SuspendedYield;
|
||||
|
||||
return IterationResult(generated_value(m_previous_value), done);
|
||||
return IterationResult(value, done);
|
||||
}
|
||||
|
||||
// 27.5.3.3 GeneratorResume ( generator, value, generatorBrand ), https://tc39.es/ecma262/#sec-generatorresume
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class GeneratorObject : public Object {
|
|||
GC_DECLARE_ALLOCATOR(GeneratorObject);
|
||||
|
||||
public:
|
||||
static GC::Ref<GeneratorObject> create(Realm&, Value, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>>, NonnullOwnPtr<ExecutionContext>);
|
||||
static GC::Ref<GeneratorObject> create(Realm&, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>>, NonnullOwnPtr<ExecutionContext>);
|
||||
virtual ~GeneratorObject() override = default;
|
||||
void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ protected:
|
|||
private:
|
||||
NonnullOwnPtr<ExecutionContext> m_execution_context;
|
||||
GC::Ptr<Bytecode::Executable> m_generating_executable;
|
||||
Value m_previous_value;
|
||||
u32 m_yield_continuation { ExecutionContext::no_yield_continuation };
|
||||
GeneratorState m_generator_state { GeneratorState::SuspendedStart };
|
||||
Optional<StringView> m_generator_brand;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/GeneratorResult.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(GeneratorResult);
|
||||
|
||||
GeneratorResult::~GeneratorResult() = default;
|
||||
|
||||
void GeneratorResult::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_result);
|
||||
visitor.visit(m_continuation);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class GeneratorResult final : public Cell {
|
||||
GC_CELL(GeneratorResult, Cell);
|
||||
GC_DECLARE_ALLOCATOR(GeneratorResult);
|
||||
|
||||
public:
|
||||
GeneratorResult(Value result, Value continuation, bool is_await)
|
||||
: m_is_await(is_await)
|
||||
, m_result(result)
|
||||
, m_continuation(continuation)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~GeneratorResult() override;
|
||||
|
||||
[[nodiscard]] Value result() const { return m_result; }
|
||||
[[nodiscard]] Value continuation() const { return m_continuation; }
|
||||
[[nodiscard]] bool is_await() const { return m_is_await; }
|
||||
|
||||
private:
|
||||
virtual bool is_generator_result() const override { return true; }
|
||||
virtual void visit_edges(Cell::Visitor& visitor) override;
|
||||
|
||||
bool m_is_await { false };
|
||||
Value m_result;
|
||||
Value m_continuation;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline bool Cell::fast_is<GeneratorResult>() const { return is_generator_result(); }
|
||||
|
||||
}
|
||||
|
|
@ -80,9 +80,9 @@ ThrowCompletionOr<Value> NativeJavaScriptBackedFunction::call()
|
|||
|
||||
auto& realm = *vm.current_realm();
|
||||
if (kind == FunctionKind::AsyncGenerator)
|
||||
return AsyncGenerator::create(realm, result, GC::Ref { *this }, vm.running_execution_context().copy());
|
||||
return AsyncGenerator::create(realm, GC::Ref { *this }, vm.running_execution_context().copy());
|
||||
|
||||
auto generator_object = GeneratorObject::create(realm, result, GC::Ref { *this }, vm.running_execution_context().copy());
|
||||
auto generator_object = GeneratorObject::create(realm, GC::Ref { *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.
|
||||
if (kind == FunctionKind::Async)
|
||||
|
|
|
|||
Loading…
Reference in a new issue