Commit b23b098f authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[builtins] Implement Promise#catch by really calling into Promise#then.

We still avoid the "then" lookup using the current fast-path
mega-guard in the baseline case, but in TurboFan we simply
constant-fold the "then" lookup in the JSCallReducer. So all
further optimizations on Promise#then in TurboFan will automatically
apply to Promise#catch as well.

Bug: v8:7253
Change-Id: Idf7252157375a0ae3a91c7a3b42c30c5f367c0a8
Reviewed-on: https://chromium-review.googlesource.com/895446
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51008}
parent 2c7de324
...@@ -1095,30 +1095,37 @@ TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) { ...@@ -1095,30 +1095,37 @@ TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) {
TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) { TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
// 1. Let promise be the this value. // 1. Let promise be the this value.
Node* const promise = Parameter(Descriptor::kReceiver); Node* const promise = Parameter(Descriptor::kReceiver);
Node* const on_resolve = UndefinedConstant(); Node* const on_fulfilled = UndefinedConstant();
Node* const on_reject = Parameter(Descriptor::kOnRejected); Node* const on_rejected = Parameter(Descriptor::kOnRejected);
Node* const context = Parameter(Descriptor::kContext); Node* const context = Parameter(Descriptor::kContext);
Label if_internalthen(this), if_customthen(this, Label::kDeferred); // 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
GotoIf(TaggedIsSmi(promise), &if_customthen); VARIABLE(var_then, MachineRepresentation::kTagged);
BranchIfFastPath(context, promise, &if_internalthen, &if_customthen); Label if_fast(this), if_slow(this, Label::kDeferred), done(this);
GotoIf(TaggedIsSmi(promise), &if_slow);
BranchIfFastPath(context, promise, &if_fast, &if_slow);
BIND(&if_internalthen); BIND(&if_fast);
{ {
Node* const result = Node* const native_context = LoadNativeContext(context);
InternalPromiseThen(context, promise, on_resolve, on_reject); var_then.Bind(
Return(result); LoadContextElement(native_context, Context::PROMISE_THEN_INDEX));
Goto(&done);
} }
BIND(&if_customthen); BIND(&if_slow);
{ {
Node* const then = var_then.Bind(
GetProperty(context, promise, isolate()->factory()->then_string()); GetProperty(context, promise, isolate()->factory()->then_string()));
Node* const result = CallJS( Goto(&done);
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
context, then, promise, on_resolve, on_reject);
Return(result);
} }
BIND(&done);
Node* const then = var_then.value();
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
context, then, promise, on_fulfilled, on_rejected);
Return(result);
} }
// ES section #sec-promiseresolvethenablejob // ES section #sec-promiseresolvethenablejob
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "src/code-stubs.h" #include "src/code-stubs.h"
#include "src/compilation-dependencies.h" #include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h" #include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/allocation-builder.h" #include "src/compiler/allocation-builder.h"
#include "src/compiler/js-graph.h" #include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h" #include "src/compiler/linkage.h"
...@@ -2966,6 +2967,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { ...@@ -2966,6 +2967,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceAsyncFunctionPromiseCreate(node); return ReduceAsyncFunctionPromiseCreate(node);
case Builtins::kAsyncFunctionPromiseRelease: case Builtins::kAsyncFunctionPromiseRelease:
return ReduceAsyncFunctionPromiseRelease(node); return ReduceAsyncFunctionPromiseRelease(node);
case Builtins::kPromisePrototypeCatch:
return ReducePromisePrototypeCatch(node);
default: default:
break; break;
} }
...@@ -3928,6 +3931,64 @@ Reduction JSCallReducer::ReduceAsyncFunctionPromiseRelease(Node* node) { ...@@ -3928,6 +3931,64 @@ Reduction JSCallReducer::ReduceAsyncFunctionPromiseRelease(Node* node) {
return Replace(value); return Replace(value);
} }
Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
// Check if we know something about {receiver} already.
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
DCHECK_NE(0, receiver_maps.size());
if (receiver_maps.size() != 1) return NoChange();
Handle<Map> receiver_map = receiver_maps[0];
// Lookup the "then" method on the {receiver_map}.
PropertyAccessInfo access_info;
AccessInfoFactory access_info_factory(dependencies(), native_context(),
graph()->zone());
if (!access_info_factory.ComputePropertyAccessInfo(
receiver_map, factory()->then_string(), AccessMode::kLoad,
&access_info) ||
!access_info.IsDataConstant()) {
return NoChange();
}
dependencies()->AssumePrototypeMapsStable(receiver_map, access_info.holder());
Handle<Object> then = access_info.constant();
// If the {receiver_maps} aren't reliable, we need to repeat the
// map check here, guarded by the CALL_IC.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
// Massage the {node} to call "then" instead by first removing all inputs
// following the onRejected parameter, and then filling up the parameters
// to two inputs from the left with undefined.
NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(then), 0);
NodeProperties::ReplaceEffectInput(node, effect);
for (; arity > 1; --arity) node->RemoveInput(3);
for (; arity < 2; ++arity) {
node->InsertInput(graph()->zone(), 2, jsgraph()->UndefinedConstant());
}
NodeProperties::ChangeOp(
node, javascript()->Call(2 + arity, p.frequency(), p.feedback(),
ConvertReceiverMode::kNotNullOrUndefined,
p.speculation_mode()));
return Changed(node);
}
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(); }
......
...@@ -101,6 +101,7 @@ class JSCallReducer final : public AdvancedReducer { ...@@ -101,6 +101,7 @@ class JSCallReducer final : public AdvancedReducer {
const Operator* string_access_operator, Node* node); const Operator* string_access_operator, Node* node);
Reduction ReduceAsyncFunctionPromiseCreate(Node* node); Reduction ReduceAsyncFunctionPromiseCreate(Node* node);
Reduction ReduceAsyncFunctionPromiseRelease(Node* node); Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
Reduction ReducePromisePrototypeCatch(Node* node);
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason); Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
......
// 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() {
function foo(p) { return p.catch(); }
foo(Promise.resolve(1));
foo(Promise.resolve(1));
%OptimizeFunctionOnNextCall(foo);
foo(Promise.resolve(1));
})();
(function() {
function foo(p) { return p.catch(foo); }
foo(Promise.resolve(1));
foo(Promise.resolve(1));
%OptimizeFunctionOnNextCall(foo);
foo(Promise.resolve(1));
})();
(function() {
function foo(p) { return p.catch(foo, undefined); }
foo(Promise.resolve(1));
foo(Promise.resolve(1));
%OptimizeFunctionOnNextCall(foo);
foo(Promise.resolve(1));
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment