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) { ...@@ -4608,12 +4608,30 @@ bool Genesis::InstallNatives(GlobalContextType context_type) {
InstallInternalArray(extras_utils, "InternalPackedArray", PACKED_ELEMENTS); InstallInternalArray(extras_utils, "InternalPackedArray", PACKED_ELEMENTS);
// v8.createPromise(parent)
Handle<JSFunction> promise_internal_constructor = Handle<JSFunction> promise_internal_constructor =
SimpleCreateFunction(isolate(), factory()->empty_string(), SimpleCreateFunction(isolate(), factory()->empty_string(),
Builtins::kPromiseInternalConstructor, 1, true); Builtins::kPromiseInternalConstructor, 1, true);
promise_internal_constructor->shared()->set_native(false); promise_internal_constructor->shared()->set_native(false);
InstallFunction(extras_utils, promise_internal_constructor, InstallFunction(extras_utils, promise_internal_constructor,
factory()->NewStringFromAsciiChecked("createPromise")); 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(), InstallFunction(extras_utils, isolate()->is_promise(),
factory()->NewStringFromAsciiChecked("isPromise")); factory()->NewStringFromAsciiChecked("isPromise"));
......
...@@ -812,7 +812,6 @@ namespace internal { ...@@ -812,7 +812,6 @@ namespace internal {
TFS(NewPromiseCapability, kConstructor, kDebugEvent) \ TFS(NewPromiseCapability, kConstructor, kDebugEvent) \
/* ES6 #sec-promise-executor */ \ /* ES6 #sec-promise-executor */ \
TFJ(PromiseConstructor, 1, kExecutor) \ TFJ(PromiseConstructor, 1, kExecutor) \
TFJ(PromiseInternalConstructor, 1, kParent) \
CPP(IsPromise) \ CPP(IsPromise) \
/* ES #sec-promise.prototype.then */ \ /* ES #sec-promise.prototype.then */ \
TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \ TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \
...@@ -840,6 +839,12 @@ namespace internal { ...@@ -840,6 +839,12 @@ namespace internal {
TFJ(PromiseAllResolveElementClosure, 1, kValue) \ TFJ(PromiseAllResolveElementClosure, 1, kValue) \
/* ES #sec-promise.race */ \ /* ES #sec-promise.race */ \
TFJ(PromiseRace, 1, kIterable) \ 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 */ \ /* Proxy */ \
TFJ(ProxyConstructor, 0) \ TFJ(ProxyConstructor, 0) \
......
...@@ -781,12 +781,31 @@ TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) { ...@@ -781,12 +781,31 @@ TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
} }
} }
// V8 Extras: v8.createPromise(parent)
TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) { TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) {
Node* const parent = Parameter(Descriptor::kParent); Node* const parent = Parameter(Descriptor::kParent);
Node* const context = Parameter(Descriptor::kContext); Node* const context = Parameter(Descriptor::kContext);
Return(AllocateAndInitJSPromise(context, parent)); 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 // ES#sec-promise.prototype.then
// Promise.prototype.then ( onFulfilled, onRejected ) // Promise.prototype.then ( onFulfilled, onRejected )
TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) { TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
......
...@@ -2982,6 +2982,12 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { ...@@ -2982,6 +2982,12 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReducePromiseCapabilityDefaultReject(node); return ReducePromiseCapabilityDefaultReject(node);
case Builtins::kPromiseCapabilityDefaultResolve: case Builtins::kPromiseCapabilityDefaultResolve:
return ReducePromiseCapabilityDefaultResolve(node); return ReducePromiseCapabilityDefaultResolve(node);
case Builtins::kPromiseInternalConstructor:
return ReducePromiseInternalConstructor(node);
case Builtins::kPromiseInternalReject:
return ReducePromiseInternalReject(node);
case Builtins::kPromiseInternalResolve:
return ReducePromiseInternalResolve(node);
case Builtins::kPromisePrototypeCatch: case Builtins::kPromisePrototypeCatch:
return ReducePromisePrototypeCatch(node); return ReducePromisePrototypeCatch(node);
case Builtins::kPromisePrototypeFinally: case Builtins::kPromisePrototypeFinally:
...@@ -4064,6 +4070,73 @@ Reduction JSCallReducer::ReducePromiseCapabilityDefaultResolve(Node* node) { ...@@ -4064,6 +4070,73 @@ Reduction JSCallReducer::ReducePromiseCapabilityDefaultResolve(Node* node) {
return Replace(value); 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 // ES section #sec-promise.prototype.catch
Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) { Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
......
...@@ -103,6 +103,9 @@ class JSCallReducer final : public AdvancedReducer { ...@@ -103,6 +103,9 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceAsyncFunctionPromiseRelease(Node* node); Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
Reduction ReducePromiseCapabilityDefaultReject(Node* node); Reduction ReducePromiseCapabilityDefaultReject(Node* node);
Reduction ReducePromiseCapabilityDefaultResolve(Node* node); Reduction ReducePromiseCapabilityDefaultResolve(Node* node);
Reduction ReducePromiseInternalConstructor(Node* node);
Reduction ReducePromiseInternalReject(Node* node);
Reduction ReducePromiseInternalResolve(Node* node);
Reduction ReducePromisePrototypeCatch(Node* node); Reduction ReducePromisePrototypeCatch(Node* node);
Reduction ReducePromisePrototypeFinally(Node* node); Reduction ReducePromisePrototypeFinally(Node* node);
Reduction ReducePromisePrototypeThen(Node* node); Reduction ReducePromisePrototypeThen(Node* node);
......
...@@ -185,15 +185,6 @@ extrasUtils.uncurryThis = function uncurryThis(func) { ...@@ -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) { extrasUtils.markPromiseAsHandled = function markPromiseAsHandled(promise) {
%PromiseMarkAsHandled(promise); %PromiseMarkAsHandled(promise);
}; };
......
...@@ -25563,6 +25563,123 @@ TEST(ExperimentalExtras) { ...@@ -25563,6 +25563,123 @@ TEST(ExperimentalExtras) {
CHECK_EQ(7, result->Int32Value(env.local()).FromJust()); 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) { TEST(ExtrasUtilsObject) {
LocalContext context; LocalContext context;
......
...@@ -49,6 +49,22 @@ ...@@ -49,6 +49,22 @@
arrayToTest[1] === 1 && slicedArray.length === 2 && arrayToTest[1] === 1 && slicedArray.length === 2 &&
slicedArray[0] === "c" && slicedArray[1] === 1; 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() { binding.testExtraCanUseUtils = function() {
const fulfilledPromise = v8.createPromise(); const fulfilledPromise = v8.createPromise();
v8.resolvePromise( 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