[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:
parent
0405a54127
commit
13ca9a0fd1
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user