[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:
gsathya 2016-12-07 22:12:35 -08:00 committed by Commit bot
parent 21c9d278f6
commit 11359e331a
19 changed files with 359 additions and 141 deletions

View File

@ -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;
}

View File

@ -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"));

View File

@ -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

View File

@ -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) \

View File

@ -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) \

View File

@ -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) \

View File

@ -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)) {

View File

@ -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;
});

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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));
}

View File

@ -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);

View File

@ -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

View File

@ -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) \

View File

@ -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: [

View File

@ -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),

View File

@ -24,4 +24,4 @@ async function bar() {
foo();
bar();
%RunMicrotasks();
assertEquals(0, count);
assertEquals(1, count);

View File

@ -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);