Commit a094e360 authored by Caitlin Potter's avatar Caitlin Potter Committed by Commit Bot

[async-iteration] eliminate Suspend for AsyncGeneratorYield await

A spec change (https://github.com/tc39/proposal-async-iteration/commit/a0dfeba1a8029012b6e964099929b8a157818c9f) introduced a number of Await operations to the spec. In turn, this caused generated bytecode for async generators to grow drastically.

This commit moves the Await within AsyncGeneratorYield (https://tc39.github.io/proposal-async-iteration/#sec-asyncgeneratoryield step 5) into a new TFJ builtin, similar in structure to AsyncGeneratorAwait, but instead of resuming the generator on resolution of the Promise, the current generator request's Promise is fulfilled instead.

This results in a reduction in generated bytecode without losing any statically available information.

BUG=v8:5855

Change-Id: Ib5bcf06132d221beffdea30639a7b4437030143b
Reviewed-on: https://chromium-review.googlesource.com/582487
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47224}
parent 10ec3951
......@@ -1488,6 +1488,12 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
info->set_length(1);
native_context()->set_async_generator_await_reject_shared_fun(*info);
code = BUILTIN_CODE(isolate, AsyncGeneratorYieldResolveClosure);
info = factory->NewSharedFunctionInfo(factory->empty_string(), code, false);
info->set_internal_formal_parameter_count(1);
info->set_length(1);
native_context()->set_async_generator_yield_resolve_shared_fun(*info);
code = BUILTIN_CODE(isolate, AsyncGeneratorReturnResolveClosure);
info = factory->NewSharedFunctionInfo(factory->empty_string(), code, false);
info->set_internal_formal_parameter_count(1);
......
......@@ -23,7 +23,7 @@ Node* AsyncBuiltinsAssembler::Await(
Node* context, Node* generator, Node* value, Node* outer_promise,
int context_length, const ContextInitializer& init_closure_context,
int on_resolve_context_index, int on_reject_context_index,
bool is_predicted_as_caught) {
Node* is_predicted_as_caught) {
DCHECK_GE(context_length, Context::MIN_CONTEXT_SLOTS);
Node* const native_context = LoadNativeContext(context);
......@@ -146,7 +146,8 @@ Node* AsyncBuiltinsAssembler::Await(
CallRuntime(Runtime::kSetProperty, context, on_reject, key,
TrueConstant(), SmiConstant(STRICT));
if (is_predicted_as_caught) PromiseSetHandledHint(value);
GotoIf(IsFalse(is_predicted_as_caught), &common);
PromiseSetHandledHint(value);
}
Goto(&common);
......
......@@ -27,7 +27,17 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
int context_length,
const ContextInitializer& init_closure_context,
int on_resolve_context_index, int on_reject_context_index,
bool is_predicted_as_caught);
Node* is_predicted_as_caught);
Node* Await(Node* context, Node* generator, Node* value, Node* outer_promise,
int context_length,
const ContextInitializer& init_closure_context,
int on_resolve_context_index, int on_reject_context_index,
bool is_predicted_as_caught) {
return Await(context, generator, value, outer_promise, context_length,
init_closure_context, on_resolve_context_index,
on_reject_context_index,
BooleanConstant(is_predicted_as_caught));
}
// Return a new built-in function object as defined in
// Async Iterator Value Unwrap Functions
......
......@@ -539,6 +539,48 @@ TF_BUILTIN(AsyncGeneratorReject, AsyncGeneratorBuiltinsAssembler) {
TrueConstant()));
}
TF_BUILTIN(AsyncGeneratorYield, AsyncGeneratorBuiltinsAssembler) {
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const value = Parameter(Descriptor::kValue);
Node* const is_caught = Parameter(Descriptor::kIsCaught);
Node* const context = Parameter(Descriptor::kContext);
Node* const request = LoadFirstAsyncGeneratorRequestFromQueue(generator);
Node* const outer_promise = LoadPromiseFromAsyncGeneratorRequest(request);
ContextInitializer init_closure_context = [&](Node* context) {
StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
generator);
};
const int on_resolve = Context::ASYNC_GENERATOR_YIELD_RESOLVE_SHARED_FUN;
const int on_reject = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;
Node* const promise =
Await(context, generator, value, outer_promise, AwaitContext::kLength,
init_closure_context, on_resolve, on_reject, is_caught);
StoreObjectField(generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset,
promise);
Return(UndefinedConstant());
}
TF_BUILTIN(AsyncGeneratorYieldResolveClosure, AsyncGeneratorBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const value = Parameter(Descriptor::kValue);
Node* const generator =
LoadContextElement(context, AwaitContext::kGeneratorSlot);
CSA_SLOW_ASSERT(this, IsGeneratorSuspendedForAwait(generator));
ClearAwaitedPromise(generator);
// Per proposal-async-iteration/#sec-asyncgeneratoryield step 9
// Return ! AsyncGeneratorResolve(_F_.[[Generator]], _value_, *false*).
CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value,
FalseConstant());
TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);
}
TF_BUILTIN(AsyncGeneratorReturnProcessor, AsyncGeneratorBuiltinsAssembler) {
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const req = LoadFirstAsyncGeneratorRequestFromQueue(generator);
......
......@@ -1053,6 +1053,7 @@ namespace internal {
\
TFS(AsyncGeneratorResolve, kGenerator, kValue, kDone) \
TFS(AsyncGeneratorReject, kGenerator, kValue) \
TFS(AsyncGeneratorYield, kGenerator, kValue, kIsCaught) \
TFS(AsyncGeneratorReturnProcessor, kGenerator) \
TFS(AsyncGeneratorResumeNext, kGenerator) \
\
......@@ -1078,6 +1079,7 @@ namespace internal {
TFJ(AsyncGeneratorAwaitUncaught, 1, kAwaited) \
TFJ(AsyncGeneratorAwaitResolveClosure, 1, kValue) \
TFJ(AsyncGeneratorAwaitRejectClosure, 1, kValue) \
TFJ(AsyncGeneratorYieldResolveClosure, 1, kValue) \
TFJ(AsyncGeneratorReturnResolveClosure, 1, kValue) \
TFJ(AsyncGeneratorReturnRejectClosure, 1, kValue) \
\
......
......@@ -46,6 +46,8 @@ Reduction JSIntrinsicLowering::Reduce(Node* node) {
return ReduceAsyncGeneratorReject(node);
case Runtime::kInlineAsyncGeneratorResolve:
return ReduceAsyncGeneratorResolve(node);
case Runtime::kInlineAsyncGeneratorYield:
return ReduceAsyncGeneratorYield(node);
case Runtime::kInlineGeneratorGetResumeMode:
return ReduceGeneratorGetResumeMode(node);
case Runtime::kInlineGeneratorGetContext:
......@@ -198,6 +200,12 @@ Reduction JSIntrinsicLowering::ReduceAsyncGeneratorResolve(Node* node) {
0);
}
Reduction JSIntrinsicLowering::ReduceAsyncGeneratorYield(Node* node) {
return Change(
node, Builtins::CallableFor(isolate(), Builtins::kAsyncGeneratorYield),
0);
}
Reduction JSIntrinsicLowering::ReduceGeneratorGetContext(Node* node) {
Node* const generator = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
......
......@@ -51,6 +51,7 @@ class V8_EXPORT_PRIVATE JSIntrinsicLowering final
Reduction ReduceGeneratorGetInputOrDebugPos(Node* node);
Reduction ReduceAsyncGeneratorReject(Node* node);
Reduction ReduceAsyncGeneratorResolve(Node* node);
Reduction ReduceAsyncGeneratorYield(Node* node);
Reduction ReduceGeneratorSaveInputForAwait(Node* node);
Reduction ReduceGeneratorGetResumeMode(Node* node);
Reduction ReduceIsInstanceType(Node* node, InstanceType instance_type);
......
......@@ -217,6 +217,8 @@ enum ContextLookupFlags {
async_generator_await_reject_shared_fun) \
V(ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN, SharedFunctionInfo, \
async_generator_await_resolve_shared_fun) \
V(ASYNC_GENERATOR_YIELD_RESOLVE_SHARED_FUN, SharedFunctionInfo, \
async_generator_yield_resolve_shared_fun) \
V(ASYNC_GENERATOR_RETURN_RESOLVE_SHARED_FUN, SharedFunctionInfo, \
async_generator_return_resolve_shared_fun) \
V(ASYNC_GENERATOR_RETURN_REJECT_SHARED_FUN, SharedFunctionInfo, \
......
......@@ -631,6 +631,10 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadFalse() {
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::LoadBoolean(bool value) {
return value ? LoadTrue() : LoadFalse();
}
BytecodeArrayBuilder& BytecodeArrayBuilder::LoadAccumulatorWithRegister(
Register reg) {
if (register_optimizer_) {
......
......@@ -79,6 +79,7 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final
BytecodeArrayBuilder& LoadTheHole();
BytecodeArrayBuilder& LoadTrue();
BytecodeArrayBuilder& LoadFalse();
BytecodeArrayBuilder& LoadBoolean(bool value);
// Global loads to the accumulator and stores from the accumulator.
BytecodeArrayBuilder& LoadGlobal(const AstRawString* name, int feedback_slot,
......
......@@ -2602,25 +2602,25 @@ void BytecodeGenerator::VisitYield(Yield* expr) {
if (!expr->IsInitialYield()) {
if (IsAsyncGeneratorFunction(function_kind())) {
// AsyncGenerator yields (with the exception of the initial yield)
// delegate to AsyncGeneratorResolve(), implemented via the runtime call
// below.
// delegate work to the AsyncGeneratorYield stub, which Awaits the operand
// and on success, wraps the value in an IteratorResult.
RegisterAllocationScope register_scope(this);
RegisterList args = register_allocator()->NewRegisterList(3);
builder()
->MoveRegister(generator_object_, args[0])
.StoreAccumulatorInRegister(args[1])
.LoadFalse()
.StoreAccumulatorInRegister(args[2])
.CallRuntime(Runtime::kInlineAsyncGeneratorResolve, args);
->MoveRegister(generator_object_, args[0]) // generator
.StoreAccumulatorInRegister(args[1]) // value
.LoadBoolean(catch_prediction() != HandlerTable::ASYNC_AWAIT)
.StoreAccumulatorInRegister(args[2]) // is_caught
.CallRuntime(Runtime::kInlineAsyncGeneratorYield, args);
} else {
// Generator yields (with the exception of the initial yield) wrap the
// value into IteratorResult.
RegisterAllocationScope register_scope(this);
RegisterList args = register_allocator()->NewRegisterList(2);
builder()
->StoreAccumulatorInRegister(args[0])
->StoreAccumulatorInRegister(args[0]) // value
.LoadFalse()
.StoreAccumulatorInRegister(args[1])
.StoreAccumulatorInRegister(args[1]) // done
.CallRuntime(Runtime::kInlineCreateIterResultObject, args);
}
}
......
......@@ -439,6 +439,11 @@ Node* IntrinsicsGenerator::AsyncGeneratorResolve(Node* input, Node* arg_count,
Builtins::kAsyncGeneratorResolve);
}
Node* IntrinsicsGenerator::AsyncGeneratorYield(Node* input, Node* arg_count,
Node* context) {
return IntrinsicAsBuiltinCall(input, context, Builtins::kAsyncGeneratorYield);
}
void IntrinsicsGenerator::AbortIfArgCountMismatch(int expected, Node* actual) {
InterpreterAssembler::Label match(assembler_);
Node* comparison = __ Word32Equal(actual, __ Int32Constant(expected));
......
......@@ -16,6 +16,7 @@ namespace interpreter {
#define INTRINSICS_LIST(V) \
V(AsyncGeneratorReject, async_generator_reject, 2) \
V(AsyncGeneratorResolve, async_generator_resolve, 3) \
V(AsyncGeneratorYield, async_generator_yield, 3) \
V(CreateJSGeneratorObject, create_js_generator_object, 2) \
V(GeneratorGetContext, generator_get_context, 1) \
V(GeneratorGetResumeMode, generator_get_resume_mode, 1) \
......
......@@ -2949,12 +2949,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseYieldExpression(
return impl()->RewriteYieldStar(expression, pos);
}
if (is_async_generator()) {
// Per https://github.com/tc39/proposal-async-iteration/pull/102, the yield
// operand must be Await-ed in async generators.
expression = factory()->NewAwait(expression, pos);
}
// Hackily disambiguate o from o.next and o [Symbol.iterator]().
// TODO(verwaest): Come up with a better solution.
ExpressionT yield =
......
......@@ -80,6 +80,12 @@ RUNTIME_FUNCTION(Runtime_AsyncGeneratorReject) {
UNREACHABLE();
}
RUNTIME_FUNCTION(Runtime_AsyncGeneratorYield) {
// Runtime call is implemented in InterpreterIntrinsics and lowered in
// JSIntrinsicLowering
UNREACHABLE();
}
RUNTIME_FUNCTION(Runtime_GeneratorGetResumeMode) {
// Runtime call is implemented in InterpreterIntrinsics and lowered in
// JSIntrinsicLowering
......
......@@ -230,17 +230,18 @@ namespace internal {
F(IsFunction, 1, 1) \
F(FunctionToString, 1, 1)
#define FOR_EACH_INTRINSIC_GENERATOR(F) \
F(CreateJSGeneratorObject, 2, 1) \
F(GeneratorClose, 1, 1) \
F(GeneratorGetFunction, 1, 1) \
F(GeneratorGetReceiver, 1, 1) \
F(GeneratorGetContext, 1, 1) \
F(GeneratorGetInputOrDebugPos, 1, 1) \
F(AsyncGeneratorResolve, 3, 1) \
F(AsyncGeneratorReject, 2, 1) \
F(GeneratorGetContinuation, 1, 1) \
F(GeneratorGetSourcePosition, 1, 1) \
#define FOR_EACH_INTRINSIC_GENERATOR(F) \
F(CreateJSGeneratorObject, 2, 1) \
F(GeneratorClose, 1, 1) \
F(GeneratorGetFunction, 1, 1) \
F(GeneratorGetReceiver, 1, 1) \
F(GeneratorGetContext, 1, 1) \
F(GeneratorGetInputOrDebugPos, 1, 1) \
F(AsyncGeneratorResolve, 3, 1) \
F(AsyncGeneratorReject, 2, 1) \
F(AsyncGeneratorYield, 3, 1) \
F(GeneratorGetContinuation, 1, 1) \
F(GeneratorGetSourcePosition, 1, 1) \
F(GeneratorGetResumeMode, 1, 1)
#ifdef V8_INTL_SUPPORT
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment