[async-await] allocate HeapObjects for Await all at once.

Allocates the Await success/failure closures, their context, and
the two required JSPromise objects all at once in a single call,
rather than performing multiple allocations throughout the function.

Saves about 2kb of snapshot space on an x64.release build.

Performance impact of this change has not been measured yet.

BUG=v8:4483
R=ishell@chromium.org, jgruber@chromium.org

Change-Id: I8d911cb91f5d0e00544ad3ba608aa170f6b2f704
Reviewed-on: https://chromium-review.googlesource.com/549999
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Cr-Commit-Position: refs/heads/master@{#46360}
This commit is contained in:
Caitlin Potter 2017-06-29 14:11:19 -04:00 committed by Commit Bot
parent 5f0d82881c
commit b57366f2e1
7 changed files with 168 additions and 51 deletions

View File

@ -104,12 +104,9 @@ void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
NodeGenerator1 create_closure_context = [&](Node* native_context) -> Node* {
Node* const context =
CreatePromiseContext(native_context, AwaitContext::kLength);
ContextInitializer init_closure_context = [&](Node* context) {
StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
generator);
return context;
};
// TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
@ -120,8 +117,8 @@ void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
// InternalPerformPromiseThen.
Node* const result = Await(
context, generator, awaited, outer_promise, create_closure_context,
Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
context, generator, awaited, outer_promise, AwaitContext::kLength,
init_closure_context, Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN, is_predicted_as_caught);
Return(result);

View File

@ -21,42 +21,116 @@ class ValueUnwrapContext {
Node* AsyncBuiltinsAssembler::Await(
Node* context, Node* generator, Node* value, Node* outer_promise,
const NodeGenerator1& create_closure_context, int on_resolve_context_index,
int on_reject_context_index, bool is_predicted_as_caught) {
int context_length, const ContextInitializer& init_closure_context,
int on_resolve_context_index, int on_reject_context_index,
bool is_predicted_as_caught) {
DCHECK_GE(context_length, Context::MIN_CONTEXT_SLOTS);
Node* const native_context = LoadNativeContext(context);
#ifdef DEBUG
{
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const instance_size = LoadMapInstanceSize(map);
// Assert that the strict function map has an instance size is
// JSFunction::kSize
CSA_ASSERT(this, WordEqual(instance_size, IntPtrConstant(JSFunction::kSize /
kPointerSize)));
}
#endif
#ifdef DEBUG
{
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
Node* const map =
LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
Node* const instance_size = LoadMapInstanceSize(map);
// Assert that the JSPromise map has an instance size is
// JSPromise::kSize
CSA_ASSERT(this,
WordEqual(instance_size,
IntPtrConstant(JSPromise::kSizeWithEmbedderFields /
kPointerSize)));
}
#endif
static const int kWrappedPromiseOffset = FixedArray::SizeFor(context_length);
static const int kThrowawayPromiseOffset =
kWrappedPromiseOffset + JSPromise::kSizeWithEmbedderFields;
static const int kResolveClosureOffset =
kThrowawayPromiseOffset + JSPromise::kSizeWithEmbedderFields;
static const int kRejectClosureOffset =
kResolveClosureOffset + JSFunction::kSize;
static const int kTotalSize = kRejectClosureOffset + JSFunction::kSize;
Node* const base = AllocateInNewSpace(kTotalSize);
Node* const closure_context = base;
{
// Initialize closure context
InitializeFunctionContext(native_context, closure_context, context_length);
init_closure_context(closure_context);
}
// Let promiseCapability be ! NewPromiseCapability(%Promise%).
Node* const wrapped_value = AllocateAndInitJSPromise(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
Node* const promise_map =
LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
Node* const wrapped_value = InnerAllocate(base, kWrappedPromiseOffset);
{
// Initialize Promise
StoreMapNoWriteBarrier(wrapped_value, promise_map);
InitializeJSObjectFromMap(
wrapped_value, promise_map,
IntPtrConstant(JSPromise::kSizeWithEmbedderFields),
EmptyFixedArrayConstant(), EmptyFixedArrayConstant());
PromiseInit(wrapped_value);
}
Node* const throwaway = InnerAllocate(base, kThrowawayPromiseOffset);
{
// Initialize throwawayPromise
StoreMapNoWriteBarrier(throwaway, promise_map);
InitializeJSObjectFromMap(
throwaway, promise_map,
IntPtrConstant(JSPromise::kSizeWithEmbedderFields),
EmptyFixedArrayConstant(), EmptyFixedArrayConstant());
PromiseInit(throwaway);
}
Node* const on_resolve = InnerAllocate(base, kResolveClosureOffset);
{
// Initialize resolve handler
InitializeNativeClosure(closure_context, native_context, on_resolve,
on_resolve_context_index);
}
Node* const on_reject = InnerAllocate(base, kRejectClosureOffset);
{
// Initialize reject handler
InitializeNativeClosure(closure_context, native_context, on_reject,
on_reject_context_index);
}
{
// Add PromiseHooks if needed
Label next(this);
GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &next);
CallRuntime(Runtime::kPromiseHookInit, context, wrapped_value,
outer_promise);
CallRuntime(Runtime::kPromiseHookInit, context, throwaway, wrapped_value);
Goto(&next);
BIND(&next);
}
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
CallBuiltin(Builtins::kResolveNativePromise, context, wrapped_value, value);
Node* const native_context = LoadNativeContext(context);
Node* const closure_context = create_closure_context(native_context);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
// Load and allocate on_resolve closure
Node* const on_resolve_shared_fun =
LoadContextElement(native_context, on_resolve_context_index);
CSA_SLOW_ASSERT(
this, HasInstanceType(on_resolve_shared_fun, SHARED_FUNCTION_INFO_TYPE));
Node* const on_resolve = AllocateFunctionWithMapAndContext(
map, on_resolve_shared_fun, closure_context);
// Load and allocate on_reject closure
Node* const on_reject_shared_fun =
LoadContextElement(native_context, on_reject_context_index);
CSA_SLOW_ASSERT(
this, HasInstanceType(on_reject_shared_fun, SHARED_FUNCTION_INFO_TYPE));
Node* const on_reject = AllocateFunctionWithMapAndContext(
map, on_reject_shared_fun, closure_context);
Node* const throwaway_promise =
AllocateAndInitJSPromise(context, wrapped_value);
// The Promise will be thrown away and not handled, but it shouldn't trigger
// unhandled reject events as its work is done
PromiseSetHasHandler(throwaway_promise);
PromiseSetHasHandler(throwaway);
Label do_perform_promise_then(this);
GotoIfNot(IsDebugActive(), &do_perform_promise_then);
@ -82,18 +156,52 @@ Node* AsyncBuiltinsAssembler::Await(
CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
Node* const key = HeapConstant(factory()->promise_handled_by_symbol());
CallRuntime(Runtime::kSetProperty, context, throwaway_promise, key,
outer_promise, SmiConstant(STRICT));
CallRuntime(Runtime::kSetProperty, context, throwaway, key, outer_promise,
SmiConstant(STRICT));
}
Goto(&do_perform_promise_then);
BIND(&do_perform_promise_then);
CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapped_value,
on_resolve, on_reject, throwaway_promise);
on_resolve, on_reject, throwaway);
return wrapped_value;
}
void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,
Node* native_context,
Node* function,
int context_index) {
Node* const function_map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
StoreMapNoWriteBarrier(function, function_map);
StoreObjectFieldRoot(function, JSObject::kPropertiesOffset,
Heap::kEmptyFixedArrayRootIndex);
StoreObjectFieldRoot(function, JSObject::kElementsOffset,
Heap::kEmptyFixedArrayRootIndex);
StoreObjectFieldRoot(function, JSFunction::kFeedbackVectorOffset,
Heap::kUndefinedCellRootIndex);
StoreObjectFieldRoot(function, JSFunction::kPrototypeOrInitialMapOffset,
Heap::kTheHoleValueRootIndex);
Node* shared_info = LoadContextElement(native_context, context_index);
CSA_ASSERT(this, IsSharedFunctionInfo(shared_info));
StoreObjectFieldNoWriteBarrier(
function, JSFunction::kSharedFunctionInfoOffset, shared_info);
StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);
Node* const code = BitcastTaggedToWord(
LoadObjectField(shared_info, SharedFunctionInfo::kCodeOffset));
Node* const code_entry =
IntPtrAdd(code, IntPtrConstant(Code::kHeaderSize - kHeapObjectTag));
StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeEntryOffset,
code_entry,
MachineType::PointerRepresentation());
StoreObjectFieldRoot(function, JSFunction::kNextFunctionLinkOffset,
Heap::kUndefinedValueRootIndex);
}
Node* AsyncBuiltinsAssembler::CreateUnwrapClosure(Node* native_context,
Node* done) {
Node* const map = LoadContextElement(

View File

@ -16,7 +16,7 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
: PromiseBuiltinsAssembler(state) {}
protected:
typedef std::function<Node*(Node*)> NodeGenerator1;
typedef std::function<void(Node*)> ContextInitializer;
// Perform steps to resume generator after `value` is resolved.
// `on_reject_context_index` is an index into the Native Context, which should
@ -24,7 +24,8 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
// value following the reject index should be a similar value for the resolve
// closure. Returns the Promise-wrapped `value`.
Node* Await(Node* context, Node* generator, Node* value, Node* outer_promise,
const NodeGenerator1& create_closure_context,
int context_length,
const ContextInitializer& init_closure_context,
int on_resolve_context_index, int on_reject_context_index,
bool is_predicted_as_caught);
@ -33,6 +34,8 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
Node* CreateUnwrapClosure(Node* const native_context, Node* const done);
private:
void InitializeNativeClosure(Node* context, Node* native_context,
Node* function, int context_index);
Node* AllocateAsyncIteratorValueUnwrapContext(Node* native_context,
Node* done);
};

View File

@ -250,12 +250,9 @@ void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
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);
ContextInitializer init_closure_context = [&](Node* context) {
StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
generator);
return context;
};
Node* outer_promise =
@ -265,8 +262,8 @@ void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
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);
Await(context, generator, value, outer_promise, AwaitContext::kLength,
init_closure_context, resolve_index, reject_index, is_catchable);
CSA_SLOW_ASSERT(this, IsGeneratorNotSuspendedForAwait(generator));
StoreObjectField(generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset,

View File

@ -205,11 +205,10 @@ Node* PromiseBuiltinsAssembler::NewPromiseCapability(Node* context,
return var_result.value();
}
Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context,
int slots) {
void PromiseBuiltinsAssembler::InitializeFunctionContext(Node* native_context,
Node* context,
int slots) {
DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS);
Node* const context = AllocateInNewSpace(FixedArray::SizeFor(slots));
StoreMapNoWriteBarrier(context, Heap::kFunctionContextMapRootIndex);
StoreObjectFieldNoWriteBarrier(context, FixedArray::kLengthOffset,
SmiConstant(slots));
@ -223,6 +222,14 @@ Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context,
TheHoleConstant());
StoreContextElementNoWriteBarrier(context, Context::NATIVE_CONTEXT_INDEX,
native_context);
}
Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context,
int slots) {
DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS);
Node* const context = AllocateInNewSpace(FixedArray::SizeFor(slots));
InitializeFunctionContext(native_context, context, slots);
return context;
}

View File

@ -134,6 +134,7 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
void BranchIfFastPath(Node* native_context, Node* promise_fun, Node* promise,
Label* if_isunmodified, Label* if_ismodified);
void InitializeFunctionContext(Node* native_context, Node* context, int len);
Node* CreatePromiseContext(Node* native_context, int slots);
void PromiseFulfill(Node* context, Node* promise, Node* result,
v8::Promise::PromiseState status);

View File

@ -56,7 +56,8 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol };
V(Tuple2Map, Tuple2Map) \
V(Tuple3Map, Tuple3Map) \
V(UndefinedValue, Undefined) \
V(WeakCellMap, WeakCellMap)
V(WeakCellMap, WeakCellMap) \
V(SharedFunctionInfoMap, SharedFunctionInfoMap)
// Provides JavaScript-specific "macro-assembler" functionality on top of the
// CodeAssembler. By factoring the JavaScript-isms out of the CodeAssembler,
@ -814,6 +815,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsPrivateSymbol(Node* object);
Node* IsPropertyCell(Node* object);
Node* IsSequentialStringInstanceType(Node* instance_type);
inline Node* IsSharedFunctionInfo(Node* object) {
return IsSharedFunctionInfoMap(LoadMap(object));
}
Node* IsShortExternalStringInstanceType(Node* instance_type);
Node* IsSpecialReceiverInstanceType(Node* instance_type);
Node* IsSpecialReceiverMap(Node* map);