diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 8f598ce035..3a5259eaec 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -4608,12 +4608,30 @@ bool Genesis::InstallNatives(GlobalContextType context_type) { InstallInternalArray(extras_utils, "InternalPackedArray", PACKED_ELEMENTS); + // v8.createPromise(parent) Handle 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 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 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")); diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 998b944257..880bd2ed51 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -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) \ diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc index d9639d82ec..ba15b5db50 100644 --- a/src/builtins/builtins-promise-gen.cc +++ b/src/builtins/builtins-promise-gen.cc @@ -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) { diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index b3ccab5954..d2255b0627 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -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()); diff --git a/src/compiler/js-call-reducer.h b/src/compiler/js-call-reducer.h index 12919c5335..9ddf17e8a0 100644 --- a/src/compiler/js-call-reducer.h +++ b/src/compiler/js-call-reducer.h @@ -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); diff --git a/src/js/prologue.js b/src/js/prologue.js index ed57fe3c9f..0d6b670367 100644 --- a/src/js/prologue.js +++ b/src/js/prologue.js @@ -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); }; diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 34464e13f8..1681a32ae4 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -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 binding = env->GetExtrasBindingObject(); + + auto func = binding->Get(env.local(), v8_str("testCreatePromise")) + .ToLocalChecked() + .As(); + CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust()); + + auto promise = CompileRun( + "func();\n" + "func();\n" + "%OptimizeFunctionOnNextCall(func);\n" + "func()\n") + .As(); + 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 binding = env->GetExtrasBindingObject(); + + auto func = binding->Get(env.local(), v8_str("testCreatePromiseWithParent")) + .ToLocalChecked() + .As(); + 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(); + 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 binding = env->GetExtrasBindingObject(); + + auto func = binding->Get(env.local(), v8_str("testRejectPromise")) + .ToLocalChecked() + .As(); + 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(); + 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 binding = env->GetExtrasBindingObject(); + + auto func = binding->Get(env.local(), v8_str("testResolvePromise")) + .ToLocalChecked() + .As(); + 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(); + 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(); + CHECK_EQ(v8::Promise::kFulfilled, fulfilled_promise->State()); + CHECK_EQ(1, fulfilled_promise->Result()->Int32Value(env.local()).FromJust()); +} TEST(ExtrasUtilsObject) { LocalContext context; diff --git a/test/cctest/test-extra.js b/test/cctest/test-extra.js index 6c94c87d05..9b17e401a2 100644 --- a/test/cctest/test-extra.js +++ b/test/cctest/test-extra.js @@ -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(