Commit 13ca9a0f authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Properly optimize calls to promise extras.

Add TurboFan inlining support for the following V8 Extras:

 - v8.createPromise
 - v8.rejectPromise
 - v8.resolvePromise

These are used by the streams implementation in Chrome currently, and
were previously not inlined into TurboFan, although TurboFan already
had all the necessary functionality (namely the JSCreatePromise,
JSRejectPromise and JSResolvePromise operators). We might eventually
want to use these functions in Node core as well (at least short-term
for Node 10), to replace the C++ internal API functions with the same
name that are currently being used by parts of Node core.

For this to work, the rejectPromise and resolvePromise builtins had
to be moved back to CSA, as for JavaScript builtins we still have the
policy that the optimizing compiler must not inline them. But that's
straight-forward since the CSA has all the necessary functionality
available anyways.

Bug: v8:7253
Change-Id: I39ab015c379956cd58ace866e17f8ec23b2257b2
Reviewed-on: https://chromium-review.googlesource.com/924146Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51332}
parent 0405a541
......@@ -4608,12 +4608,30 @@ bool Genesis::InstallNatives(GlobalContextType context_type) {
InstallInternalArray(extras_utils, "InternalPackedArray", PACKED_ELEMENTS);
// v8.createPromise(parent)
Handle<JSFunction> promise_internal_constructor =
SimpleCreateFunction(isolate(), factory()->empty_string(),
Builtins::kPromiseInternalConstructor, 1, true);
promise_internal_constructor->shared()->set_native(false);
InstallFunction(extras_utils, promise_internal_constructor,
factory()->NewStringFromAsciiChecked("createPromise"));
// v8.rejectPromise(promise, reason)
Handle<JSFunction> promise_internal_reject =
SimpleCreateFunction(isolate(), factory()->empty_string(),
Builtins::kPromiseInternalReject, 2, true);
promise_internal_reject->shared()->set_native(false);
InstallFunction(extras_utils, promise_internal_reject,
factory()->NewStringFromAsciiChecked("rejectPromise"));
// v8.resolvePromise(promise, resolution)
Handle<JSFunction> promise_internal_resolve =
SimpleCreateFunction(isolate(), factory()->empty_string(),
Builtins::kPromiseInternalResolve, 2, true);
promise_internal_resolve->shared()->set_native(false);
InstallFunction(extras_utils, promise_internal_resolve,
factory()->NewStringFromAsciiChecked("resolvePromise"));
InstallFunction(extras_utils, isolate()->is_promise(),
factory()->NewStringFromAsciiChecked("isPromise"));
......
......@@ -812,7 +812,6 @@ namespace internal {
TFS(NewPromiseCapability, kConstructor, kDebugEvent) \
/* ES6 #sec-promise-executor */ \
TFJ(PromiseConstructor, 1, kExecutor) \
TFJ(PromiseInternalConstructor, 1, kParent) \
CPP(IsPromise) \
/* ES #sec-promise.prototype.then */ \
TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \
......@@ -840,6 +839,12 @@ namespace internal {
TFJ(PromiseAllResolveElementClosure, 1, kValue) \
/* ES #sec-promise.race */ \
TFJ(PromiseRace, 1, kIterable) \
/* V8 Extras: v8.createPromise(parent) */ \
TFJ(PromiseInternalConstructor, 1, kParent) \
/* V8 Extras: v8.rejectPromise(promise, reason) */ \
TFJ(PromiseInternalReject, 2, kPromise, kReason) \
/* V8 Extras: v8.resolvePromise(promise, resolution) */ \
TFJ(PromiseInternalResolve, 2, kPromise, kResolution) \
\
/* Proxy */ \
TFJ(ProxyConstructor, 0) \
......
......@@ -781,12 +781,31 @@ TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
}
}
// V8 Extras: v8.createPromise(parent)
TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) {
Node* const parent = Parameter(Descriptor::kParent);
Node* const context = Parameter(Descriptor::kContext);
Return(AllocateAndInitJSPromise(context, parent));
}
// V8 Extras: v8.rejectPromise(promise, reason)
TF_BUILTIN(PromiseInternalReject, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const reason = Parameter(Descriptor::kReason);
Node* const context = Parameter(Descriptor::kContext);
// We pass true to trigger the debugger's on exception handler.
Return(CallBuiltin(Builtins::kRejectPromise, context, promise, reason,
TrueConstant()));
}
// V8 Extras: v8.resolvePromise(promise, resolution)
TF_BUILTIN(PromiseInternalResolve, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const resolution = Parameter(Descriptor::kResolution);
Node* const context = Parameter(Descriptor::kContext);
Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution));
}
// ES#sec-promise.prototype.then
// Promise.prototype.then ( onFulfilled, onRejected )
TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
......
......@@ -2982,6 +2982,12 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReducePromiseCapabilityDefaultReject(node);
case Builtins::kPromiseCapabilityDefaultResolve:
return ReducePromiseCapabilityDefaultResolve(node);
case Builtins::kPromiseInternalConstructor:
return ReducePromiseInternalConstructor(node);
case Builtins::kPromiseInternalReject:
return ReducePromiseInternalReject(node);
case Builtins::kPromiseInternalResolve:
return ReducePromiseInternalResolve(node);
case Builtins::kPromisePrototypeCatch:
return ReducePromisePrototypeCatch(node);
case Builtins::kPromisePrototypeFinally:
......@@ -4064,6 +4070,73 @@ Reduction JSCallReducer::ReducePromiseCapabilityDefaultResolve(Node* node) {
return Replace(value);
}
// V8 Extras: v8.createPromise(parent)
Reduction JSCallReducer::ReducePromiseInternalConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
// Check that promises aren't being observed through (debug) hooks.
if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange();
// Install a code dependency on the promise hook protector cell.
dependencies()->AssumePropertyCell(factory()->promise_hook_protector());
// Create a new pending promise.
Node* value = effect =
graph()->NewNode(javascript()->CreatePromise(), context, effect);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// V8 Extras: v8.rejectPromise(promise, reason)
Reduction JSCallReducer::ReducePromiseInternalReject(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* promise = node->op()->ValueInputCount() >= 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* reason = node->op()->ValueInputCount() >= 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
Node* debug_event = jsgraph()->TrueConstant();
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Reject the {promise} using the given {reason}, and trigger debug logic.
Node* value = effect =
graph()->NewNode(javascript()->RejectPromise(), promise, reason,
debug_event, context, frame_state, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// V8 Extras: v8.resolvePromise(promise, resolution)
Reduction JSCallReducer::ReducePromiseInternalResolve(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* promise = node->op()->ValueInputCount() >= 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* resolution = node->op()->ValueInputCount() >= 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Resolve the {promise} using the given {resolution}.
Node* value = effect =
graph()->NewNode(javascript()->ResolvePromise(), promise, resolution,
context, frame_state, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES section #sec-promise.prototype.catch
Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
......
......@@ -103,6 +103,9 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
Reduction ReducePromiseCapabilityDefaultReject(Node* node);
Reduction ReducePromiseCapabilityDefaultResolve(Node* node);
Reduction ReducePromiseInternalConstructor(Node* node);
Reduction ReducePromiseInternalReject(Node* node);
Reduction ReducePromiseInternalResolve(Node* node);
Reduction ReducePromisePrototypeCatch(Node* node);
Reduction ReducePromisePrototypeFinally(Node* node);
Reduction ReducePromisePrototypeThen(Node* node);
......
......@@ -185,15 +185,6 @@ extrasUtils.uncurryThis = function uncurryThis(func) {
};
};
extrasUtils.resolvePromise = function resolvePromise(promise, resolution) {
%_ResolvePromise(promise, resolution);
}
// We pass true to trigger the debugger's on exception handler.
extrasUtils.rejectPromise = function rejectPromise(promise, reason) {
%_RejectPromise(promise, reason, true);
}
extrasUtils.markPromiseAsHandled = function markPromiseAsHandled(promise) {
%PromiseMarkAsHandled(promise);
};
......
......@@ -25563,6 +25563,123 @@ TEST(ExperimentalExtras) {
CHECK_EQ(7, result->Int32Value(env.local()).FromJust());
}
TEST(ExtrasCreatePromise) {
i::FLAG_allow_natives_syntax = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
LocalContext env;
v8::Local<v8::Object> binding = env->GetExtrasBindingObject();
auto func = binding->Get(env.local(), v8_str("testCreatePromise"))
.ToLocalChecked()
.As<v8::Function>();
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
auto promise = CompileRun(
"func();\n"
"func();\n"
"%OptimizeFunctionOnNextCall(func);\n"
"func()\n")
.As<v8::Promise>();
CHECK_EQ(v8::Promise::kPending, promise->State());
}
TEST(ExtrasCreatePromiseWithParent) {
i::FLAG_allow_natives_syntax = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
LocalContext env;
v8::Local<v8::Object> binding = env->GetExtrasBindingObject();
auto func = binding->Get(env.local(), v8_str("testCreatePromiseWithParent"))
.ToLocalChecked()
.As<v8::Function>();
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
auto promise = CompileRun(
"var parent = new Promise((a, b) => {});\n"
"func(parent);\n"
"func(parent);\n"
"%OptimizeFunctionOnNextCall(func);\n"
"func(parent)\n")
.As<v8::Promise>();
CHECK_EQ(v8::Promise::kPending, promise->State());
}
TEST(ExtrasRejectPromise) {
i::FLAG_allow_natives_syntax = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
LocalContext env;
v8::Local<v8::Object> binding = env->GetExtrasBindingObject();
auto func = binding->Get(env.local(), v8_str("testRejectPromise"))
.ToLocalChecked()
.As<v8::Function>();
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
auto rejected_promise = CompileRun(
"function newPromise() {\n"
" return new Promise((a, b) => {});\n"
"}\n"
"func(newPromise(), 1);\n"
"func(newPromise(), 1);\n"
"%OptimizeFunctionOnNextCall(func);\n"
"var promise = newPromise();\n"
"func(promise, 1);\n"
"promise;\n")
.As<v8::Promise>();
CHECK_EQ(v8::Promise::kRejected, rejected_promise->State());
CHECK_EQ(1, rejected_promise->Result()->Int32Value(env.local()).FromJust());
}
TEST(ExtrasResolvePromise) {
i::FLAG_allow_natives_syntax = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
LocalContext env;
v8::Local<v8::Object> binding = env->GetExtrasBindingObject();
auto func = binding->Get(env.local(), v8_str("testResolvePromise"))
.ToLocalChecked()
.As<v8::Function>();
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
auto pending_promise = CompileRun(
"function newPromise() {\n"
" return new Promise((a, b) => {});\n"
"}\n"
"func(newPromise(), newPromise());\n"
"func(newPromise(), newPromise());\n"
"%OptimizeFunctionOnNextCall(func);\n"
"var promise = newPromise();\n"
"func(promise, newPromise());\n"
"promise;\n")
.As<v8::Promise>();
CHECK_EQ(v8::Promise::kPending, pending_promise->State());
auto fulfilled_promise = CompileRun(
"function newPromise() {\n"
" return new Promise((a, b) => {});\n"
"}\n"
"func(newPromise(), 1);\n"
"func(newPromise(), 1);\n"
"%OptimizeFunctionOnNextCall(func);\n"
"var promise = newPromise();\n"
"func(promise, 1);\n"
"promise;\n")
.As<v8::Promise>();
CHECK_EQ(v8::Promise::kFulfilled, fulfilled_promise->State());
CHECK_EQ(1, fulfilled_promise->Result()->Int32Value(env.local()).FromJust());
}
TEST(ExtrasUtilsObject) {
LocalContext context;
......
......@@ -49,6 +49,22 @@
arrayToTest[1] === 1 && slicedArray.length === 2 &&
slicedArray[0] === "c" && slicedArray[1] === 1;
binding.testCreatePromise = function() {
return v8.createPromise();
}
binding.testCreatePromiseWithParent = function(parent) {
return v8.createPromise(parent);
}
binding.testRejectPromise = function(promise, reason) {
return v8.rejectPromise(promise, reason);
}
binding.testResolvePromise = function(promise, resolution) {
return v8.resolvePromise(promise, resolution);
}
binding.testExtraCanUseUtils = function() {
const fulfilledPromise = v8.createPromise();
v8.resolvePromise(
......
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