[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:
parent
5f0d82881c
commit
b57366f2e1
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user