ladybird/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp
Andreas Kling 3c74dc9f4d LibJS: Segregate GC-allocated objects by type
This patch adds two macros to declare per-type allocators:

- JS_DECLARE_ALLOCATOR(TypeName)
- JS_DEFINE_ALLOCATOR(TypeName)

When used, they add a type-specific CellAllocator that the Heap will
delegate allocation requests to.

The result of this is that GC objects of the same type always end up
within the same HeapBlock, drastically reducing the ability to perform
type confusion attacks.

It also improves HeapBlock utilization, since each block now has cells
sized exactly to the type used within that block. (Previously we only
had a handful of block sizes available, and most GC allocations ended
up with a large amount of slack in their tails.)

There is a small performance hit from this, but I'm sure we can make
up for it elsewhere.

Note that the old size-based allocators still exist, and we fall back
to them for any type that doesn't have its own CellAllocator.
2023-11-19 12:10:31 +01:00

560 lines
24 KiB
C++

/*
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AsyncGenerator.h>
#include <LibJS/Runtime/AsyncGeneratorPrototype.h>
#include <LibJS/Runtime/AsyncGeneratorRequest.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PromiseConstructor.h>
namespace JS {
JS_DEFINE_ALLOCATOR(AsyncGenerator);
ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, ExecutionContext execution_context, Bytecode::CallFrame frame)
{
auto& vm = realm.vm();
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
auto generating_function_prototype = TRY(generating_function->get(vm.names.prototype));
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;
}
AsyncGenerator::AsyncGenerator(Realm&, Object& prototype, ExecutionContext context)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_async_generator_context(move(context))
{
}
void AsyncGenerator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto const& request : m_async_generator_queue) {
if (request.completion.value().has_value())
visitor.visit(*request.completion.value());
visitor.visit(request.capability);
}
m_async_generator_context.visit_edges(visitor);
visitor.visit(m_generating_function);
visitor.visit(m_previous_value);
if (m_frame.has_value())
m_frame->visit_edges(visitor);
visitor.visit(m_current_promise);
}
// 27.6.3.4 AsyncGeneratorEnqueue ( generator, completion, promiseCapability ), https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
void AsyncGenerator::async_generator_enqueue(Completion completion, NonnullGCPtr<PromiseCapability> promise_capability)
{
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
auto request = AsyncGeneratorRequest { .completion = move(completion), .capability = promise_capability };
// 2. Append request to generator.[[AsyncGeneratorQueue]].
m_async_generator_queue.append(move(request));
// 3. Return unused.
}
void AsyncGenerator::set_async_generator_state(Badge<AsyncGeneratorPrototype>, AsyncGenerator::State value)
{
m_async_generator_state = value;
}
// 27.7.5.3 Await ( value ), https://tc39.es/ecma262/#await
ThrowCompletionOr<void> AsyncGenerator::await(Value value)
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
// 1. Let asyncContext be the running execution context.
auto& async_context = vm.running_execution_context();
// 2. Let promise be ? PromiseResolve(%Promise%, value).
auto* promise_object = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), value));
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (v) that captures asyncContext and performs the
// following steps when called:
auto fulfilled_closure = [this, &async_context](VM& vm) -> ThrowCompletionOr<Value> {
auto value = vm.argument(0);
// a. Let prevContext be the running execution context.
auto& prev_context = vm.running_execution_context();
// FIXME: b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
TRY(vm.push_execution_context(async_context, {}));
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(v) as the result of the operation that
// suspended it.
execute(vm, normal_completion(value));
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and
// prevContext is the currently running execution context.
VERIFY(&vm.running_execution_context() == &prev_context);
// f. Return undefined.
return js_undefined();
};
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the
// following steps when called:
auto rejected_closure = [this, &async_context](VM& vm) -> ThrowCompletionOr<Value> {
auto reason = vm.argument(0);
// a. Let prevContext be the running execution context.
auto& prev_context = vm.running_execution_context();
// FIXME: b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
TRY(vm.push_execution_context(async_context, {}));
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that
// suspended it.
execute(vm, throw_completion(reason));
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and
// prevContext is the currently running execution context.
VERIFY(&vm.running_execution_context() == &prev_context);
// f. Return undefined.
return js_undefined();
};
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
m_current_promise = verify_cast<Promise>(promise_object);
m_current_promise->perform_then(on_fulfilled, on_rejected, {});
// 8. Remove asyncContext 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();
// NOTE: None of these are necessary. 10-12 are handled by step d of the above lambdas.
// 9. Let callerContext be the running execution context.
// 10. Resume callerContext passing empty. If asyncContext is ever resumed again, let completion be the Completion Record with which it is resumed.
// 11. Assert: If control reaches here, then asyncContext is the running execution context again.
// 12. Return completion.
return {};
}
void AsyncGenerator::execute(VM& vm, Completion completion)
{
while (true) {
// Loosely based on step 4 of https://tc39.es/ecma262/#sec-asyncgeneratorstart
VERIFY(completion.value().has_value());
auto generated_value = [](Value value) -> Value {
if (value.is_object())
return value.as_object().get_without_side_effects("result");
return value.is_empty() ? js_undefined() : value;
};
auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* {
if (value.is_object()) {
auto number_value = value.as_object().get_without_side_effects("continuation");
return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(number_value.as_double()));
}
return nullptr;
};
auto generated_is_await = [](Value value) -> bool {
if (value.is_object())
return value.as_object().get_without_side_effects("isAwait").as_bool();
return false;
};
auto& realm = *vm.current_realm();
auto completion_object = Object::create(realm, nullptr);
completion_object->define_direct_property(vm.names.type, Value(to_underlying(completion.type())), default_attributes);
completion_object->define_direct_property(vm.names.value, completion.value().value(), default_attributes);
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);
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.has_value())
frame = &m_frame.value();
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.has_value())
m_frame = move(*next_result.frame);
auto result_value = move(next_result.value);
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);
if (is_await) {
auto await_result = this->await(value);
if (await_result.is_throw_completion()) {
completion = await_result.release_error();
continue;
}
return;
}
}
bool done = result_value.is_throw_completion() || generated_continuation(m_previous_value) == nullptr;
if (!done) {
// 27.6.3.8 AsyncGeneratorYield ( value ), https://tc39.es/ecma262/#sec-asyncgeneratoryield
// 1. Let genContext be the running execution context.
// 2. Assert: genContext is the execution context of a generator.
// 3. Let generator be the value of the Generator component of genContext.
// 4. Assert: GetGeneratorKind() is async.
// 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.
VERIFY(vm.execution_context_stack().size() >= 2);
// 7. Let previousContext be the second to top element of the execution context stack.
auto& previous_context = vm.execution_context_stack().at(vm.execution_context_stack().size() - 2);
// 8. Let previousRealm be previousContext's Realm.
auto previous_realm = previous_context->realm;
// 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm).
complete_step(yield_completion, false, previous_realm.ptr());
// 10. Let queue be generator.[[AsyncGeneratorQueue]].
auto& queue = m_async_generator_queue;
// 11. If queue is not empty, then
if (!queue.is_empty()) {
// a. NOTE: Execution continues without suspending the generator.
// b. Let toYield be the first element of queue.
auto& to_yield = queue.first();
// c. Let resumptionValue be Completion(toYield.[[Completion]]).
completion = Completion(to_yield.completion);
// d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
// NOTE: AsyncGeneratorUnwrapYieldResumption is performed inside the continuation block inside the generator,
// so we just need to enter the generator again.
continue;
}
// 12. Else,
else {
// a. Set generator.[[AsyncGeneratorState]] to suspendedYield.
m_async_generator_state = State::SuspendedYield;
// b. Remove genContext 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();
// c. Let callerContext be the running execution context.
// d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed.
// e. Assert: If control reaches here, then genContext is the running execution context again.
// f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
// NOTE: e-f are performed whenever someone calls `execute` again.
return;
}
}
// 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();
// 4.g. Set acGenerator.[[AsyncGeneratorState]] to completed.
m_async_generator_state = State::Completed;
// 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);
// 4.k. Perform AsyncGeneratorDrainQueue(acGenerator).
drain_queue();
// 4.l. Return undefined.
return;
}
}
// 27.6.3.6 AsyncGeneratorResume ( generator, completion ), https://tc39.es/ecma262/#sec-asyncgeneratorresume
ThrowCompletionOr<void> AsyncGenerator::resume(VM& vm, Completion completion)
{
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
VERIFY(m_async_generator_state == State::SuspendedStart || m_async_generator_state == State::SuspendedYield);
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
auto& generator_context = m_async_generator_context;
// 3. Let callerContext be the running execution context.
auto const& caller_context = vm.running_execution_context();
// FIXME: 4. Suspend callerContext.
// 5. Set generator.[[AsyncGeneratorState]] to executing.
m_async_generator_state = State::Executing;
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
TRY(vm.push_execution_context(generator_context, {}));
// 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended
// it. Let result be the Completion Record returned by the resumed computation.
// 8. Assert: result is never an abrupt completion.
execute(vm, completion);
// 9. Assert: When we return here, genContext has already been removed from the execution context stack and
// callerContext is the currently running execution context.
VERIFY(&vm.running_execution_context() == &caller_context);
// 10. Return unused.
return {};
}
// 27.6.3.9 AsyncGeneratorAwaitReturn ( generator ), https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
// With unmerged broken promise fixup from https://github.com/tc39/ecma262/pull/2683
void AsyncGenerator::await_return()
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
auto& queue = m_async_generator_queue;
// 2. Assert: queue is not empty.
VERIFY(!queue.is_empty());
// 3. Let next be the first element of queue.
auto& next = m_async_generator_queue.first();
// 4. Let completion be Completion(next.[[Completion]]).
auto completion = Completion(next.completion);
// 5. Assert: completion.[[Type]] is return.
VERIFY(completion.type() == Completion::Type::Return);
// 6. Let promiseCompletion be Completion(PromiseResolve(%Promise%, _completion_.[[Value]])).
auto promise_completion = promise_resolve(vm, realm.intrinsics().promise_constructor(), completion.value().value());
// 7. If promiseCompletion is an abrupt completion, then
if (promise_completion.is_throw_completion()) {
// a. Set generator.[[AsyncGeneratorState]] to completed.
m_async_generator_state = State::Completed;
// b. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true).
complete_step(promise_completion.release_error(), true);
// c. Perform AsyncGeneratorDrainQueue(generator).
drain_queue();
// d. Return unused.
return;
}
// 8. Assert: promiseCompletion.[[Type]] is normal.
VERIFY(!promise_completion.is_throw_completion());
// 9. Let promise be promiseCompletion.[[Value]].
auto* promise = promise_completion.release_value();
// 10. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs
// the following steps when called:
auto fulfilled_closure = [this](VM& vm) -> ThrowCompletionOr<Value> {
// a. Set generator.[[AsyncGeneratorState]] to completed.
m_async_generator_state = State::Completed;
// b. Let result be NormalCompletion(value).
auto result = normal_completion(vm.argument(0));
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
complete_step(result, true);
// d. Perform AsyncGeneratorDrainQueue(generator).
drain_queue();
// e. Return undefined.
return js_undefined();
};
// 11. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
// 12. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs
// the following steps when called:
auto rejected_closure = [this](VM& vm) -> ThrowCompletionOr<Value> {
// a. Set generator.[[AsyncGeneratorState]] to completed.
m_async_generator_state = State::Completed;
// b. Let result be ThrowCompletion(reason).
auto result = throw_completion(vm.argument(0));
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
complete_step(result, true);
// d. Perform AsyncGeneratorDrainQueue(generator).
drain_queue();
// e. Return undefined.
return js_undefined();
};
// 13. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
// 14. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
// NOTE: await_return should only be called when the generator is in SuspendedStart or Completed state,
// so an await shouldn't be running currently, so it should be safe to overwrite m_current_promise.
m_current_promise = verify_cast<Promise>(promise);
m_current_promise->perform_then(on_fulfilled, on_rejected, {});
// 15. Return unused.
return;
}
// 27.6.3.5 AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] ), https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
void AsyncGenerator::complete_step(Completion completion, bool done, Realm* realm)
{
auto& vm = this->vm();
// 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.
VERIFY(!m_async_generator_queue.is_empty());
// 2. Let next be the first element of generator.[[AsyncGeneratorQueue]].
// 3. Remove the first element from generator.[[AsyncGeneratorQueue]].
auto next = m_async_generator_queue.take_first();
// 4. Let promiseCapability be next.[[Capability]].
auto promise_capability = next.capability;
// 5. Let value be completion.[[Value]].
auto value = completion.value().value();
// 6. If completion.[[Type]] is throw, then
if (completion.type() == Completion::Type::Throw) {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
MUST(call(vm, *promise_capability->reject(), js_undefined(), value));
}
// 7. Else,
else {
// a. Assert: completion.[[Type]] is normal.
VERIFY(completion.type() == Completion::Type::Normal);
GCPtr<Object> iterator_result;
// b. If realm is present, then
if (realm) {
// i. Let oldRealm be the running execution context's Realm.
auto old_realm = vm.running_execution_context().realm;
// ii. Set the running execution context's Realm to realm.
vm.running_execution_context().realm = realm;
// iii. Let iteratorResult be CreateIterResultObject(value, done).
iterator_result = create_iterator_result_object(vm, value, done);
// iv. Set the running execution context's Realm to oldRealm.
vm.running_execution_context().realm = old_realm;
}
// c. Else,
else {
// i. Let iteratorResult be CreateIterResultObject(value, done).
iterator_result = create_iterator_result_object(vm, value, done);
}
VERIFY(iterator_result);
// d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
MUST(call(vm, *promise_capability->resolve(), js_undefined(), iterator_result));
}
// 8. Return unused.
}
// 27.6.3.10 AsyncGeneratorDrainQueue ( generator ), https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
void AsyncGenerator::drain_queue()
{
// 1. Assert: generator.[[AsyncGeneratorState]] is completed.
VERIFY(m_async_generator_state == State::Completed);
// 2. Let queue be generator.[[AsyncGeneratorQueue]].
auto& queue = m_async_generator_queue;
// 3. If queue is empty, return unused.
if (queue.is_empty())
return;
// 4. Let done be false.
bool done = false;
// 5. Repeat, while done is false,
while (!done) {
// a. Let next be the first element of queue.
auto& next = m_async_generator_queue.first();
// b. Let completion be Completion(next.[[Completion]]).
auto completion = Completion(next.completion);
// c. If completion.[[Type]] is return, then
if (completion.type() == Completion::Type::Return) {
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
m_async_generator_state = State::AwaitingReturn;
// ii. Perform AsyncGeneratorAwaitReturn(generator).
await_return();
// iii. Set done to true.
done = true;
}
// d. Else,
else {
// i. If completion.[[Type]] is normal, then
if (completion.type() == Completion::Type::Normal) {
// 1. Set completion to NormalCompletion(undefined).
completion = normal_completion(js_undefined());
}
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
complete_step(completion, true);
// iii. If queue is empty, set done to true.
if (queue.is_empty())
done = true;
}
}
// 6. Return unused.
}
}