[promises] Port ResolvePromise to TF
-- Moves promiseHasHandlerSymbol to inobject property -- Ports PromiseResolveClosure to TF -- Fix a non spec async-await test which fails now because we do a map check for native promise check (instead of IsPromise). Changing the constructor (in the test) invalidates the map check. This patch results in a 7.1% performance improvement in the bluebird benchmark (over 5 runs). BUG=v8:5343 Review-Url: https://codereview.chromium.org/2541283002 Cr-Commit-Position: refs/heads/master@{#41569}
This commit is contained in:
parent
21c9d278f6
commit
11359e331a
@ -7238,8 +7238,11 @@ bool Promise::HasHandler() {
|
||||
i::Isolate* isolate = promise->GetIsolate();
|
||||
LOG_API(isolate, Promise, HasRejectHandler);
|
||||
ENTER_V8(isolate);
|
||||
i::Handle<i::Symbol> key = isolate->factory()->promise_has_handler_symbol();
|
||||
return i::JSReceiver::GetDataProperty(promise, key)->IsTrue(isolate);
|
||||
if (promise->IsJSPromise()) {
|
||||
i::Handle<i::JSPromise> js_promise = i::Handle<i::JSPromise>::cast(promise);
|
||||
return js_promise->has_handler();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1855,9 +1855,25 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
prototype, factory->to_string_tag_symbol(), factory->Promise_string(),
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY));
|
||||
|
||||
SimpleInstallFunction(prototype, "then", Builtins::kPromiseThen, 2, true,
|
||||
Handle<JSFunction> promise_then =
|
||||
SimpleCreateFunction(isolate, isolate->factory()->then_string(),
|
||||
Builtins::kPromiseThen, 2, true);
|
||||
JSObject::AddProperty(prototype, isolate->factory()->then_string(),
|
||||
promise_then, DONT_ENUM);
|
||||
InstallWithIntrinsicDefaultProto(isolate, promise_then,
|
||||
Context::PROMISE_THEN_INDEX);
|
||||
|
||||
// TODO(gsathya): Move to TF
|
||||
SimpleInstallFunction(prototype, "catch", Builtins::kIllegal, 1, true,
|
||||
DONT_ENUM);
|
||||
|
||||
Handle<Map> prototype_map(prototype->map());
|
||||
Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate);
|
||||
|
||||
// Store the initial Promise.prototype map. This is used in fast-path
|
||||
// checks. Do not alter the prototype after this point.
|
||||
native_context()->set_promise_prototype_map(*prototype_map);
|
||||
|
||||
{ // Internal: PromiseInternalConstructor
|
||||
Handle<JSFunction> function =
|
||||
SimpleCreateFunction(isolate, factory->empty_string(),
|
||||
@ -1889,6 +1905,14 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
Context::PERFORM_PROMISE_THEN_INDEX);
|
||||
}
|
||||
|
||||
{ // Internal: ResolvePromise
|
||||
Handle<JSFunction> function =
|
||||
SimpleCreateFunction(isolate, factory->empty_string(),
|
||||
Builtins::kResolvePromise, 2, false);
|
||||
InstallWithIntrinsicDefaultProto(isolate, function,
|
||||
Context::PROMISE_RESOLVE_INDEX);
|
||||
}
|
||||
|
||||
{
|
||||
Handle<Code> code =
|
||||
handle(isolate->builtins()->builtin(Builtins::kPromiseResolveClosure),
|
||||
@ -3523,6 +3547,9 @@ bool Genesis::InstallNatives(GlobalContextType context_type) {
|
||||
|
||||
InstallInternalArray(extras_utils, "InternalPackedArray", FAST_ELEMENTS);
|
||||
|
||||
InstallFunction(extras_utils, isolate()->promise_resolve(),
|
||||
factory()->NewStringFromAsciiChecked("resolvePromise"));
|
||||
|
||||
int builtin_index = Natives::GetDebuggerCount();
|
||||
// Only run prologue.js and runtime.js at this point.
|
||||
DCHECK_EQ(builtin_index, Natives::GetIndex("prologue"));
|
||||
|
@ -11,30 +11,6 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// ES#sec-promise-resolve-functions
|
||||
// Promise Resolve Functions
|
||||
BUILTIN(PromiseResolveClosure) {
|
||||
HandleScope scope(isolate);
|
||||
|
||||
Handle<Context> context(isolate->context(), isolate);
|
||||
|
||||
if (PromiseUtils::HasAlreadyVisited(context)) {
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
PromiseUtils::SetAlreadyVisited(context);
|
||||
Handle<JSObject> promise = handle(PromiseUtils::GetPromise(context), isolate);
|
||||
Handle<Object> value = args.atOrUndefined(isolate, 1);
|
||||
|
||||
MaybeHandle<Object> maybe_result;
|
||||
Handle<Object> argv[] = {promise, value};
|
||||
RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, Execution::Call(isolate, isolate->promise_resolve(),
|
||||
isolate->factory()->undefined_value(),
|
||||
arraysize(argv), argv));
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
// ES#sec-promise-reject-functions
|
||||
// Promise Reject Functions
|
||||
BUILTIN(PromiseRejectClosure) {
|
||||
@ -86,6 +62,7 @@ void PromiseInit(CodeStubAssembler* a, compiler::Node* promise,
|
||||
CSA_ASSERT(a, a->TaggedIsSmi(status));
|
||||
a->StoreObjectField(promise, JSPromise::kStatusOffset, status);
|
||||
a->StoreObjectField(promise, JSPromise::kResultOffset, result);
|
||||
a->StoreObjectField(promise, JSPromise::kFlagsOffset, a->SmiConstant(0));
|
||||
}
|
||||
|
||||
void Builtins::Generate_PromiseConstructor(
|
||||
@ -286,6 +263,7 @@ compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate,
|
||||
a->Bind(&out);
|
||||
return var_value_map.value();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Builtins::Generate_IsPromise(compiler::CodeAssemblerState* state) {
|
||||
@ -307,6 +285,24 @@ void Builtins::Generate_IsPromise(compiler::CodeAssemblerState* state) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
compiler::Node* PromiseHasHandler(CodeStubAssembler* a,
|
||||
compiler::Node* promise) {
|
||||
typedef compiler::Node Node;
|
||||
|
||||
Node* const flags = a->LoadObjectField(promise, JSPromise::kFlagsOffset);
|
||||
return a->IsSetWord(a->SmiUntag(flags), 1 << JSPromise::kHasHandlerBit);
|
||||
}
|
||||
|
||||
void PromiseSetHasHandler(CodeStubAssembler* a, compiler::Node* promise) {
|
||||
typedef compiler::Node Node;
|
||||
|
||||
Node* const flags = a->LoadObjectField(promise, JSPromise::kFlagsOffset);
|
||||
Node* const new_flags =
|
||||
a->WordOr(flags, a->IntPtrConstant(1 << JSPromise::kHasHandlerBit));
|
||||
a->StoreObjectField(promise, JSPromise::kFlagsOffset, a->SmiTag(new_flags));
|
||||
}
|
||||
|
||||
compiler::Node* SpeciesConstructor(CodeStubAssembler* a, Isolate* isolate,
|
||||
compiler::Node* context,
|
||||
compiler::Node* object,
|
||||
@ -404,7 +400,6 @@ compiler::Node* InternalPerformPromiseThen(CodeStubAssembler* a,
|
||||
typedef CodeStubAssembler::Variable Variable;
|
||||
typedef CodeStubAssembler::Label Label;
|
||||
typedef compiler::Node Node;
|
||||
|
||||
Isolate* isolate = a->isolate();
|
||||
Node* const native_context = a->LoadNativeContext(context);
|
||||
|
||||
@ -536,16 +531,11 @@ compiler::Node* InternalPerformPromiseThen(CodeStubAssembler* a,
|
||||
|
||||
a->Bind(&reject);
|
||||
{
|
||||
Callable getproperty_callable = CodeFactory::GetProperty(isolate);
|
||||
Node* const key =
|
||||
a->HeapConstant(isolate->factory()->promise_has_handler_symbol());
|
||||
Node* const has_handler =
|
||||
a->CallStub(getproperty_callable, context, promise, key);
|
||||
|
||||
Node* const has_handler = PromiseHasHandler(a, promise);
|
||||
Label enqueue(a);
|
||||
|
||||
// TODO(gsathya): Fold these runtime calls and move to TF.
|
||||
a->GotoIf(a->WordEqual(has_handler, a->TrueConstant()), &enqueue);
|
||||
a->GotoIf(has_handler, &enqueue);
|
||||
a->CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
|
||||
a->Goto(&enqueue);
|
||||
|
||||
@ -562,11 +552,7 @@ compiler::Node* InternalPerformPromiseThen(CodeStubAssembler* a,
|
||||
}
|
||||
|
||||
a->Bind(&out);
|
||||
// TODO(gsathya): Protect with debug check.
|
||||
a->CallRuntime(
|
||||
Runtime::kSetProperty, context, promise,
|
||||
a->HeapConstant(isolate->factory()->promise_has_handler_symbol()),
|
||||
a->TrueConstant(), a->SmiConstant(STRICT));
|
||||
PromiseSetHasHandler(a, promise);
|
||||
|
||||
// TODO(gsathya): This call will be removed once we don't have to
|
||||
// deal with deferred objects.
|
||||
@ -667,5 +653,244 @@ void Builtins::Generate_PromiseThen(compiler::CodeAssemblerState* state) {
|
||||
a.Return(result);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Promise fast path implementations rely on unmodified JSPromise instances.
|
||||
// We use a fairly coarse granularity for this and simply check whether both
|
||||
// the promise itself is unmodified (i.e. its map has not changed) and its
|
||||
// prototype is unmodified.
|
||||
// TODO(gsathya): Refactor this out to prevent code dupe with builtins-regexp
|
||||
void BranchIfFastPath(CodeStubAssembler* a, compiler::Node* context,
|
||||
compiler::Node* promise,
|
||||
CodeStubAssembler::Label* if_isunmodified,
|
||||
CodeStubAssembler::Label* if_ismodified) {
|
||||
typedef compiler::Node Node;
|
||||
|
||||
// TODO(gsathya): Assert if promise is receiver
|
||||
Node* const map = a->LoadMap(promise);
|
||||
Node* const native_context = a->LoadNativeContext(context);
|
||||
Node* const promise_fun =
|
||||
a->LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
|
||||
Node* const initial_map =
|
||||
a->LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
||||
Node* const has_initialmap = a->WordEqual(map, initial_map);
|
||||
|
||||
a->GotoUnless(has_initialmap, if_ismodified);
|
||||
|
||||
Node* const initial_proto_initial_map = a->LoadContextElement(
|
||||
native_context, Context::PROMISE_PROTOTYPE_MAP_INDEX);
|
||||
Node* const proto_map = a->LoadMap(a->LoadMapPrototype(map));
|
||||
Node* const proto_has_initialmap =
|
||||
a->WordEqual(proto_map, initial_proto_initial_map);
|
||||
|
||||
a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
|
||||
}
|
||||
|
||||
void InternalResolvePromise(CodeStubAssembler* a, compiler::Node* context,
|
||||
compiler::Node* promise, compiler::Node* result,
|
||||
CodeStubAssembler::Label* out) {
|
||||
typedef CodeStubAssembler::Variable Variable;
|
||||
typedef CodeStubAssembler::Label Label;
|
||||
typedef compiler::Node Node;
|
||||
|
||||
Isolate* isolate = a->isolate();
|
||||
|
||||
Variable var_reason(a, MachineRepresentation::kTagged),
|
||||
var_then(a, MachineRepresentation::kTagged);
|
||||
|
||||
Label do_enqueue(a), fulfill(a), if_cycle(a, Label::kDeferred),
|
||||
if_rejectpromise(a, Label::kDeferred);
|
||||
|
||||
// 6. If SameValue(resolution, promise) is true, then
|
||||
a->GotoIf(a->SameValue(promise, result, context), &if_cycle);
|
||||
|
||||
// 7. If Type(resolution) is not Object, then
|
||||
a->GotoIf(a->TaggedIsSmi(result), &fulfill);
|
||||
a->GotoUnless(a->IsJSReceiver(result), &fulfill);
|
||||
|
||||
Label if_nativepromise(a), if_notnativepromise(a, Label::kDeferred);
|
||||
BranchIfFastPath(a, context, result, &if_nativepromise, &if_notnativepromise);
|
||||
|
||||
// Resolution is a native promise and if it's already resolved or
|
||||
// rejected, shortcircuit the resolution procedure by directly
|
||||
// reusing the value from the promise.
|
||||
a->Bind(&if_nativepromise);
|
||||
{
|
||||
Node* const thenable_status =
|
||||
a->LoadObjectField(result, JSPromise::kStatusOffset);
|
||||
Node* const thenable_value =
|
||||
a->LoadObjectField(result, JSPromise::kResultOffset);
|
||||
|
||||
Label if_isnotpending(a);
|
||||
a->GotoUnless(a->SmiEqual(a->SmiConstant(kPromisePending), thenable_status),
|
||||
&if_isnotpending);
|
||||
|
||||
// TODO(gsathya): Use a marker here instead of the actual then
|
||||
// callback, and check for the marker in PromiseResolveThenableJob
|
||||
// and perform PromiseThen.
|
||||
Node* const native_context = a->LoadNativeContext(context);
|
||||
Node* const then =
|
||||
a->LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
|
||||
var_then.Bind(then);
|
||||
a->Goto(&do_enqueue);
|
||||
|
||||
a->Bind(&if_isnotpending);
|
||||
{
|
||||
Label if_fulfilled(a), if_rejected(a);
|
||||
a->Branch(a->SmiEqual(a->SmiConstant(kPromiseFulfilled), thenable_status),
|
||||
&if_fulfilled, &if_rejected);
|
||||
|
||||
a->Bind(&if_fulfilled);
|
||||
{
|
||||
a->CallRuntime(Runtime::kPromiseFulfill, context, promise,
|
||||
a->SmiConstant(kPromiseFulfilled), thenable_value);
|
||||
PromiseSetHasHandler(a, promise);
|
||||
a->Goto(out);
|
||||
}
|
||||
|
||||
a->Bind(&if_rejected);
|
||||
{
|
||||
Label reject(a);
|
||||
Node* const has_handler = PromiseHasHandler(a, result);
|
||||
|
||||
// Promise has already been rejected, but had no handler.
|
||||
// Revoke previously triggered reject event.
|
||||
a->GotoIf(has_handler, &reject);
|
||||
a->CallRuntime(Runtime::kPromiseRevokeReject, context, result);
|
||||
a->Goto(&reject);
|
||||
|
||||
a->Bind(&reject);
|
||||
// Don't cause a debug event as this case is forwarding a rejection
|
||||
a->CallRuntime(Runtime::kPromiseReject, context, promise,
|
||||
thenable_value, a->FalseConstant());
|
||||
PromiseSetHasHandler(a, result);
|
||||
a->Goto(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a->Bind(&if_notnativepromise);
|
||||
{
|
||||
// 8. Let then be Get(resolution, "then").
|
||||
Node* const then_str = a->HeapConstant(isolate->factory()->then_string());
|
||||
Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
|
||||
Node* const then =
|
||||
a->CallStub(getproperty_callable, context, result, then_str);
|
||||
|
||||
// 9. If then is an abrupt completion, then
|
||||
a->GotoIfException(then, &if_rejectpromise, &var_reason);
|
||||
|
||||
// 11. If IsCallable(thenAction) is false, then
|
||||
a->GotoIf(a->TaggedIsSmi(then), &fulfill);
|
||||
Node* const then_map = a->LoadMap(then);
|
||||
a->GotoUnless(a->IsCallableMap(then_map), &fulfill);
|
||||
var_then.Bind(then);
|
||||
a->Goto(&do_enqueue);
|
||||
}
|
||||
|
||||
a->Bind(&do_enqueue);
|
||||
{
|
||||
Label enqueue(a);
|
||||
a->GotoUnless(a->IsDebugActive(), &enqueue);
|
||||
a->GotoIf(a->TaggedIsSmi(result), &enqueue);
|
||||
a->GotoUnless(a->HasInstanceType(result, JS_PROMISE_TYPE), &enqueue);
|
||||
// Mark the dependency of the new promise on the resolution
|
||||
Node* const key =
|
||||
a->HeapConstant(isolate->factory()->promise_handled_by_symbol());
|
||||
a->CallRuntime(Runtime::kSetProperty, context, result, key, promise,
|
||||
a->SmiConstant(STRICT));
|
||||
a->Goto(&enqueue);
|
||||
|
||||
// 12. Perform EnqueueJob("PromiseJobs",
|
||||
// PromiseResolveThenableJob, « promise, resolution, thenAction
|
||||
// »).
|
||||
a->Bind(&enqueue);
|
||||
a->CallRuntime(Runtime::kEnqueuePromiseResolveThenableJob, context, promise,
|
||||
result, var_then.value());
|
||||
a->Goto(out);
|
||||
}
|
||||
// 7.b Return FulfillPromise(promise, resolution).
|
||||
a->Bind(&fulfill);
|
||||
{
|
||||
a->CallRuntime(Runtime::kPromiseFulfill, context, promise,
|
||||
a->SmiConstant(kPromiseFulfilled), result);
|
||||
a->Goto(out);
|
||||
}
|
||||
|
||||
a->Bind(&if_cycle);
|
||||
{
|
||||
// 6.a Let selfResolutionError be a newly created TypeError object.
|
||||
Node* const message_id = a->SmiConstant(MessageTemplate::kPromiseCyclic);
|
||||
Node* const error =
|
||||
a->CallRuntime(Runtime::kNewTypeError, context, message_id, result);
|
||||
var_reason.Bind(error);
|
||||
|
||||
// 6.b Return RejectPromise(promise, selfResolutionError).
|
||||
a->Goto(&if_rejectpromise);
|
||||
}
|
||||
|
||||
// 9.a Return RejectPromise(promise, then.[[Value]]).
|
||||
a->Bind(&if_rejectpromise);
|
||||
{
|
||||
a->CallRuntime(Runtime::kPromiseReject, context, promise,
|
||||
var_reason.value(), a->TrueConstant());
|
||||
a->Goto(out);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ES#sec-promise-resolve-functions
|
||||
// Promise Resolve Functions
|
||||
void Builtins::Generate_PromiseResolveClosure(
|
||||
compiler::CodeAssemblerState* state) {
|
||||
CodeStubAssembler a(state);
|
||||
typedef compiler::Node Node;
|
||||
typedef CodeStubAssembler::Label Label;
|
||||
|
||||
Node* const value = a.Parameter(1);
|
||||
Node* const context = a.Parameter(4);
|
||||
|
||||
Label out(&a);
|
||||
|
||||
// 3. Let alreadyResolved be F.[[AlreadyResolved]].
|
||||
Node* const has_already_visited_slot =
|
||||
a.IntPtrConstant(PromiseUtils::kAlreadyVisitedSlot);
|
||||
|
||||
Node* const has_already_visited =
|
||||
a.LoadFixedArrayElement(context, has_already_visited_slot);
|
||||
|
||||
// 4. If alreadyResolved.[[Value]] is true, return undefined.
|
||||
a.GotoIf(a.SmiEqual(has_already_visited, a.SmiConstant(1)), &out);
|
||||
|
||||
// 5.Set alreadyResolved.[[Value]] to true.
|
||||
a.StoreFixedArrayElement(context, has_already_visited_slot, a.SmiConstant(1));
|
||||
|
||||
// 2. Let promise be F.[[Promise]].
|
||||
Node* const promise = a.LoadFixedArrayElement(
|
||||
context, a.IntPtrConstant(PromiseUtils::kPromiseSlot));
|
||||
|
||||
InternalResolvePromise(&a, context, promise, value, &out);
|
||||
|
||||
a.Bind(&out);
|
||||
a.Return(a.UndefinedConstant());
|
||||
}
|
||||
|
||||
void Builtins::Generate_ResolvePromise(compiler::CodeAssemblerState* state) {
|
||||
CodeStubAssembler a(state);
|
||||
typedef compiler::Node Node;
|
||||
typedef CodeStubAssembler::Label Label;
|
||||
|
||||
Node* const promise = a.Parameter(1);
|
||||
Node* const result = a.Parameter(2);
|
||||
Node* const context = a.Parameter(5);
|
||||
|
||||
Label out(&a);
|
||||
InternalResolvePromise(&a, context, promise, result, &out);
|
||||
|
||||
a.Bind(&out);
|
||||
a.Return(a.UndefinedConstant());
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -565,11 +565,12 @@ namespace internal {
|
||||
TFJ(PromiseInternalConstructor, 0) \
|
||||
TFJ(IsPromise, 1) \
|
||||
CPP(CreateResolvingFunctions) \
|
||||
CPP(PromiseResolveClosure) \
|
||||
TFJ(PromiseResolveClosure, 1) \
|
||||
CPP(PromiseRejectClosure) \
|
||||
TFJ(PromiseThen, 2) \
|
||||
TFJ(PromiseCreateAndSet, 2) \
|
||||
TFJ(PerformPromiseThen, 4) \
|
||||
TFJ(ResolvePromise, 2) \
|
||||
\
|
||||
/* Proxy */ \
|
||||
CPP(ProxyConstructor) \
|
||||
|
@ -65,7 +65,9 @@ enum ContextLookupFlags {
|
||||
promise_internal_constructor) \
|
||||
V(IS_PROMISE_INDEX, JSFunction, is_promise) \
|
||||
V(PERFORM_PROMISE_THEN_INDEX, JSFunction, perform_promise_then) \
|
||||
V(PROMISE_CREATE_AND_SET_INDEX, JSFunction, promise_create_and_set)
|
||||
V(PROMISE_CREATE_AND_SET_INDEX, JSFunction, promise_create_and_set) \
|
||||
V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \
|
||||
V(PROMISE_THEN_INDEX, JSFunction, promise_then)
|
||||
|
||||
#define NATIVE_CONTEXT_IMPORTED_FIELDS(V) \
|
||||
V(ARRAY_CONCAT_INDEX, JSFunction, array_concat) \
|
||||
@ -105,10 +107,8 @@ enum ContextLookupFlags {
|
||||
V(PROMISE_DEBUG_GET_INFO_INDEX, JSFunction, promise_debug_get_info) \
|
||||
V(PROMISE_REJECT_INDEX, JSFunction, promise_reject) \
|
||||
V(PROMISE_INTERNAL_REJECT_INDEX, JSFunction, promise_internal_reject) \
|
||||
V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \
|
||||
V(PROMISE_ID_RESOLVE_HANDLER_INDEX, JSFunction, promise_id_resolve_handler) \
|
||||
V(PROMISE_ID_REJECT_HANDLER_INDEX, JSFunction, promise_id_reject_handler) \
|
||||
V(PROMISE_THEN_INDEX, JSFunction, promise_then) \
|
||||
V(NEW_PROMISE_CAPABILITY_INDEX, JSFunction, new_promise_capability) \
|
||||
V(INTERNAL_PROMISE_CAPABILITY_INDEX, JSFunction, \
|
||||
internal_promise_capability) \
|
||||
@ -291,6 +291,7 @@ enum ContextLookupFlags {
|
||||
V(PROMISE_RESOLVE_SHARED_FUN, SharedFunctionInfo, \
|
||||
promise_resolve_shared_fun) \
|
||||
V(PROMISE_REJECT_SHARED_FUN, SharedFunctionInfo, promise_reject_shared_fun) \
|
||||
V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \
|
||||
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
|
||||
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
|
||||
V(REGEXP_LAST_MATCH_INFO_INDEX, RegExpMatchInfo, regexp_last_match_info) \
|
||||
|
@ -150,6 +150,7 @@
|
||||
V(symbol_string, "symbol") \
|
||||
V(Symbol_string, "Symbol") \
|
||||
V(SyntaxError_string, "SyntaxError") \
|
||||
V(then_string, "then") \
|
||||
V(this_string, "this") \
|
||||
V(throw_string, "throw") \
|
||||
V(timed_out, "timed-out") \
|
||||
@ -213,7 +214,6 @@
|
||||
V(promise_forwarding_handler_symbol) \
|
||||
V(promise_handled_by_symbol) \
|
||||
V(promise_handled_hint_symbol) \
|
||||
V(promise_has_handler_symbol) \
|
||||
V(sealed_symbol) \
|
||||
V(stack_trace_symbol) \
|
||||
V(strict_function_transition_symbol) \
|
||||
|
@ -17,7 +17,6 @@ var CreateInternalPromiseCapability;
|
||||
var PromiseCreate;
|
||||
var PromiseNextMicrotaskID;
|
||||
var RejectPromise;
|
||||
var ResolvePromise;
|
||||
|
||||
utils.Import(function(from) {
|
||||
AsyncFunctionNext = from.AsyncFunctionNext;
|
||||
@ -25,7 +24,6 @@ utils.Import(function(from) {
|
||||
CreateInternalPromiseCapability = from.CreateInternalPromiseCapability;
|
||||
PromiseCreate = from.PromiseCreate;
|
||||
RejectPromise = from.RejectPromise;
|
||||
ResolvePromise = from.ResolvePromise;
|
||||
});
|
||||
|
||||
var promiseAsyncStackIDSymbol =
|
||||
@ -36,8 +34,6 @@ var promiseForwardingHandlerSymbol =
|
||||
utils.ImportNow("promise_forwarding_handler_symbol");
|
||||
var promiseHandledHintSymbol =
|
||||
utils.ImportNow("promise_handled_hint_symbol");
|
||||
var promiseHasHandlerSymbol =
|
||||
utils.ImportNow("promise_has_handler_symbol");
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@ -47,7 +43,7 @@ function PromiseCastResolved(value) {
|
||||
return value;
|
||||
} else {
|
||||
var promise = PromiseCreate();
|
||||
ResolvePromise(promise, value);
|
||||
%promise_resolve(promise, value);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@ -90,7 +86,7 @@ function AsyncFunctionAwait(generator, awaited, outerPromise) {
|
||||
|
||||
// The Promise will be thrown away and not handled, but it shouldn't trigger
|
||||
// unhandled reject events as its work is done
|
||||
SET_PRIVATE(throwawayCapability.promise, promiseHasHandlerSymbol, true);
|
||||
%PromiseMarkAsHandled(throwawayCapability.promise);
|
||||
|
||||
if (DEBUG_IS_ACTIVE) {
|
||||
if (%is_promise(awaited)) {
|
||||
|
@ -18,8 +18,6 @@ var promiseHandledBySymbol =
|
||||
utils.ImportNow("promise_handled_by_symbol");
|
||||
var promiseForwardingHandlerSymbol =
|
||||
utils.ImportNow("promise_forwarding_handler_symbol");
|
||||
var promiseHasHandlerSymbol =
|
||||
utils.ImportNow("promise_has_handler_symbol");
|
||||
var promiseHandledHintSymbol =
|
||||
utils.ImportNow("promise_handled_hint_symbol");
|
||||
var promiseRawSymbol = utils.ImportNow("promise_raw_symbol");
|
||||
@ -47,7 +45,7 @@ function PromiseHandle(value, handler, deferred) {
|
||||
if (debug_is_active) %DebugPushPromise(deferred.promise);
|
||||
var result = handler(value);
|
||||
if (IS_UNDEFINED(deferred.resolve)) {
|
||||
ResolvePromise(deferred.promise, result);
|
||||
%promise_resolve(deferred.promise, result);
|
||||
} else {
|
||||
%_Call(deferred.resolve, UNDEFINED, result);
|
||||
}
|
||||
@ -106,61 +104,6 @@ function PromiseCreate() {
|
||||
return %promise_internal_constructor();
|
||||
}
|
||||
|
||||
// ES#sec-promise-resolve-functions
|
||||
// Promise Resolve Functions, steps 6-13
|
||||
function ResolvePromise(promise, resolution) {
|
||||
if (resolution === promise) {
|
||||
var exception = %make_type_error(kPromiseCyclic, resolution);
|
||||
%PromiseReject(promise, exception, true);
|
||||
return;
|
||||
}
|
||||
if (IS_RECEIVER(resolution)) {
|
||||
// 25.4.1.3.2 steps 8-12
|
||||
try {
|
||||
var then = resolution.then;
|
||||
} catch (e) {
|
||||
%PromiseReject(promise, e, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolution is a native promise and if it's already resolved or
|
||||
// rejected, shortcircuit the resolution procedure by directly
|
||||
// reusing the value from the promise.
|
||||
if (%is_promise(resolution) && then === PromiseThen) {
|
||||
var thenableState = %PromiseStatus(resolution);
|
||||
if (thenableState === kFulfilled) {
|
||||
// This goes inside the if-else to save one symbol lookup in
|
||||
// the slow path.
|
||||
var thenableValue = %PromiseResult(resolution);
|
||||
%PromiseFulfill(promise, kFulfilled, thenableValue);
|
||||
SET_PRIVATE(promise, promiseHasHandlerSymbol, true);
|
||||
return;
|
||||
} else if (thenableState === kRejected) {
|
||||
var thenableValue = %PromiseResult(resolution);
|
||||
if (!HAS_DEFINED_PRIVATE(resolution, promiseHasHandlerSymbol)) {
|
||||
// Promise has already been rejected, but had no handler.
|
||||
// Revoke previously triggered reject event.
|
||||
%PromiseRevokeReject(resolution);
|
||||
}
|
||||
// Don't cause a debug event as this case is forwarding a rejection
|
||||
%PromiseReject(promise, thenableValue, false);
|
||||
SET_PRIVATE(resolution, promiseHasHandlerSymbol, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_CALLABLE(then)) {
|
||||
if (DEBUG_IS_ACTIVE && %is_promise(resolution)) {
|
||||
// Mark the dependency of the new promise on the resolution
|
||||
SET_PRIVATE(resolution, promiseHandledBySymbol, promise);
|
||||
}
|
||||
%EnqueuePromiseResolveThenableJob(promise, resolution, then);
|
||||
return;
|
||||
}
|
||||
}
|
||||
%PromiseFulfill(promise, kFulfilled, resolution);
|
||||
}
|
||||
|
||||
// Only used by async-await.js
|
||||
function RejectPromise(promise, reason, debugEvent) {
|
||||
%PromiseReject(promise, reason, debugEvent);
|
||||
@ -251,7 +194,7 @@ function PromiseResolve(x) {
|
||||
// Avoid creating resolving functions.
|
||||
if (this === GlobalPromise) {
|
||||
var promise = %promise_internal_constructor();
|
||||
ResolvePromise(promise, x);
|
||||
%promise_resolve(promise, x);
|
||||
return promise;
|
||||
}
|
||||
|
||||
@ -422,7 +365,7 @@ function PromiseHasUserDefinedRejectHandler() {
|
||||
};
|
||||
|
||||
function MarkPromiseAsHandled(promise) {
|
||||
SET_PRIVATE(promise, promiseHasHandlerSymbol, true);
|
||||
%PromiseMarkAsHandled(promise);
|
||||
}
|
||||
|
||||
|
||||
@ -442,9 +385,7 @@ utils.InstallFunctions(GlobalPromise, DONT_ENUM, [
|
||||
|
||||
utils.InstallGetter(GlobalPromise, speciesSymbol, PromiseSpecies);
|
||||
|
||||
utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [
|
||||
"catch", PromiseCatch
|
||||
]);
|
||||
%SetCode(GlobalPromise.prototype.catch, PromiseCatch);
|
||||
|
||||
%InstallToContext([
|
||||
"promise_catch", PromiseCatch,
|
||||
@ -453,8 +394,6 @@ utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [
|
||||
"promise_reject", DoRejectPromise,
|
||||
// TODO(gsathya): Remove this once we update the promise builtin.
|
||||
"promise_internal_reject", RejectPromise,
|
||||
"promise_resolve", ResolvePromise,
|
||||
"promise_then", PromiseThen,
|
||||
"promise_handle", PromiseHandle,
|
||||
"promise_debug_get_info", PromiseDebugGetInfo,
|
||||
"new_promise_capability", NewPromiseCapability,
|
||||
@ -468,7 +407,6 @@ utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [
|
||||
// promise without having to hold on to those closures forever.
|
||||
utils.InstallFunctions(extrasUtils, 0, [
|
||||
"createPromise", PromiseCreate,
|
||||
"resolvePromise", ResolvePromise,
|
||||
"rejectPromise", DoRejectPromise,
|
||||
"markPromiseAsHandled", MarkPromiseAsHandled
|
||||
]);
|
||||
@ -478,7 +416,6 @@ utils.Export(function(to) {
|
||||
to.PromiseThen = PromiseThen;
|
||||
|
||||
to.CreateInternalPromiseCapability = CreateInternalPromiseCapability;
|
||||
to.ResolvePromise = ResolvePromise;
|
||||
to.RejectPromise = RejectPromise;
|
||||
});
|
||||
|
||||
|
@ -7100,6 +7100,8 @@ ACCESSORS(JSPromise, result, Object, kResultOffset)
|
||||
ACCESSORS(JSPromise, deferred, Object, kDeferredOffset)
|
||||
ACCESSORS(JSPromise, fulfill_reactions, Object, kFulfillReactionsOffset)
|
||||
ACCESSORS(JSPromise, reject_reactions, Object, kRejectReactionsOffset)
|
||||
SMI_ACCESSORS(JSPromise, flags, kFlagsOffset)
|
||||
BOOL_ACCESSORS(JSPromise, flags, has_handler, kHasHandlerBit)
|
||||
|
||||
ACCESSORS(JSRegExp, data, Object, kDataOffset)
|
||||
ACCESSORS(JSRegExp, flags, Object, kFlagsOffset)
|
||||
|
@ -551,6 +551,7 @@ void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT
|
||||
os << "\n - deferreds = " << Brief(deferred());
|
||||
os << "\n - fulfill_reactions = " << Brief(fulfill_reactions());
|
||||
os << "\n - reject_reactions = " << Brief(reject_reactions());
|
||||
os << "\n - has_handler = " << has_handler();
|
||||
}
|
||||
|
||||
void JSRegExp::JSRegExpPrint(std::ostream& os) { // NOLINT
|
||||
|
@ -8910,6 +8910,11 @@ class JSPromise : public JSObject {
|
||||
DECL_ACCESSORS(fulfill_reactions, Object)
|
||||
DECL_ACCESSORS(reject_reactions, Object)
|
||||
|
||||
DECL_INT_ACCESSORS(flags)
|
||||
|
||||
// [has_handler]: Whether this promise has a reject handler or not.
|
||||
DECL_BOOLEAN_ACCESSORS(has_handler)
|
||||
|
||||
static const char* Status(int status);
|
||||
|
||||
DECLARE_CAST(JSPromise)
|
||||
@ -8925,7 +8930,11 @@ class JSPromise : public JSObject {
|
||||
static const int kFulfillReactionsOffset = kDeferredOffset + kPointerSize;
|
||||
static const int kRejectReactionsOffset =
|
||||
kFulfillReactionsOffset + kPointerSize;
|
||||
static const int kSize = kRejectReactionsOffset + kPointerSize;
|
||||
static const int kFlagsOffset = kRejectReactionsOffset + kPointerSize;
|
||||
static const int kSize = kFlagsOffset + kPointerSize;
|
||||
|
||||
// Flags layout.
|
||||
static const int kHasHandlerBit = 0;
|
||||
};
|
||||
|
||||
// Regular expressions
|
||||
|
@ -11,13 +11,6 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
enum PromiseResolvingFunctionContextSlot {
|
||||
kAlreadyVisitedSlot = Context::MIN_CONTEXT_SLOTS,
|
||||
kPromiseSlot,
|
||||
kDebugEventSlot,
|
||||
kPromiseContextLength,
|
||||
};
|
||||
|
||||
JSObject* PromiseUtils::GetPromise(Handle<Context> context) {
|
||||
return JSObject::cast(context->get(kPromiseSlot));
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef V8_PROMISE_UTILS_H_
|
||||
#define V8_PROMISE_UTILS_H_
|
||||
|
||||
#include "src/contexts.h"
|
||||
#include "src/objects.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -13,6 +14,19 @@ namespace internal {
|
||||
// Helper methods for Promise builtins.
|
||||
class PromiseUtils : public AllStatic {
|
||||
public:
|
||||
enum PromiseResolvingFunctionContextSlot {
|
||||
// Whether the resolve/reject callback was already called.
|
||||
kAlreadyVisitedSlot = Context::MIN_CONTEXT_SLOTS,
|
||||
|
||||
// The promise which resolve/reject callbacks fulfill.
|
||||
kPromiseSlot,
|
||||
|
||||
// Whether to trigger a debug event or not. Used in catch
|
||||
// prediction.
|
||||
kDebugEventSlot,
|
||||
kPromiseContextLength,
|
||||
};
|
||||
|
||||
// These get and set the slots on the PromiseResolvingContext, which
|
||||
// is used by the resolve/reject promise callbacks.
|
||||
static JSObject* GetPromise(Handle<Context> context);
|
||||
|
@ -12,15 +12,15 @@ namespace internal {
|
||||
|
||||
namespace {
|
||||
|
||||
void PromiseRejectEvent(Isolate* isolate, Handle<JSReceiver> promise,
|
||||
void PromiseRejectEvent(Isolate* isolate, Handle<JSPromise> promise,
|
||||
Handle<Object> rejected_promise, Handle<Object> value,
|
||||
bool debug_event) {
|
||||
if (isolate->debug()->is_active() && debug_event) {
|
||||
isolate->debug()->OnPromiseReject(rejected_promise, value);
|
||||
}
|
||||
Handle<Symbol> key = isolate->factory()->promise_has_handler_symbol();
|
||||
// Do not report if we actually have a handler.
|
||||
if (JSReceiver::GetDataProperty(promise, key)->IsUndefined(isolate)) {
|
||||
|
||||
// Report only if we don't actually have a handler.
|
||||
if (!promise->has_handler()) {
|
||||
isolate->ReportPromiseReject(Handle<JSObject>::cast(promise), value,
|
||||
v8::kPromiseRejectWithNoHandler);
|
||||
}
|
||||
@ -31,7 +31,7 @@ void PromiseRejectEvent(Isolate* isolate, Handle<JSReceiver> promise,
|
||||
RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) {
|
||||
DCHECK(args.length() == 2);
|
||||
HandleScope scope(isolate);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
|
||||
|
||||
Handle<Object> rejected_promise = promise;
|
||||
@ -48,10 +48,9 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) {
|
||||
RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) {
|
||||
DCHECK(args.length() == 1);
|
||||
HandleScope scope(isolate);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
|
||||
Handle<Symbol> key = isolate->factory()->promise_has_handler_symbol();
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
|
||||
// At this point, no revocation has been issued before
|
||||
CHECK(JSReceiver::GetDataProperty(promise, key)->IsUndefined(isolate));
|
||||
CHECK(!promise->has_handler());
|
||||
isolate->ReportPromiseReject(promise, Handle<Object>(),
|
||||
v8::kPromiseHandlerAddedAfterReject);
|
||||
return isolate->heap()->undefined_value();
|
||||
@ -275,5 +274,14 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectReactions) {
|
||||
Handle<FixedArray>::cast(reject_reactions));
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_PromiseMarkAsHandled) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 1);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
|
||||
|
||||
promise->set_has_handler(true);
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -309,6 +309,7 @@ namespace internal {
|
||||
F(PromiseDeferred, 1, 1) \
|
||||
F(PromiseReject, 3, 1) \
|
||||
F(PromiseFulfill, 3, 1) \
|
||||
F(PromiseMarkAsHandled, 1, 1) \
|
||||
F(PromiseRejectEventFromStack, 2, 1) \
|
||||
F(PromiseRejectReactions, 1, 1) \
|
||||
F(PromiseRevokeReject, 1, 1) \
|
||||
|
@ -79,7 +79,7 @@ bytecodes: [
|
||||
B(Star), R(0),
|
||||
B(CreateArrayLiteral), U8(0), U8(0), U8(9),
|
||||
B(Star), R(1),
|
||||
B(CallJSRuntime), U8(156), R(0), U8(2),
|
||||
B(CallJSRuntime), U8(157), R(0), U8(2),
|
||||
/* 44 S> */ B(Return),
|
||||
]
|
||||
constant pool: [
|
||||
|
@ -126,14 +126,14 @@ bytecodes: [
|
||||
B(LdaUndefined),
|
||||
B(Star), R(11),
|
||||
B(Mov), R(2), R(12),
|
||||
/* 152 E> */ B(CallJSRuntime), U8(156), R(11), U8(2),
|
||||
/* 152 E> */ B(CallJSRuntime), U8(157), R(11), U8(2),
|
||||
B(Star), R(9),
|
||||
B(CreateArrayLiteral), U8(1), U8(1), U8(9),
|
||||
B(Star), R(10),
|
||||
B(CallJSRuntime), U8(155), R(7), U8(4),
|
||||
B(CallJSRuntime), U8(156), R(7), U8(4),
|
||||
B(Star), R(5),
|
||||
B(Mov), R(0), R(6),
|
||||
/* 140 E> */ B(CallJSRuntime), U8(152), R(3), U8(4),
|
||||
/* 140 E> */ B(CallJSRuntime), U8(153), R(3), U8(4),
|
||||
B(Star), R(3),
|
||||
B(Ldar), R(this),
|
||||
B(JumpIfNotHole), U8(4),
|
||||
|
@ -24,4 +24,4 @@ async function bar() {
|
||||
foo();
|
||||
bar();
|
||||
%RunMicrotasks();
|
||||
assertEquals(0, count);
|
||||
assertEquals(1, count);
|
||||
|
@ -49,7 +49,7 @@ function getStack(error) {
|
||||
map(line => line.replace(/^\s*at (@?[a-zA-Z0-9_\.\[\]]+)(.*)/, "$1"));
|
||||
|
||||
// remove `Promise.then()` invocation by assertEqualsAsync()
|
||||
if (stack[2] === "assertEqualsAsync") return [];
|
||||
if (stack[1] === "assertEqualsAsync") return [];
|
||||
|
||||
return stack.reverse();
|
||||
}
|
||||
@ -96,6 +96,6 @@ assertEqualsAsync(
|
||||
}),
|
||||
"should call Promise[@@Species] after non-internal Then");
|
||||
assertEquals([
|
||||
"@@Species: [@testThenOnReturnedPromise > Promise.then > FakePromise]",
|
||||
"@@Species: [@testThenOnReturnedPromise > FakePromise]",
|
||||
"Then: foo"
|
||||
], log);
|
||||
|
Loading…
Reference in New Issue
Block a user