[builtins] Unify PerformPromiseThen and optimize it with TurboFan.
This creates a uniform PerformPromiseThen builtin, which performs the operation with the same name from the spec, except that it expects the handlers to be either undefined or callable already, since this is only relevant for a single callsite (namely Promise.prototype.then). Introduce a matching operator JSPerformPromiseThen into TurboFan, which represents this operation and removes the additional checks in case of Promise.prototype.then based on the information we can derived from the receiver maps. This yields a nice 20-25% improvement on Promise.prototype.then, as illustrated by the following micro-benchmark ```js const N = 1e7; function inc(x) { return x + 1; } function chain(promise) { return promise.then(inc).then(value => { if (value < N) chain(Promise.resolve(value)); }); } console.time('total'); chain(Promise.resolve(0)); setTimeout(console.timeEnd.bind(console, 'total')); ``` which goes from around 1230ms to 930ms with this patch. Bug: v8:7253 Change-Id: I5712a863acdbe7da3bb8e621887c7b952148c51a Reviewed-on: https://chromium-review.googlesource.com/899064 Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/master@{#51071}
This commit is contained in:
parent
ac5ad35283
commit
a582199c5e
@ -143,11 +143,8 @@ Node* AsyncBuiltinsAssembler::Await(
|
|||||||
|
|
||||||
Goto(&do_perform_promise_then);
|
Goto(&do_perform_promise_then);
|
||||||
BIND(&do_perform_promise_then);
|
BIND(&do_perform_promise_then);
|
||||||
|
return CallBuiltin(Builtins::kPerformPromiseThen, context, wrapped_value,
|
||||||
CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapped_value,
|
on_resolve, on_reject, throwaway);
|
||||||
on_resolve, on_reject, throwaway);
|
|
||||||
|
|
||||||
return wrapped_value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,
|
void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,
|
||||||
|
@ -128,7 +128,7 @@ void AsyncFromSyncBuiltinsAssembler::Generate_AsyncFromSyncIteratorMethod(
|
|||||||
|
|
||||||
// Perform ! PerformPromiseThen(valueWrapperCapability.[[Promise]],
|
// Perform ! PerformPromiseThen(valueWrapperCapability.[[Promise]],
|
||||||
// onFulfilled, undefined, promiseCapability).
|
// onFulfilled, undefined, promiseCapability).
|
||||||
Return(CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapper,
|
Return(CallBuiltin(Builtins::kPerformPromiseThen, context, wrapper,
|
||||||
on_fulfilled, UndefinedConstant(), promise));
|
on_fulfilled, UndefinedConstant(), promise));
|
||||||
|
|
||||||
BIND(&reject_promise);
|
BIND(&reject_promise);
|
||||||
|
@ -219,8 +219,6 @@ namespace internal {
|
|||||||
/* Promise helpers */ \
|
/* Promise helpers */ \
|
||||||
TFS(ResolveNativePromise, kPromise, kValue) \
|
TFS(ResolveNativePromise, kPromise, kValue) \
|
||||||
TFS(RejectNativePromise, kPromise, kValue, kDebugEvent) \
|
TFS(RejectNativePromise, kPromise, kValue, kDebugEvent) \
|
||||||
TFS(PerformNativePromiseThen, kPromise, kResolveReaction, kRejectReaction, \
|
|
||||||
kResultPromise) \
|
|
||||||
TFS(EnqueueMicrotask, kMicrotask) \
|
TFS(EnqueueMicrotask, kMicrotask) \
|
||||||
TFC(RunMicrotasks, RunMicrotasks, 1) \
|
TFC(RunMicrotasks, RunMicrotasks, 1) \
|
||||||
\
|
\
|
||||||
@ -811,6 +809,8 @@ namespace internal {
|
|||||||
TFJ(PromiseAllResolveElementClosure, 1, kValue) \
|
TFJ(PromiseAllResolveElementClosure, 1, kValue) \
|
||||||
/* ES #sec-promise.prototype.then */ \
|
/* ES #sec-promise.prototype.then */ \
|
||||||
TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \
|
TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \
|
||||||
|
/* ES #sec-performpromisethen */ \
|
||||||
|
TFS(PerformPromiseThen, kPromise, kOnFulfilled, kOnRejected, kResultPromise) \
|
||||||
/* ES #sec-promise.prototype.catch */ \
|
/* ES #sec-promise.prototype.catch */ \
|
||||||
TFJ(PromisePrototypeCatch, 1, kOnRejected) \
|
TFJ(PromisePrototypeCatch, 1, kOnRejected) \
|
||||||
/* ES #sec-fulfillpromise */ \
|
/* ES #sec-fulfillpromise */ \
|
||||||
@ -1246,7 +1246,6 @@ namespace internal {
|
|||||||
V(AsyncGeneratorResolve) \
|
V(AsyncGeneratorResolve) \
|
||||||
V(AsyncGeneratorAwaitCaught) \
|
V(AsyncGeneratorAwaitCaught) \
|
||||||
V(AsyncGeneratorAwaitUncaught) \
|
V(AsyncGeneratorAwaitUncaught) \
|
||||||
V(PerformNativePromiseThen) \
|
|
||||||
V(PromiseAll) \
|
V(PromiseAll) \
|
||||||
V(PromiseConstructor) \
|
V(PromiseConstructor) \
|
||||||
V(PromiseFulfillReactionJob) \
|
V(PromiseFulfillReactionJob) \
|
||||||
|
@ -256,36 +256,18 @@ void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) {
|
|||||||
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
|
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
|
// ES section #sec-performpromisethen
|
||||||
Node* promise,
|
void PromiseBuiltinsAssembler::PerformPromiseThen(
|
||||||
Node* on_fulfilled,
|
Node* context, Node* promise, Node* on_fulfilled, Node* on_rejected,
|
||||||
Node* on_rejected,
|
Node* result_promise_or_capability) {
|
||||||
Node* result) {
|
CSA_ASSERT(this, TaggedIsNotSmi(promise));
|
||||||
CSA_ASSERT(this, TaggedIsNotSmi(result));
|
CSA_ASSERT(this, IsJSPromise(promise));
|
||||||
CSA_ASSERT(this, Word32Or(IsJSPromise(result), IsPromiseCapability(result)));
|
CSA_ASSERT(this,
|
||||||
|
Word32Or(IsCallable(on_fulfilled), IsUndefined(on_fulfilled)));
|
||||||
// 3. If IsCallable(onFulfilled) is false, then
|
CSA_ASSERT(this, Word32Or(IsCallable(on_rejected), IsUndefined(on_rejected)));
|
||||||
// a. Set onFulfilled to undefined.
|
CSA_ASSERT(this, TaggedIsNotSmi(result_promise_or_capability));
|
||||||
VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled);
|
CSA_ASSERT(this, Word32Or(IsJSPromise(result_promise_or_capability),
|
||||||
Label if_fulfilled_done(this), if_fulfilled_notcallable(this);
|
IsPromiseCapability(result_promise_or_capability)));
|
||||||
GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable);
|
|
||||||
Branch(IsCallable(on_fulfilled), &if_fulfilled_done,
|
|
||||||
&if_fulfilled_notcallable);
|
|
||||||
BIND(&if_fulfilled_notcallable);
|
|
||||||
var_on_fulfilled.Bind(UndefinedConstant());
|
|
||||||
Goto(&if_fulfilled_done);
|
|
||||||
BIND(&if_fulfilled_done);
|
|
||||||
|
|
||||||
// 4. If IsCallable(onRejected) is false, then
|
|
||||||
// a. Set onRejected to undefined.
|
|
||||||
VARIABLE(var_on_rejected, MachineRepresentation::kTagged, on_rejected);
|
|
||||||
Label if_rejected_done(this), if_rejected_notcallable(this);
|
|
||||||
GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable);
|
|
||||||
Branch(IsCallable(on_rejected), &if_rejected_done, &if_rejected_notcallable);
|
|
||||||
BIND(&if_rejected_notcallable);
|
|
||||||
var_on_rejected.Bind(UndefinedConstant());
|
|
||||||
Goto(&if_rejected_done);
|
|
||||||
BIND(&if_rejected_done);
|
|
||||||
|
|
||||||
Label if_pending(this), if_notpending(this), done(this);
|
Label if_pending(this), if_notpending(this), done(this);
|
||||||
Node* const status = PromiseStatus(promise);
|
Node* const status = PromiseStatus(promise);
|
||||||
@ -300,9 +282,9 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
|
|||||||
// push onto the microtask queue.
|
// push onto the microtask queue.
|
||||||
Node* const promise_reactions =
|
Node* const promise_reactions =
|
||||||
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
|
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
|
||||||
Node* const reaction = AllocatePromiseReaction(promise_reactions, result,
|
Node* const reaction =
|
||||||
var_on_fulfilled.value(),
|
AllocatePromiseReaction(promise_reactions, result_promise_or_capability,
|
||||||
var_on_rejected.value());
|
on_fulfilled, on_rejected);
|
||||||
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
|
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
|
||||||
Goto(&done);
|
Goto(&done);
|
||||||
}
|
}
|
||||||
@ -319,7 +301,7 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
|
|||||||
BIND(&if_fulfilled);
|
BIND(&if_fulfilled);
|
||||||
{
|
{
|
||||||
var_map.Bind(LoadRoot(Heap::kPromiseFulfillReactionJobTaskMapRootIndex));
|
var_map.Bind(LoadRoot(Heap::kPromiseFulfillReactionJobTaskMapRootIndex));
|
||||||
var_handler.Bind(var_on_fulfilled.value());
|
var_handler.Bind(on_fulfilled);
|
||||||
Goto(&enqueue);
|
Goto(&enqueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +309,7 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
|
|||||||
{
|
{
|
||||||
CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected));
|
CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected));
|
||||||
var_map.Bind(LoadRoot(Heap::kPromiseRejectReactionJobTaskMapRootIndex));
|
var_map.Bind(LoadRoot(Heap::kPromiseRejectReactionJobTaskMapRootIndex));
|
||||||
var_handler.Bind(var_on_rejected.value());
|
var_handler.Bind(on_rejected);
|
||||||
GotoIf(PromiseHasHandler(promise), &enqueue);
|
GotoIf(PromiseHasHandler(promise), &enqueue);
|
||||||
CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
|
CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
|
||||||
Goto(&enqueue);
|
Goto(&enqueue);
|
||||||
@ -337,7 +319,8 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
|
|||||||
Node* argument =
|
Node* argument =
|
||||||
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
|
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
|
||||||
Node* microtask = AllocatePromiseReactionJobTask(
|
Node* microtask = AllocatePromiseReactionJobTask(
|
||||||
var_map.value(), context, argument, var_handler.value(), result);
|
var_map.value(), context, argument, var_handler.value(),
|
||||||
|
result_promise_or_capability);
|
||||||
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask);
|
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask);
|
||||||
Goto(&done);
|
Goto(&done);
|
||||||
}
|
}
|
||||||
@ -346,6 +329,22 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
|
|||||||
PromiseSetHasHandler(promise);
|
PromiseSetHasHandler(promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ES section #sec-performpromisethen
|
||||||
|
TF_BUILTIN(PerformPromiseThen, PromiseBuiltinsAssembler) {
|
||||||
|
Node* const context = Parameter(Descriptor::kContext);
|
||||||
|
Node* const promise = Parameter(Descriptor::kPromise);
|
||||||
|
Node* const on_fulfilled = Parameter(Descriptor::kOnFulfilled);
|
||||||
|
Node* const on_rejected = Parameter(Descriptor::kOnRejected);
|
||||||
|
Node* const result_promise = Parameter(Descriptor::kResultPromise);
|
||||||
|
|
||||||
|
CSA_ASSERT(this, TaggedIsNotSmi(result_promise));
|
||||||
|
CSA_ASSERT(this, IsJSPromise(result_promise));
|
||||||
|
|
||||||
|
PerformPromiseThen(context, promise, on_fulfilled, on_rejected,
|
||||||
|
result_promise);
|
||||||
|
Return(result_promise);
|
||||||
|
}
|
||||||
|
|
||||||
// Promise fast path implementations rely on unmodified JSPromise instances.
|
// Promise fast path implementations rely on unmodified JSPromise instances.
|
||||||
// We use a fairly coarse granularity for this and simply check whether both
|
// 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
|
// the promise itself is unmodified (i.e. its map has not changed) and its
|
||||||
@ -957,9 +956,8 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
|
|||||||
BIND(&fast_promise_capability);
|
BIND(&fast_promise_capability);
|
||||||
{
|
{
|
||||||
Node* const result_promise = AllocateAndInitJSPromise(context, promise);
|
Node* const result_promise = AllocateAndInitJSPromise(context, promise);
|
||||||
var_result_promise.Bind(result_promise);
|
|
||||||
var_result_promise_or_capability.Bind(result_promise);
|
var_result_promise_or_capability.Bind(result_promise);
|
||||||
CSA_ASSERT(this, IsJSPromise(var_result_promise_or_capability.value()));
|
var_result_promise.Bind(result_promise);
|
||||||
Goto(&perform_promise_then);
|
Goto(&perform_promise_then);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,21 +969,47 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
|
|||||||
var_result_promise.Bind(
|
var_result_promise.Bind(
|
||||||
LoadObjectField(capability, PromiseCapability::kPromiseOffset));
|
LoadObjectField(capability, PromiseCapability::kPromiseOffset));
|
||||||
var_result_promise_or_capability.Bind(capability);
|
var_result_promise_or_capability.Bind(capability);
|
||||||
CSA_ASSERT(this,
|
|
||||||
IsPromiseCapability(var_result_promise_or_capability.value()));
|
|
||||||
Goto(&perform_promise_then);
|
Goto(&perform_promise_then);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
|
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
|
||||||
// resultCapability).
|
// resultCapability).
|
||||||
BIND(&perform_promise_then);
|
BIND(&perform_promise_then);
|
||||||
CSA_ASSERT(
|
{
|
||||||
this,
|
// We do some work of the PerformPromiseThen operation here, in that
|
||||||
Word32Or(IsJSPromise(var_result_promise_or_capability.value()),
|
// we check the handlers and turn non-callable handlers into undefined.
|
||||||
IsPromiseCapability(var_result_promise_or_capability.value())));
|
// This is because this is the one and only callsite of PerformPromiseThen
|
||||||
InternalPerformPromiseThen(context, promise, on_fulfilled, on_rejected,
|
// that has to do this.
|
||||||
var_result_promise_or_capability.value());
|
|
||||||
Return(var_result_promise.value());
|
// 3. If IsCallable(onFulfilled) is false, then
|
||||||
|
// a. Set onFulfilled to undefined.
|
||||||
|
VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled);
|
||||||
|
Label if_fulfilled_done(this), if_fulfilled_notcallable(this);
|
||||||
|
GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable);
|
||||||
|
Branch(IsCallable(on_fulfilled), &if_fulfilled_done,
|
||||||
|
&if_fulfilled_notcallable);
|
||||||
|
BIND(&if_fulfilled_notcallable);
|
||||||
|
var_on_fulfilled.Bind(UndefinedConstant());
|
||||||
|
Goto(&if_fulfilled_done);
|
||||||
|
BIND(&if_fulfilled_done);
|
||||||
|
|
||||||
|
// 4. If IsCallable(onRejected) is false, then
|
||||||
|
// a. Set onRejected to undefined.
|
||||||
|
VARIABLE(var_on_rejected, MachineRepresentation::kTagged, on_rejected);
|
||||||
|
Label if_rejected_done(this), if_rejected_notcallable(this);
|
||||||
|
GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable);
|
||||||
|
Branch(IsCallable(on_rejected), &if_rejected_done,
|
||||||
|
&if_rejected_notcallable);
|
||||||
|
BIND(&if_rejected_notcallable);
|
||||||
|
var_on_rejected.Bind(UndefinedConstant());
|
||||||
|
Goto(&if_rejected_done);
|
||||||
|
BIND(&if_rejected_done);
|
||||||
|
|
||||||
|
PerformPromiseThen(context, promise, var_on_fulfilled.value(),
|
||||||
|
var_on_rejected.value(),
|
||||||
|
var_result_promise_or_capability.value());
|
||||||
|
Return(var_result_promise.value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES#sec-promise-resolve-functions
|
// ES#sec-promise-resolve-functions
|
||||||
@ -1621,20 +1645,6 @@ TF_BUILTIN(RejectNativePromise, PromiseBuiltinsAssembler) {
|
|||||||
Return(UndefinedConstant());
|
Return(UndefinedConstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
TF_BUILTIN(PerformNativePromiseThen, PromiseBuiltinsAssembler) {
|
|
||||||
Node* const promise = Parameter(Descriptor::kPromise);
|
|
||||||
Node* const resolve_reaction = Parameter(Descriptor::kResolveReaction);
|
|
||||||
Node* const reject_reaction = Parameter(Descriptor::kRejectReaction);
|
|
||||||
Node* const result_promise = Parameter(Descriptor::kResultPromise);
|
|
||||||
Node* const context = Parameter(Descriptor::kContext);
|
|
||||||
|
|
||||||
CSA_ASSERT(this, IsJSPromise(result_promise));
|
|
||||||
|
|
||||||
InternalPerformPromiseThen(context, promise, resolve_reaction,
|
|
||||||
reject_reaction, result_promise);
|
|
||||||
Return(result_promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* PromiseBuiltinsAssembler::PerformPromiseAll(
|
Node* PromiseBuiltinsAssembler::PerformPromiseAll(
|
||||||
Node* context, Node* constructor, Node* capability,
|
Node* context, Node* constructor, Node* capability,
|
||||||
const IteratorRecord& iterator, Label* if_exception,
|
const IteratorRecord& iterator, Label* if_exception,
|
||||||
|
@ -121,9 +121,9 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
|
|||||||
void PromiseSetHasHandler(Node* promise);
|
void PromiseSetHasHandler(Node* promise);
|
||||||
void PromiseSetHandledHint(Node* promise);
|
void PromiseSetHandledHint(Node* promise);
|
||||||
|
|
||||||
void InternalPerformPromiseThen(Node* context, Node* promise,
|
void PerformPromiseThen(Node* context, Node* promise, Node* on_fulfilled,
|
||||||
Node* on_fulfill, Node* on_reject,
|
Node* on_rejected,
|
||||||
Node* result);
|
Node* result_promise_or_capability);
|
||||||
|
|
||||||
void InternalResolvePromise(Node* context, Node* promise, Node* result);
|
void InternalResolvePromise(Node* context, Node* promise, Node* result);
|
||||||
|
|
||||||
@ -177,7 +177,6 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
|
|||||||
Node* promise_or_capability,
|
Node* promise_or_capability,
|
||||||
PromiseReaction::Type type);
|
PromiseReaction::Type type);
|
||||||
|
|
||||||
private:
|
|
||||||
Node* IsPromiseStatus(Node* actual, v8::Promise::PromiseState expected);
|
Node* IsPromiseStatus(Node* actual, v8::Promise::PromiseState expected);
|
||||||
void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status);
|
void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status);
|
||||||
|
|
||||||
|
@ -2969,6 +2969,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
|
|||||||
return ReduceAsyncFunctionPromiseRelease(node);
|
return ReduceAsyncFunctionPromiseRelease(node);
|
||||||
case Builtins::kPromisePrototypeCatch:
|
case Builtins::kPromisePrototypeCatch:
|
||||||
return ReducePromisePrototypeCatch(node);
|
return ReducePromisePrototypeCatch(node);
|
||||||
|
case Builtins::kPromisePrototypeThen:
|
||||||
|
return ReducePromisePrototypeThen(node);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -3989,6 +3991,87 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
|
|||||||
return Changed(node);
|
return Changed(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reduction JSCallReducer::ReducePromisePrototypeThen(Node* node) {
|
||||||
|
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||||
|
CallParameters const& p = CallParametersOf(node->op());
|
||||||
|
Node* receiver = NodeProperties::GetValueInput(node, 1);
|
||||||
|
Node* on_fulfilled = node->op()->ValueInputCount() > 2
|
||||||
|
? NodeProperties::GetValueInput(node, 2)
|
||||||
|
: jsgraph()->UndefinedConstant();
|
||||||
|
Node* on_rejected = node->op()->ValueInputCount() > 3
|
||||||
|
? NodeProperties::GetValueInput(node, 3)
|
||||||
|
: jsgraph()->UndefinedConstant();
|
||||||
|
Node* context = NodeProperties::GetContextInput(node);
|
||||||
|
Node* effect = NodeProperties::GetEffectInput(node);
|
||||||
|
Node* control = NodeProperties::GetControlInput(node);
|
||||||
|
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
|
||||||
|
return NoChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that promises aren't being observed through (debug) hooks.
|
||||||
|
if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange();
|
||||||
|
|
||||||
|
// Check if the @@species protector is intact. The @@species protector
|
||||||
|
// guards the "constructor" lookup on all JSPromise instances and the
|
||||||
|
// initial Promise.prototype, as well as the Symbol.species lookup on
|
||||||
|
// the Promise constructor.
|
||||||
|
if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange();
|
||||||
|
|
||||||
|
// Check if we know something about {receiver} already.
|
||||||
|
ZoneHandleSet<Map> receiver_maps;
|
||||||
|
NodeProperties::InferReceiverMapsResult infer_receiver_maps_result =
|
||||||
|
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
|
||||||
|
if (infer_receiver_maps_result == NodeProperties::kNoReceiverMaps) {
|
||||||
|
return NoChange();
|
||||||
|
}
|
||||||
|
DCHECK_NE(0, receiver_maps.size());
|
||||||
|
|
||||||
|
// Check whether all {receiver_maps} are JSPromise maps and
|
||||||
|
// have the initial Promise.prototype as their [[Prototype]].
|
||||||
|
for (Handle<Map> receiver_map : receiver_maps) {
|
||||||
|
if (!receiver_map->IsJSPromiseMap()) return NoChange();
|
||||||
|
if (receiver_map->prototype() != native_context()->promise_prototype()) {
|
||||||
|
return NoChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a code dependency on the necessary protectors.
|
||||||
|
dependencies()->AssumePropertyCell(factory()->promise_hook_protector());
|
||||||
|
dependencies()->AssumePropertyCell(factory()->species_protector());
|
||||||
|
|
||||||
|
// If the {receiver_maps} aren't reliable, we need to repeat the
|
||||||
|
// map check here, guarded by the CALL_IC.
|
||||||
|
if (infer_receiver_maps_result == NodeProperties::kUnreliableReceiverMaps) {
|
||||||
|
effect =
|
||||||
|
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
|
||||||
|
receiver_maps, p.feedback()),
|
||||||
|
receiver, effect, control);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that {on_fulfilled} is callable.
|
||||||
|
on_fulfilled = graph()->NewNode(
|
||||||
|
common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue),
|
||||||
|
graph()->NewNode(simplified()->ObjectIsCallable(), on_fulfilled),
|
||||||
|
on_fulfilled, jsgraph()->UndefinedConstant());
|
||||||
|
|
||||||
|
// Check that {on_rejected} is callable.
|
||||||
|
on_rejected = graph()->NewNode(
|
||||||
|
common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue),
|
||||||
|
graph()->NewNode(simplified()->ObjectIsCallable(), on_rejected),
|
||||||
|
on_rejected, jsgraph()->UndefinedConstant());
|
||||||
|
|
||||||
|
// Create the resulting JSPromise.
|
||||||
|
Node* result = effect =
|
||||||
|
graph()->NewNode(javascript()->CreatePromise(), context, effect);
|
||||||
|
|
||||||
|
// Chain {result} onto {receiver}.
|
||||||
|
result = effect = graph()->NewNode(javascript()->PerformPromiseThen(),
|
||||||
|
receiver, on_fulfilled, on_rejected,
|
||||||
|
result, context, effect, control);
|
||||||
|
ReplaceWithValue(node, result, effect, control);
|
||||||
|
return Replace(result);
|
||||||
|
}
|
||||||
|
|
||||||
Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
|
Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
|
||||||
|
|
||||||
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
|
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
|
||||||
|
@ -102,6 +102,7 @@ class JSCallReducer final : public AdvancedReducer {
|
|||||||
Reduction ReduceAsyncFunctionPromiseCreate(Node* node);
|
Reduction ReduceAsyncFunctionPromiseCreate(Node* node);
|
||||||
Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
|
Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
|
||||||
Reduction ReducePromisePrototypeCatch(Node* node);
|
Reduction ReducePromisePrototypeCatch(Node* node);
|
||||||
|
Reduction ReducePromisePrototypeThen(Node* node);
|
||||||
|
|
||||||
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
|
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
|
||||||
|
|
||||||
|
@ -766,6 +766,13 @@ void JSGenericLowering::LowerJSDebugger(Node* node) {
|
|||||||
ReplaceWithStubCall(node, callable, flags);
|
ReplaceWithStubCall(node, callable, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JSGenericLowering::LowerJSPerformPromiseThen(Node* node) {
|
||||||
|
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
|
||||||
|
Callable callable =
|
||||||
|
Builtins::CallableFor(isolate(), Builtins::kPerformPromiseThen);
|
||||||
|
ReplaceWithStubCall(node, callable, flags);
|
||||||
|
}
|
||||||
|
|
||||||
Zone* JSGenericLowering::zone() const { return graph()->zone(); }
|
Zone* JSGenericLowering::zone() const { return graph()->zone(); }
|
||||||
|
|
||||||
|
|
||||||
|
@ -543,44 +543,45 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
|
|||||||
return OpParameter<CompareOperationHint>(op);
|
return OpParameter<CompareOperationHint>(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CACHED_OP_LIST(V) \
|
#define CACHED_OP_LIST(V) \
|
||||||
V(BitwiseOr, Operator::kNoProperties, 2, 1) \
|
V(BitwiseOr, Operator::kNoProperties, 2, 1) \
|
||||||
V(BitwiseXor, Operator::kNoProperties, 2, 1) \
|
V(BitwiseXor, Operator::kNoProperties, 2, 1) \
|
||||||
V(BitwiseAnd, Operator::kNoProperties, 2, 1) \
|
V(BitwiseAnd, Operator::kNoProperties, 2, 1) \
|
||||||
V(ShiftLeft, Operator::kNoProperties, 2, 1) \
|
V(ShiftLeft, Operator::kNoProperties, 2, 1) \
|
||||||
V(ShiftRight, Operator::kNoProperties, 2, 1) \
|
V(ShiftRight, Operator::kNoProperties, 2, 1) \
|
||||||
V(ShiftRightLogical, Operator::kNoProperties, 2, 1) \
|
V(ShiftRightLogical, Operator::kNoProperties, 2, 1) \
|
||||||
V(Subtract, Operator::kNoProperties, 2, 1) \
|
V(Subtract, Operator::kNoProperties, 2, 1) \
|
||||||
V(Multiply, Operator::kNoProperties, 2, 1) \
|
V(Multiply, Operator::kNoProperties, 2, 1) \
|
||||||
V(Divide, Operator::kNoProperties, 2, 1) \
|
V(Divide, Operator::kNoProperties, 2, 1) \
|
||||||
V(Modulus, Operator::kNoProperties, 2, 1) \
|
V(Modulus, Operator::kNoProperties, 2, 1) \
|
||||||
V(Exponentiate, Operator::kNoProperties, 2, 1) \
|
V(Exponentiate, Operator::kNoProperties, 2, 1) \
|
||||||
V(BitwiseNot, Operator::kNoProperties, 1, 1) \
|
V(BitwiseNot, Operator::kNoProperties, 1, 1) \
|
||||||
V(Decrement, Operator::kNoProperties, 1, 1) \
|
V(Decrement, Operator::kNoProperties, 1, 1) \
|
||||||
V(Increment, Operator::kNoProperties, 1, 1) \
|
V(Increment, Operator::kNoProperties, 1, 1) \
|
||||||
V(Negate, Operator::kNoProperties, 1, 1) \
|
V(Negate, Operator::kNoProperties, 1, 1) \
|
||||||
V(ToInteger, Operator::kNoProperties, 1, 1) \
|
V(ToInteger, Operator::kNoProperties, 1, 1) \
|
||||||
V(ToLength, Operator::kNoProperties, 1, 1) \
|
V(ToLength, Operator::kNoProperties, 1, 1) \
|
||||||
V(ToName, Operator::kNoProperties, 1, 1) \
|
V(ToName, Operator::kNoProperties, 1, 1) \
|
||||||
V(ToNumber, Operator::kNoProperties, 1, 1) \
|
V(ToNumber, Operator::kNoProperties, 1, 1) \
|
||||||
V(ToNumeric, Operator::kNoProperties, 1, 1) \
|
V(ToNumeric, Operator::kNoProperties, 1, 1) \
|
||||||
V(ToObject, Operator::kFoldable, 1, 1) \
|
V(ToObject, Operator::kFoldable, 1, 1) \
|
||||||
V(ToString, Operator::kNoProperties, 1, 1) \
|
V(ToString, Operator::kNoProperties, 1, 1) \
|
||||||
V(Create, Operator::kNoProperties, 2, 1) \
|
V(Create, Operator::kNoProperties, 2, 1) \
|
||||||
V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \
|
V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \
|
||||||
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
|
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
|
||||||
V(CreatePromise, Operator::kEliminatable, 0, 1) \
|
V(CreatePromise, Operator::kEliminatable, 0, 1) \
|
||||||
V(HasProperty, Operator::kNoProperties, 2, 1) \
|
V(HasProperty, Operator::kNoProperties, 2, 1) \
|
||||||
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
|
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
|
||||||
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
|
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
|
||||||
V(ForInEnumerate, Operator::kNoProperties, 1, 1) \
|
V(ForInEnumerate, Operator::kNoProperties, 1, 1) \
|
||||||
V(LoadMessage, Operator::kNoThrow | Operator::kNoWrite, 0, 1) \
|
V(LoadMessage, Operator::kNoThrow | Operator::kNoWrite, 0, 1) \
|
||||||
V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \
|
V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \
|
||||||
V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \
|
V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \
|
||||||
V(GeneratorRestoreContext, Operator::kNoThrow, 1, 1) \
|
V(GeneratorRestoreContext, Operator::kNoThrow, 1, 1) \
|
||||||
V(GeneratorRestoreInputOrDebugPos, Operator::kNoThrow, 1, 1) \
|
V(GeneratorRestoreInputOrDebugPos, Operator::kNoThrow, 1, 1) \
|
||||||
V(StackCheck, Operator::kNoWrite, 0, 0) \
|
V(StackCheck, Operator::kNoWrite, 0, 0) \
|
||||||
V(Debugger, Operator::kNoProperties, 0, 0) \
|
V(Debugger, Operator::kNoProperties, 0, 0) \
|
||||||
|
V(PerformPromiseThen, Operator::kNoDeopt | Operator::kNoThrow, 4, 1) \
|
||||||
V(GetSuperConstructor, Operator::kNoWrite, 1, 1)
|
V(GetSuperConstructor, Operator::kNoWrite, 1, 1)
|
||||||
|
|
||||||
#define BINARY_OP_LIST(V) V(Add)
|
#define BINARY_OP_LIST(V) V(Add)
|
||||||
@ -1157,6 +1158,10 @@ const Operator* JSOperatorBuilder::CreateBlockContext(
|
|||||||
scope_info); // parameter
|
scope_info); // parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef BINARY_OP_LIST
|
||||||
|
#undef CACHED_OP_LIST
|
||||||
|
#undef COMPARE_OP_LIST
|
||||||
|
|
||||||
} // namespace compiler
|
} // namespace compiler
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
@ -754,6 +754,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
|||||||
const Operator* StackCheck();
|
const Operator* StackCheck();
|
||||||
const Operator* Debugger();
|
const Operator* Debugger();
|
||||||
|
|
||||||
|
const Operator* PerformPromiseThen();
|
||||||
|
|
||||||
const Operator* CreateFunctionContext(int slot_count, ScopeType scope_type);
|
const Operator* CreateFunctionContext(int slot_count, ScopeType scope_type);
|
||||||
const Operator* CreateCatchContext(const Handle<String>& name,
|
const Operator* CreateCatchContext(const Handle<String>& name,
|
||||||
const Handle<ScopeInfo>& scope_info);
|
const Handle<ScopeInfo>& scope_info);
|
||||||
|
@ -195,6 +195,7 @@
|
|||||||
V(JSGeneratorRestoreContext) \
|
V(JSGeneratorRestoreContext) \
|
||||||
V(JSGeneratorRestoreRegister) \
|
V(JSGeneratorRestoreRegister) \
|
||||||
V(JSGeneratorRestoreInputOrDebugPos) \
|
V(JSGeneratorRestoreInputOrDebugPos) \
|
||||||
|
V(JSPerformPromiseThen) \
|
||||||
V(JSStackCheck) \
|
V(JSStackCheck) \
|
||||||
V(JSDebugger)
|
V(JSDebugger)
|
||||||
|
|
||||||
|
@ -1842,6 +1842,11 @@ Type* Typer::Visitor::TypeJSStackCheck(Node* node) { return Type::Any(); }
|
|||||||
|
|
||||||
Type* Typer::Visitor::TypeJSDebugger(Node* node) { return Type::Any(); }
|
Type* Typer::Visitor::TypeJSDebugger(Node* node) { return Type::Any(); }
|
||||||
|
|
||||||
|
Type* Typer::Visitor::TypeJSPerformPromiseThen(Node* node) {
|
||||||
|
// TODO(turbofan): Introduce a Type::Promise here.
|
||||||
|
return Type::OtherObject();
|
||||||
|
}
|
||||||
|
|
||||||
// Simplified operators.
|
// Simplified operators.
|
||||||
|
|
||||||
Type* Typer::Visitor::TypeBooleanNot(Node* node) { return Type::Boolean(); }
|
Type* Typer::Visitor::TypeBooleanNot(Node* node) { return Type::Boolean(); }
|
||||||
|
@ -853,6 +853,14 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
|
|||||||
CheckNotTyped(node);
|
CheckNotTyped(node);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case IrOpcode::kJSPerformPromiseThen:
|
||||||
|
CheckValueInputIs(node, 0, Type::Any());
|
||||||
|
CheckValueInputIs(node, 1, Type::Any());
|
||||||
|
CheckValueInputIs(node, 2, Type::Any());
|
||||||
|
CheckValueInputIs(node, 3, Type::Any());
|
||||||
|
CheckTypeIs(node, Type::OtherObject());
|
||||||
|
break;
|
||||||
|
|
||||||
case IrOpcode::kComment:
|
case IrOpcode::kComment:
|
||||||
case IrOpcode::kDebugAbort:
|
case IrOpcode::kDebugAbort:
|
||||||
case IrOpcode::kDebugBreak:
|
case IrOpcode::kDebugBreak:
|
||||||
|
@ -503,6 +503,7 @@ bool Map::IsJSObjectMap() const {
|
|||||||
STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE);
|
STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE);
|
||||||
return instance_type() >= FIRST_JS_OBJECT_TYPE;
|
return instance_type() >= FIRST_JS_OBJECT_TYPE;
|
||||||
}
|
}
|
||||||
|
bool Map::IsJSPromiseMap() const { return instance_type() == JS_PROMISE_TYPE; }
|
||||||
bool Map::IsJSArrayMap() const { return instance_type() == JS_ARRAY_TYPE; }
|
bool Map::IsJSArrayMap() const { return instance_type() == JS_ARRAY_TYPE; }
|
||||||
bool Map::IsJSFunctionMap() const {
|
bool Map::IsJSFunctionMap() const {
|
||||||
return instance_type() == JS_FUNCTION_TYPE;
|
return instance_type() == JS_FUNCTION_TYPE;
|
||||||
|
@ -713,6 +713,7 @@ class Map : public HeapObject {
|
|||||||
inline bool IsPrimitiveMap() const;
|
inline bool IsPrimitiveMap() const;
|
||||||
inline bool IsJSReceiverMap() const;
|
inline bool IsJSReceiverMap() const;
|
||||||
inline bool IsJSObjectMap() const;
|
inline bool IsJSObjectMap() const;
|
||||||
|
inline bool IsJSPromiseMap() const;
|
||||||
inline bool IsJSArrayMap() const;
|
inline bool IsJSArrayMap() const;
|
||||||
inline bool IsJSFunctionMap() const;
|
inline bool IsJSFunctionMap() const;
|
||||||
inline bool IsStringMap() const;
|
inline bool IsStringMap() const;
|
||||||
|
50
test/mjsunit/compiler/promise-prototype-then.js
Normal file
50
test/mjsunit/compiler/promise-prototype-then.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flags: --allow-natives-syntax
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const p = Promise.resolve(1);
|
||||||
|
function foo(p) { return p.then(); }
|
||||||
|
foo(p);
|
||||||
|
foo(p);
|
||||||
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
foo(p);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const p = Promise.resolve(1);
|
||||||
|
function foo(p) { return p.then(x => x); }
|
||||||
|
foo(p);
|
||||||
|
foo(p);
|
||||||
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
foo(p);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const p = Promise.resolve(1);
|
||||||
|
function foo(p) { return p.then(x => x, y => y); }
|
||||||
|
foo(p);
|
||||||
|
foo(p);
|
||||||
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
foo(p);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const p = Promise.resolve(1);
|
||||||
|
function foo(p, f) { return p.then(f, f); }
|
||||||
|
foo(p, x => x);
|
||||||
|
foo(p, x => x);
|
||||||
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
foo(p, x => x);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const p = Promise.resolve(1);
|
||||||
|
function foo(p, f) { return p.then(f, f).then(f, f); }
|
||||||
|
foo(p, x => x);
|
||||||
|
foo(p, x => x);
|
||||||
|
%OptimizeFunctionOnNextCall(foo);
|
||||||
|
foo(p, x => x);
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user