[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/924146
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51332}
This commit is contained in:
Benedikt Meurer 2018-02-16 20:10:09 +01:00 committed by Commit Bot
parent 0405a54127
commit 13ca9a0fd1
8 changed files with 252 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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