blob: 30d03b83bb5928cc241bd2e2d71decd02590d45d [file] [log] [blame]
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/builtins/builtins-async-gen.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/code-factory.h"
#include "src/code-stub-assembler.h"
#include "src/frames-inl.h"
namespace v8 {
namespace internal {
using compiler::Node;
namespace {
// Describe fields of Context associated with AsyncGeneratorAwait resume
// closures.
class AwaitContext {
public:
enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
};
class AsyncGeneratorBuiltinsAssembler : public AsyncBuiltinsAssembler {
public:
explicit AsyncGeneratorBuiltinsAssembler(CodeAssemblerState* state)
: AsyncBuiltinsAssembler(state) {}
inline Node* LoadGeneratorState(Node* const generator) {
return LoadObjectField(generator, JSGeneratorObject::kContinuationOffset);
}
inline Node* IsGeneratorStateClosed(Node* const state) {
return SmiEqual(state, SmiConstant(JSGeneratorObject::kGeneratorClosed));
}
inline Node* IsGeneratorClosed(Node* const generator) {
return IsGeneratorStateClosed(LoadGeneratorState(generator));
}
inline Node* IsGeneratorStateSuspended(Node* const state) {
return SmiGreaterThanOrEqual(state, SmiConstant(0));
}
inline Node* IsGeneratorSuspended(Node* const generator) {
return IsGeneratorStateSuspended(LoadGeneratorState(generator));
}
inline Node* IsGeneratorStateSuspendedAtStart(Node* const state) {
return SmiEqual(state, SmiConstant(0));
}
inline Node* IsGeneratorStateNotExecuting(Node* const state) {
return SmiNotEqual(state,
SmiConstant(JSGeneratorObject::kGeneratorExecuting));
}
inline Node* IsGeneratorNotExecuting(Node* const generator) {
return IsGeneratorStateNotExecuting(LoadGeneratorState(generator));
}
inline Node* LoadGeneratorAwaitedPromise(Node* const generator) {
return LoadObjectField(generator,
JSAsyncGeneratorObject::kAwaitedPromiseOffset);
}
inline Node* IsGeneratorNotSuspendedForAwait(Node* const generator) {
return IsUndefined(LoadGeneratorAwaitedPromise(generator));
}
inline Node* IsGeneratorSuspendedForAwait(Node* const generator) {
return HasInstanceType(LoadGeneratorAwaitedPromise(generator),
JS_PROMISE_TYPE);
}
inline void ClearAwaitedPromise(Node* const generator) {
StoreObjectFieldRoot(generator,
JSAsyncGeneratorObject::kAwaitedPromiseOffset,
Heap::kUndefinedValueRootIndex);
}
inline void CloseGenerator(Node* const generator) {
StoreObjectFieldNoWriteBarrier(
generator, JSGeneratorObject::kContinuationOffset,
SmiConstant(JSGeneratorObject::kGeneratorClosed));
}
inline Node* IsFastJSIterResult(Node* const value, Node* const context) {
CSA_ASSERT(this, TaggedIsNotSmi(value));
Node* const native_context = LoadNativeContext(context);
return WordEqual(
LoadMap(value),
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX));
}
inline Node* LoadFirstAsyncGeneratorRequestFromQueue(Node* const generator) {
return LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
}
inline Node* LoadResumeTypeFromAsyncGeneratorRequest(Node* const request) {
return LoadObjectField(request, AsyncGeneratorRequest::kResumeModeOffset);
}
inline Node* LoadPromiseFromAsyncGeneratorRequest(Node* const request) {
return LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
}
inline Node* LoadValueFromAsyncGeneratorRequest(Node* const request) {
return LoadObjectField(request, AsyncGeneratorRequest::kValueOffset);
}
inline Node* IsAbruptResumeType(Node* const resume_type) {
return SmiNotEqual(resume_type, SmiConstant(JSGeneratorObject::kNext));
}
void AsyncGeneratorEnqueue(Node* context, Node* generator, Node* value,
JSAsyncGeneratorObject::ResumeMode resume_mode,
const char* method_name);
Node* TakeFirstAsyncGeneratorRequestFromQueue(Node* generator);
Node* TakeFirstAsyncGeneratorRequestFromQueueIfPresent(Node* generator,
Label* if_not_present);
void AddAsyncGeneratorRequestToQueue(Node* generator, Node* request);
Node* AllocateAsyncGeneratorRequest(
JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
Node* promise);
// Shared implementation of the catchable and uncatchable variations of Await
// for AsyncGenerators.
template <typename Descriptor>
void AsyncGeneratorAwait(bool is_catchable);
void AsyncGeneratorAwaitResumeClosure(
Node* context, Node* value,
JSAsyncGeneratorObject::ResumeMode resume_mode);
};
// Shared implementation for the 3 Async Iterator protocol methods of Async
// Generators.
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorEnqueue(
Node* context, Node* generator, Node* value,
JSAsyncGeneratorObject::ResumeMode resume_mode, const char* method_name) {
// AsyncGeneratorEnqueue produces a new Promise, and appends it to the list
// of async generator requests to be executed. If the generator is not
// presently executing, then this method will loop through, processing each
// request from front to back.
// This loop resides in AsyncGeneratorResumeNext.
Node* promise = AllocateAndInitJSPromise(context);
Label enqueue(this), if_receiverisincompatible(this, Label::kDeferred);
GotoIf(TaggedIsSmi(generator), &if_receiverisincompatible);
Branch(HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE), &enqueue,
&if_receiverisincompatible);
Bind(&enqueue);
{
Label done(this);
Node* const req =
AllocateAsyncGeneratorRequest(resume_mode, value, promise);
AddAsyncGeneratorRequestToQueue(generator, req);
// Let state be generator.[[AsyncGeneratorState]]
// If state is not "executing", then
// Perform AsyncGeneratorResumeNext(Generator)
// Check if the {receiver} is running or already closed.
Node* continuation = LoadGeneratorState(generator);
GotoIf(SmiEqual(continuation,
SmiConstant(JSAsyncGeneratorObject::kGeneratorExecuting)),
&done);
CallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);
Goto(&done);
Bind(&done);
Return(promise);
}
Bind(&if_receiverisincompatible);
{
Node* const error =
MakeTypeError(MessageTemplate::kIncompatibleMethodReceiver, context,
CStringConstant(method_name), generator);
InternalPromiseReject(context, promise, error, true);
Return(promise);
}
}
Node* AsyncGeneratorBuiltinsAssembler::AllocateAsyncGeneratorRequest(
JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
Node* promise) {
CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
Node* request = Allocate(AsyncGeneratorRequest::kSize);
StoreMapNoWriteBarrier(request, Heap::kAsyncGeneratorRequestMapRootIndex);
StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kNextOffset,
UndefinedConstant());
StoreObjectFieldNoWriteBarrier(request,
AsyncGeneratorRequest::kResumeModeOffset,
SmiConstant(resume_mode));
StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kValueOffset,
resume_value);
StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kPromiseOffset,
promise);
StoreObjectFieldRoot(request, AsyncGeneratorRequest::kNextOffset,
Heap::kUndefinedValueRootIndex);
return request;
}
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
Node* context, Node* value,
JSAsyncGeneratorObject::ResumeMode resume_mode) {
Node* const generator =
LoadContextElement(context, AwaitContext::kGeneratorSlot);
CSA_SLOW_ASSERT(this,
HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
#if defined(DEBUG) && defined(ENABLE_SLOW_DCHECKS)
Node* const awaited_promise = LoadGeneratorAwaitedPromise(generator);
CSA_SLOW_ASSERT(this, HasInstanceType(awaited_promise, JS_PROMISE_TYPE));
CSA_SLOW_ASSERT(this, SmiNotEqual(LoadObjectField(awaited_promise,
JSPromise::kStatusOffset),
SmiConstant(v8::Promise::kPending)));
#endif
ClearAwaitedPromise(generator);
CSA_SLOW_ASSERT(this, IsGeneratorSuspended(generator));
CallStub(CodeFactory::ResumeAwaitedAsyncGenerator(isolate()), context, value,
generator, SmiConstant(resume_mode));
TailCallStub(CodeFactory::AsyncGeneratorResumeNext(isolate()), context,
generator);
}
template <typename Descriptor>
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
Node* generator = Parameter(1);
Node* value = Parameter(2);
Node* context = Parameter(5);
CSA_SLOW_ASSERT(this,
HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
Node* const request = LoadFirstAsyncGeneratorRequestFromQueue(generator);
CSA_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
NodeGenerator1 closure_context = [&](Node* native_context) -> Node* {
Node* const context =
CreatePromiseContext(native_context, AwaitContext::kLength);
StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
generator);
return context;
};
Node* outer_promise =
LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
const int resolve_index = Context::ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN;
const int reject_index = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;
Node* promise =
Await(context, generator, value, outer_promise, closure_context,
resolve_index, reject_index, is_catchable);
CSA_SLOW_ASSERT(this, IsGeneratorNotSuspendedForAwait(generator));
StoreObjectField(generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset,
promise);
Return(UndefinedConstant());
}
void AsyncGeneratorBuiltinsAssembler::AddAsyncGeneratorRequestToQueue(
Node* generator, Node* request) {
Variable var_current(this, MachineRepresentation::kTagged);
Label empty(this), loop(this, &var_current), done(this);
var_current.Bind(
LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
Branch(IsUndefined(var_current.value()), &empty, &loop);
Bind(&empty);
{
StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, request);
Goto(&done);
}
Bind(&loop);
{
Label loop_next(this), next_empty(this);
Node* current = var_current.value();
Node* next = LoadObjectField(current, AsyncGeneratorRequest::kNextOffset);
Branch(IsUndefined(next), &next_empty, &loop_next);
Bind(&next_empty);
{
StoreObjectField(current, AsyncGeneratorRequest::kNextOffset, request);
Goto(&done);
}
Bind(&loop_next);
{
var_current.Bind(next);
Goto(&loop);
}
}
Bind(&done);
}
Node* AsyncGeneratorBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
Node* generator) {
// Removes and returns the first AsyncGeneratorRequest from a
// JSAsyncGeneratorObject's queue. Asserts that the queue is not empty.
CSA_ASSERT(this, HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
Node* request =
LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
CSA_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
Node* next = LoadObjectField(request, AsyncGeneratorRequest::kNextOffset);
StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
return request;
}
} // namespace
// https://tc39.github.io/proposal-async-iteration/
// Section #sec-asyncgenerator-prototype-next
TF_BUILTIN(AsyncGeneratorPrototypeNext, AsyncGeneratorBuiltinsAssembler) {
Node* const generator = Parameter(Descriptor::kReceiver);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
AsyncGeneratorEnqueue(context, generator, value,
JSAsyncGeneratorObject::kNext,
"[AsyncGenerator].prototype.next");
}
// https://tc39.github.io/proposal-async-iteration/
// Section #sec-asyncgenerator-prototype-return
TF_BUILTIN(AsyncGeneratorPrototypeReturn, AsyncGeneratorBuiltinsAssembler) {
Node* generator = Parameter(Descriptor::kReceiver);
Node* value = Parameter(Descriptor::kValue);
Node* context = Parameter(Descriptor::kContext);
AsyncGeneratorEnqueue(context, generator, value,
JSAsyncGeneratorObject::kReturn,
"[AsyncGenerator].prototype.return");
}
// https://tc39.github.io/proposal-async-iteration/
// Section #sec-asyncgenerator-prototype-throw
TF_BUILTIN(AsyncGeneratorPrototypeThrow, AsyncGeneratorBuiltinsAssembler) {
Node* generator = Parameter(Descriptor::kReceiver);
Node* value = Parameter(Descriptor::kValue);
Node* context = Parameter(Descriptor::kContext);
AsyncGeneratorEnqueue(context, generator, value,
JSAsyncGeneratorObject::kThrow,
"[AsyncGenerator].prototype.throw");
}
TF_BUILTIN(AsyncGeneratorYield, AsyncGeneratorBuiltinsAssembler) {
Node* const generator = Parameter(Descriptor::kReceiver);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
CSA_ASSERT_JS_ARGC_EQ(this, 1);
CSA_SLOW_ASSERT(this,
HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
CSA_ASSERT(this, IsGeneratorNotSuspendedForAwait(generator));
CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value,
FalseConstant());
// Yield must have been reached via ResumeNext(), so don't recursively call
// it.
Return(UndefinedConstant());
}
TF_BUILTIN(AsyncGeneratorRawYield, AsyncGeneratorBuiltinsAssembler) {
Node* const generator = Parameter(Descriptor::kReceiver);
Node* const iter_result = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
CSA_ASSERT_JS_ARGC_EQ(this, 1);
CSA_SLOW_ASSERT(this,
HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
CSA_ASSERT(this, IsGeneratorNotSuspendedForAwait(generator));
Variable var_value(this, MachineRepresentation::kTagged);
Variable var_done(this, MachineRepresentation::kTagged);
// RawYield is used for yield*, and values sent to yield* are always
// iterator result objects.
Label if_slow(this), async_generator_resolve(this);
GotoIfNot(IsFastJSIterResult(context, iter_result), &if_slow);
var_value.Bind(LoadObjectField(iter_result, JSIteratorResult::kValueOffset));
var_done.Bind(LoadObjectField(iter_result, JSIteratorResult::kDoneOffset));
Goto(&async_generator_resolve);
Bind(&if_slow);
{
var_value.Bind(
GetProperty(context, iter_result, factory()->value_string()));
Node* const done =
GetProperty(context, iter_result, factory()->done_string());
var_done.Bind(Select(
IsBoolean(done), [=]() { return done; },
[=]() { return CallBuiltin(Builtins::kToBoolean, context, done); },
MachineRepresentation::kTagged));
Goto(&async_generator_resolve);
}
Bind(&async_generator_resolve);
Node* const value = var_value.value();
Node* const done = var_done.value();
CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value,
done);
Return(UndefinedConstant());
}
TF_BUILTIN(AsyncGeneratorAwaitResolveClosure, AsyncGeneratorBuiltinsAssembler) {
Node* value = Parameter(Descriptor::kValue);
Node* context = Parameter(Descriptor::kContext);
AsyncGeneratorAwaitResumeClosure(context, value,
JSAsyncGeneratorObject::kNext);
}
TF_BUILTIN(AsyncGeneratorAwaitRejectClosure, AsyncGeneratorBuiltinsAssembler) {
Node* value = Parameter(Descriptor::kValue);
Node* context = Parameter(Descriptor::kContext);
AsyncGeneratorAwaitResumeClosure(context, value,
JSAsyncGeneratorObject::kThrow);
}
TF_BUILTIN(AsyncGeneratorAwaitUncaught, AsyncGeneratorBuiltinsAssembler) {
const bool kIsCatchable = false;
AsyncGeneratorAwait<Descriptor>(kIsCatchable);
}
TF_BUILTIN(AsyncGeneratorAwaitCaught, AsyncGeneratorBuiltinsAssembler) {
const bool kIsCatchable = true;
AsyncGeneratorAwait<Descriptor>(kIsCatchable);
}
TF_BUILTIN(AsyncGeneratorResumeNext, AsyncGeneratorBuiltinsAssembler) {
typedef AsyncGeneratorResumeNextDescriptor Descriptor;
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const context = Parameter(Descriptor::kContext);
// The penultimate step of proposal-async-iteration/#sec-asyncgeneratorresolve
// and proposal-async-iteration/#sec-asyncgeneratorreject both recursively
// invoke AsyncGeneratorResumeNext() again.
//
// This implementation does not implement this recursively, but instead
// performs a loop in AsyncGeneratorResumeNext, which continues as long as
// there is an AsyncGeneratorRequest in the queue, and as long as the
// generator is not suspended due to an AwaitExpression.
Variable var_state(this, MachineRepresentation::kTaggedSigned,
LoadGeneratorState(generator));
Variable var_next(this, MachineRepresentation::kTagged,
LoadFirstAsyncGeneratorRequestFromQueue(generator));
Variable* labels[] = {&var_state, &var_next};
Label start(this, 2, labels);
Goto(&start);
Bind(&start);
CSA_ASSERT(this, IsGeneratorNotExecuting(generator));
// Stop resuming if suspended for Await.
ReturnIf(IsGeneratorSuspendedForAwait(generator), UndefinedConstant());
// Stop resuming if request queue is empty.
ReturnIf(IsUndefined(var_next.value()), UndefinedConstant());
Node* const next = var_next.value();
Node* const resume_type = LoadResumeTypeFromAsyncGeneratorRequest(next);
Label if_abrupt(this), if_normal(this), resume_generator(this);
Branch(IsAbruptResumeType(resume_type), &if_abrupt, &if_normal);
Bind(&if_abrupt);
{
Label settle_promise(this), fulfill_promise(this), reject_promise(this);
GotoIfNot(IsGeneratorStateSuspendedAtStart(var_state.value()),
&settle_promise);
CloseGenerator(generator);
var_state.Bind(SmiConstant(JSGeneratorObject::kGeneratorClosed));
Goto(&settle_promise);
Bind(&settle_promise);
GotoIfNot(IsGeneratorStateClosed(var_state.value()), &resume_generator);
Branch(SmiEqual(resume_type, SmiConstant(JSGeneratorObject::kReturn)),
&fulfill_promise, &reject_promise);
Bind(&fulfill_promise);
CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator,
LoadValueFromAsyncGeneratorRequest(next), TrueConstant());
var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
Goto(&start);
Bind(&reject_promise);
CallBuiltin(Builtins::kAsyncGeneratorReject, context, generator,
LoadValueFromAsyncGeneratorRequest(next));
var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
Goto(&start);
}
Bind(&if_normal);
{
GotoIfNot(IsGeneratorStateClosed(var_state.value()), &resume_generator);
CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator,
UndefinedConstant(), TrueConstant());
var_state.Bind(LoadGeneratorState(generator));
var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
Goto(&start);
}
Bind(&resume_generator);
{
CallStub(CodeFactory::ResumeAsyncGenerator(isolate()), context,
LoadValueFromAsyncGeneratorRequest(next), generator, resume_type);
var_state.Bind(LoadGeneratorState(generator));
var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
Goto(&start);
}
}
TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) {
typedef AsyncGeneratorResolveDescriptor Descriptor;
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const value = Parameter(Descriptor::kValue);
Node* const done = Parameter(Descriptor::kDone);
Node* const context = Parameter(Descriptor::kContext);
Node* const next = TakeFirstAsyncGeneratorRequestFromQueue(generator);
Node* const promise = LoadPromiseFromAsyncGeneratorRequest(next);
Node* const wrapper = AllocateAndInitJSPromise(context);
InternalResolvePromise(context, wrapper, value);
Node* const on_fulfilled =
CreateUnwrapClosure(LoadNativeContext(context), done);
Node* const undefined = UndefinedConstant();
InternalPerformPromiseThen(context, wrapper, on_fulfilled, undefined, promise,
undefined, undefined);
// Per spec, AsyncGeneratorResolve() returns undefined. However, for the
// benefit of %TraceExit(), return the Promise.
Return(promise);
}
TF_BUILTIN(AsyncGeneratorReject, AsyncGeneratorBuiltinsAssembler) {
typedef AsyncGeneratorRejectDescriptor Descriptor;
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
Node* const next = TakeFirstAsyncGeneratorRequestFromQueue(generator);
Node* const promise = LoadPromiseFromAsyncGeneratorRequest(next);
InternalPromiseReject(context, promise, value, true);
Return(UndefinedConstant());
}
} // namespace internal
} // namespace v8