[async-await] Refactor await optimization and include async generators

Design doc:
https://docs.google.com/document/d/1kL08cz4lR6gO5b2FATNK3QAfS8t-6K6kdk88U-n8tug/edit

This CL is a follow-up after the original implementation, see CL:
https://chromium-review.googlesource.com/c/v8/v8/+/1106977

It includes a fix for the missing async generators optimization,
as well as cleanup of the manual patching of the builtins. It also includes
mjsunit test for all usages of the new behaviour.

Bug: v8:8267

Change-Id: I999f341acb746c6da5216e44b68a519656fd5403
Reviewed-on: https://chromium-review.googlesource.com/c/1261124
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56414}
This commit is contained in:
Maya Lekova 2018-10-05 14:32:47 +02:00 committed by Commit Bot
parent 3eceaf0349
commit 2a2c9e5f79
9 changed files with 175 additions and 202 deletions

View File

@ -4543,54 +4543,6 @@ void Genesis::InitializeGlobal_harmony_string_matchall() {
}
void Genesis::InitializeGlobal_harmony_await_optimization() {
if (!FLAG_harmony_await_optimization) return;
// async/await
Handle<JSFunction> await_caught_function = SimpleCreateFunction(
isolate(), factory()->empty_string(),
Builtins::kAsyncFunctionAwaitCaughtOptimized, 2, false);
native_context()->set_async_function_await_caught(*await_caught_function);
Handle<JSFunction> await_uncaught_function = SimpleCreateFunction(
isolate(), factory()->empty_string(),
Builtins::kAsyncFunctionAwaitUncaughtOptimized, 2, false);
native_context()->set_async_function_await_uncaught(*await_uncaught_function);
// async generators
Handle<JSObject> async_iterator_prototype =
factory()->NewJSObject(isolate()->object_function(), TENURED);
SimpleInstallFunction(
isolate(), async_iterator_prototype, factory()->async_iterator_symbol(),
"[Symbol.asyncIterator]", Builtins::kReturnReceiver, 0, true);
Handle<JSObject> async_from_sync_iterator_prototype =
factory()->NewJSObject(isolate()->object_function(), TENURED);
SimpleInstallFunction(
isolate(), async_from_sync_iterator_prototype, factory()->next_string(),
Builtins::kAsyncFromSyncIteratorPrototypeNextOptimized, 1, true);
SimpleInstallFunction(
isolate(), async_from_sync_iterator_prototype, factory()->return_string(),
Builtins::kAsyncFromSyncIteratorPrototypeReturnOptimized, 1, true);
SimpleInstallFunction(
isolate(), async_from_sync_iterator_prototype, factory()->throw_string(),
Builtins::kAsyncFromSyncIteratorPrototypeThrowOptimized, 1, true);
JSObject::AddProperty(
isolate(), async_from_sync_iterator_prototype,
factory()->to_string_tag_symbol(),
factory()->NewStringFromAsciiChecked("Async-from-Sync Iterator"),
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY));
JSObject::ForceSetPrototype(async_from_sync_iterator_prototype,
async_iterator_prototype);
Handle<Map> async_from_sync_iterator_map = factory()->NewMap(
JS_ASYNC_FROM_SYNC_ITERATOR_TYPE, JSAsyncFromSyncIterator::kSize);
Map::SetPrototype(isolate(), async_from_sync_iterator_map,
async_from_sync_iterator_prototype);
native_context()->set_async_from_sync_iterator_map(
*async_from_sync_iterator_map);
}
#ifdef V8_INTL_SUPPORT

View File

@ -21,10 +21,6 @@ class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
void AsyncFunctionAwait(Node* const context, Node* const generator,
Node* const awaited, Node* const outer_promise,
const bool is_predicted_as_caught);
void AsyncFunctionAwaitOptimized(Node* const context, Node* const generator,
Node* const awaited,
Node* const outer_promise,
const bool is_predicted_as_caught);
void AsyncFunctionAwaitResumeClosure(
Node* const context, Node* const sent_value,
@ -126,38 +122,6 @@ void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
Goto(&after_debug_hook);
}
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitOptimized(
Node* const context, Node* const generator, Node* const awaited,
Node* const outer_promise, const bool is_predicted_as_caught) {
CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
// TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
// the awaited promise if it is already a promise. Reuse is non-spec compliant
// but part of our old behavior gives us a couple of percent
// performance boost.
// TODO(jgruber): Use a faster specialized version of
// InternalPerformPromiseThen.
Label after_debug_hook(this), call_debug_hook(this, Label::kDeferred);
GotoIf(HasAsyncEventDelegate(), &call_debug_hook);
Goto(&after_debug_hook);
BIND(&after_debug_hook);
AwaitOptimized(context, generator, awaited, outer_promise,
Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN,
is_predicted_as_caught);
// Return outer promise to avoid adding an load of the outer promise before
// suspending in BytecodeGenerator.
Return(outer_promise);
BIND(&call_debug_hook);
CallRuntime(Runtime::kDebugAsyncFunctionSuspended, context, outer_promise);
Goto(&after_debug_hook);
}
// Called by the parser from the desugaring of 'await' when catch
// prediction indicates that there is a locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) {
@ -173,19 +137,6 @@ TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) {
kIsPredictedAsCaught);
}
TF_BUILTIN(AsyncFunctionAwaitCaughtOptimized, AsyncFunctionBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 3);
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const awaited = Parameter(Descriptor::kAwaited);
Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
Node* const context = Parameter(Descriptor::kContext);
static const bool kIsPredictedAsCaught = true;
AsyncFunctionAwaitOptimized(context, generator, awaited, outer_promise,
kIsPredictedAsCaught);
}
// Called by the parser from the desugaring of 'await' when catch
// prediction indicates no locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) {
@ -201,20 +152,6 @@ TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) {
kIsPredictedAsCaught);
}
TF_BUILTIN(AsyncFunctionAwaitUncaughtOptimized,
AsyncFunctionBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 3);
Node* const generator = Parameter(Descriptor::kGenerator);
Node* const awaited = Parameter(Descriptor::kAwaited);
Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
Node* const context = Parameter(Descriptor::kContext);
static const bool kIsPredictedAsCaught = false;
AsyncFunctionAwaitOptimized(context, generator, awaited, outer_promise,
kIsPredictedAsCaught);
}
TF_BUILTIN(AsyncFunctionPromiseCreate, AsyncFunctionBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 0);
Node* const context = Parameter(Descriptor::kContext);

View File

@ -23,10 +23,11 @@ class ValueUnwrapContext {
} // namespace
Node* AsyncBuiltinsAssembler::Await(
Node* context, Node* generator, Node* value, Node* outer_promise,
Node* on_resolve_context_index, Node* on_reject_context_index,
Node* is_predicted_as_caught) {
Node* AsyncBuiltinsAssembler::AwaitOld(Node* context, Node* generator,
Node* value, Node* outer_promise,
Node* on_resolve_context_index,
Node* on_reject_context_index,
Node* is_predicted_as_caught) {
Node* const native_context = LoadNativeContext(context);
static const int kWrappedPromiseOffset =
@ -278,6 +279,37 @@ Node* AsyncBuiltinsAssembler::AwaitOptimized(
on_resolve, on_reject, throwaway);
}
Node* AsyncBuiltinsAssembler::Await(Node* context, Node* generator, Node* value,
Node* outer_promise,
Node* on_resolve_context_index,
Node* on_reject_context_index,
Node* is_predicted_as_caught) {
VARIABLE(result, MachineRepresentation::kTagged);
Label if_old(this), if_new(this), done(this);
TNode<Word32T> flag_value = UncheckedCast<Word32T>(Load(
MachineType::Int32(),
ExternalConstant(
ExternalReference::address_of_harmony_await_optimization_flag())));
Branch(Word32Equal(flag_value, Int32Constant(0)), &if_old, &if_new);
BIND(&if_old);
result.Bind(AwaitOld(context, generator, value, outer_promise,
on_resolve_context_index, on_reject_context_index,
is_predicted_as_caught));
Goto(&done);
BIND(&if_new);
result.Bind(AwaitOptimized(context, generator, value, outer_promise,
on_resolve_context_index, on_reject_context_index,
is_predicted_as_caught));
Goto(&done);
BIND(&done);
return result.value();
}
void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,
Node* native_context,
Node* function,

View File

@ -24,10 +24,6 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
Node* Await(Node* context, Node* generator, Node* value, Node* outer_promise,
Node* on_resolve_context_index, Node* on_reject_context_index,
Node* is_predicted_as_caught);
Node* AwaitOptimized(Node* context, Node* generator, Node* value,
Node* outer_promise, Node* on_resolve_context_index,
Node* on_reject_context_index,
Node* is_predicted_as_caught);
Node* Await(Node* context, Node* generator, Node* value, Node* outer_promise,
int on_resolve_context_index, int on_reject_context_index,
Node* is_predicted_as_caught) {
@ -36,15 +32,6 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
IntPtrConstant(on_reject_context_index),
is_predicted_as_caught);
}
Node* AwaitOptimized(Node* context, Node* generator, Node* value,
Node* outer_promise, int on_resolve_context_index,
int on_reject_context_index,
Node* is_predicted_as_caught) {
return AwaitOptimized(context, generator, value, outer_promise,
IntPtrConstant(on_resolve_context_index),
IntPtrConstant(on_reject_context_index),
is_predicted_as_caught);
}
Node* Await(Node* context, Node* generator, Node* value, Node* outer_promise,
int on_resolve_context_index, int on_reject_context_index,
bool is_predicted_as_caught) {
@ -52,14 +39,6 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
on_resolve_context_index, on_reject_context_index,
BooleanConstant(is_predicted_as_caught));
}
Node* AwaitOptimized(Node* context, Node* generator, Node* value,
Node* outer_promise, int on_resolve_context_index,
int on_reject_context_index,
bool is_predicted_as_caught) {
return AwaitOptimized(context, generator, value, outer_promise,
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
@ -70,6 +49,14 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
Node* function, Node* context_index);
Node* AllocateAsyncIteratorValueUnwrapContext(Node* native_context,
Node* done);
Node* AwaitOld(Node* context, Node* generator, Node* value,
Node* outer_promise, Node* on_resolve_context_index,
Node* on_reject_context_index, Node* is_predicted_as_caught);
Node* AwaitOptimized(Node* context, Node* generator, Node* value,
Node* outer_promise, Node* on_resolve_context_index,
Node* on_reject_context_index,
Node* is_predicted_as_caught);
};
} // namespace internal

View File

@ -333,20 +333,6 @@ TF_BUILTIN(AsyncFromSyncIteratorPrototypeNext, AsyncFromSyncBuiltinsAssembler) {
"[Async-from-Sync Iterator].prototype.next");
}
TF_BUILTIN(AsyncFromSyncIteratorPrototypeNextOptimized,
AsyncFromSyncBuiltinsAssembler) {
Node* const iterator = Parameter(Descriptor::kReceiver);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
auto get_method = [=](Node* const unused) {
return LoadObjectField(iterator, JSAsyncFromSyncIterator::kNextOffset);
};
Generate_AsyncFromSyncIteratorMethodOptimized(
context, iterator, value, get_method, UndefinedMethodHandler(),
"[Async-from-Sync Iterator].prototype.next");
}
// https://tc39.github.io/proposal-async-iteration/
// Section #sec-%asyncfromsynciteratorprototype%.return
TF_BUILTIN(AsyncFromSyncIteratorPrototypeReturn,
@ -374,31 +360,6 @@ TF_BUILTIN(AsyncFromSyncIteratorPrototypeReturn,
"[Async-from-Sync Iterator].prototype.return");
}
TF_BUILTIN(AsyncFromSyncIteratorPrototypeReturnOptimized,
AsyncFromSyncBuiltinsAssembler) {
Node* const iterator = Parameter(Descriptor::kReceiver);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
auto if_return_undefined = [=](Node* const native_context,
Node* const promise, Label* if_exception) {
// If return is undefined, then
// Let iterResult be ! CreateIterResultObject(value, true)
Node* const iter_result = CallBuiltin(Builtins::kCreateIterResultObject,
context, value, TrueConstant());
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « iterResult »).
// IfAbruptRejectPromise(nextDone, promiseCapability).
// Return promiseCapability.[[Promise]].
CallBuiltin(Builtins::kResolvePromise, context, promise, iter_result);
Return(promise);
};
Generate_AsyncFromSyncIteratorMethodOptimized(
context, iterator, value, factory()->return_string(), if_return_undefined,
"[Async-from-Sync Iterator].prototype.return");
}
// https://tc39.github.io/proposal-async-iteration/
// Section #sec-%asyncfromsynciteratorprototype%.throw
TF_BUILTIN(AsyncFromSyncIteratorPrototypeThrow,
@ -416,20 +377,5 @@ TF_BUILTIN(AsyncFromSyncIteratorPrototypeThrow,
reason);
}
TF_BUILTIN(AsyncFromSyncIteratorPrototypeThrowOptimized,
AsyncFromSyncBuiltinsAssembler) {
Node* const iterator = Parameter(Descriptor::kReceiver);
Node* const reason = Parameter(Descriptor::kReason);
Node* const context = Parameter(Descriptor::kContext);
auto if_throw_undefined = [=](Node* const native_context, Node* const promise,
Label* if_exception) { Goto(if_exception); };
Generate_AsyncFromSyncIteratorMethodOptimized(
context, iterator, reason, factory()->throw_string(), if_throw_undefined,
"[Async-from-Sync Iterator].prototype.throw", Label::kNonDeferred,
reason);
}
} // namespace internal
} // namespace v8

View File

@ -429,12 +429,8 @@ namespace internal {
/* AsyncFunction */ \
TFJ(AsyncFunctionAwaitCaught, 3, kReceiver, kGenerator, kAwaited, \
kOuterPromise) \
TFJ(AsyncFunctionAwaitCaughtOptimized, 3, kReceiver, kGenerator, kAwaited, \
kOuterPromise) \
TFJ(AsyncFunctionAwaitUncaught, 3, kReceiver, kGenerator, kAwaited, \
kOuterPromise) \
TFJ(AsyncFunctionAwaitUncaughtOptimized, 3, kReceiver, kGenerator, kAwaited, \
kOuterPromise) \
TFJ(AsyncFunctionAwaitRejectClosure, 1, kReceiver, kSentError) \
TFJ(AsyncFunctionAwaitResolveClosure, 1, kReceiver, kSentValue) \
TFJ(AsyncFunctionPromiseCreate, 0, kReceiver) \
@ -1297,13 +1293,10 @@ namespace internal {
/* See tc39.github.io/proposal-async-iteration/ */ \
/* #sec-%asyncfromsynciteratorprototype%-object) */ \
TFJ(AsyncFromSyncIteratorPrototypeNext, 1, kReceiver, kValue) \
TFJ(AsyncFromSyncIteratorPrototypeNextOptimized, 1, kReceiver, kValue) \
/* #sec-%asyncfromsynciteratorprototype%.throw */ \
TFJ(AsyncFromSyncIteratorPrototypeThrow, 1, kReceiver, kReason) \
TFJ(AsyncFromSyncIteratorPrototypeThrowOptimized, 1, kReceiver, kReason) \
/* #sec-%asyncfromsynciteratorprototype%.return */ \
TFJ(AsyncFromSyncIteratorPrototypeReturn, 1, kReceiver, kValue) \
TFJ(AsyncFromSyncIteratorPrototypeReturnOptimized, 1, kReceiver, kValue) \
/* #sec-async-iterator-value-unwrap-functions */ \
TFJ(AsyncIteratorValueUnwrap, 1, kReceiver, kValue) \
\
@ -1480,14 +1473,9 @@ namespace internal {
#define BUILTIN_PROMISE_REJECTION_PREDICTION_LIST(V) \
V(AsyncFromSyncIteratorPrototypeNext) \
V(AsyncFromSyncIteratorPrototypeReturn) \
V(AsyncFromSyncIteratorPrototypeNextOptimized) \
V(AsyncFromSyncIteratorPrototypeThrowOptimized) \
V(AsyncFromSyncIteratorPrototypeReturnOptimized) \
V(AsyncFromSyncIteratorPrototypeThrow) \
V(AsyncFunctionAwaitCaught) \
V(AsyncFunctionAwaitCaughtOptimized) \
V(AsyncFunctionAwaitUncaught) \
V(AsyncFunctionAwaitUncaughtOptimized) \
V(AsyncGeneratorResolve) \
V(AsyncGeneratorAwaitCaught) \
V(AsyncGeneratorAwaitUncaught) \

View File

@ -459,6 +459,11 @@ ExternalReference ExternalReference::abort_with_reason() {
return ExternalReference(Redirect(FUNCTION_ADDR(i::abort_with_reason)));
}
ExternalReference
ExternalReference::address_of_harmony_await_optimization_flag() {
return ExternalReference(&FLAG_harmony_await_optimization);
}
ExternalReference ExternalReference::address_of_min_int() {
return ExternalReference(reinterpret_cast<Address>(&double_min_int_constant));
}

View File

@ -73,6 +73,8 @@ class StatsCounter;
V(address_of_double_neg_constant, "double_negate_constant") \
V(address_of_float_abs_constant, "float_absolute_constant") \
V(address_of_float_neg_constant, "float_negate_constant") \
V(address_of_harmony_await_optimization_flag, \
"FLAG_harmony_await_optimization") \
V(address_of_min_int, "LDoubleConstant::min_int") \
V(address_of_one_half, "LDoubleConstant::one_half") \
V(address_of_runtime_stats_flag, "FLAG_runtime_stats") \

View File

@ -0,0 +1,124 @@
// Copyright 2018 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.
// Flags: --harmony-await-optimization
// test basic interleaving
(function () {
const actual = [];
const expected = [ 'await', 1, 'await', 2 ];
const iterations = 2;
async function pushAwait() {
actual.push('await');
}
async function callAsync() {
for (let i = 0; i < iterations; i++) {
await pushAwait();
}
return 0;
}
function checkAssertions() {
assertArrayEquals(expected, actual,
'Async/await and promises should be interleaved.');
}
assertPromiseResult((async() => {
callAsync();
return new Promise(function (resolve) {
actual.push(1);
resolve();
}).then(function () {
actual.push(2);
}).then(checkAssertions);
})());
})();
// test async generators
(function () {
const actual = [];
const expected = [ 'await', 1, 'await', 2 ];
const iterations = 2;
async function pushAwait() {
actual.push('await');
}
async function* callAsync() {
for (let i = 0; i < iterations; i++) {
await pushAwait();
}
return 0;
}
function checkAssertions() {
assertArrayEquals(expected, actual,
'Async/await and promises should be interleaved when using async generators.');
}
assertPromiseResult((async() => {
callAsync().next();
return new Promise(function (resolve) {
actual.push(1);
resolve();
}).then(function () {
actual.push(2);
}).then(checkAssertions);
})());
})();
// test yielding from async generators
(function () {
const actual = [];
const expected = [
'Promise: 6',
'Promise: 5',
'Await: 3',
'Promise: 4',
'Promise: 3',
'Await: 2',
'Promise: 2',
'Promise: 1',
'Await: 1',
'Promise: 0'
];
const iterations = 3;
async function* naturalNumbers(start) {
let current = start;
while (current > 0) {
yield Promise.resolve(current--);
}
}
async function trigger() {
for await (const num of naturalNumbers(iterations)) {
actual.push('Await: ' + num);
}
}
async function checkAssertions() {
assertArrayEquals(expected, actual,
'Async/await and promises should be interleaved when yielding.');
}
async function countdown(counter) {
actual.push('Promise: ' + counter);
if (counter > 0) {
return Promise.resolve(counter - 1).then(countdown);
} else {
await checkAssertions();
}
}
assertPromiseResult((async() => {
trigger();
return countdown(iterations * 2);
})());
})();