Commit 18ad0f13 authored by gsathya's avatar gsathya Committed by Commit bot

[ESnext] Implement Promise.prototype.finally

Adds five new TF builtins for the spec defined functions/closures. This follows
mechanism similar to promise resolving functions approach where we store the
closure variables in a custom context.

Adds a new --harmony-promise-finally flag.

BUG=v8:5967

Review-Url: https://codereview.chromium.org/2695753002
Cr-Commit-Position: refs/heads/master@{#43294}
parent 00a379a0
...@@ -3619,6 +3619,67 @@ void Genesis::InitializeGlobal_harmony_async_iteration() { ...@@ -3619,6 +3619,67 @@ void Genesis::InitializeGlobal_harmony_async_iteration() {
factory()->async_iterator_symbol()); factory()->async_iterator_symbol());
} }
void Genesis::InitializeGlobal_harmony_promise_finally() {
if (!FLAG_harmony_promise_finally) return;
Handle<JSFunction> constructor(native_context()->promise_function());
Handle<JSObject> prototype(JSObject::cast(constructor->instance_prototype()));
SimpleInstallFunction(prototype, "finally", Builtins::kPromiseFinally, 1,
true, DONT_ENUM);
// The promise prototype map has changed because we added a property
// to prototype, so we update the saved map.
Handle<Map> prototype_map(prototype->map());
Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate());
native_context()->set_promise_prototype_map(*prototype_map);
{
Handle<Code> code =
handle(isolate()->builtins()->builtin(Builtins::kPromiseThenFinally),
isolate());
Handle<SharedFunctionInfo> info = factory()->NewSharedFunctionInfo(
factory()->empty_string(), code, false);
info->set_internal_formal_parameter_count(1);
info->set_length(1);
info->set_native(true);
native_context()->set_promise_then_finally_shared_fun(*info);
}
{
Handle<Code> code =
handle(isolate()->builtins()->builtin(Builtins::kPromiseCatchFinally),
isolate());
Handle<SharedFunctionInfo> info = factory()->NewSharedFunctionInfo(
factory()->empty_string(), code, false);
info->set_internal_formal_parameter_count(1);
info->set_length(1);
info->set_native(true);
native_context()->set_promise_catch_finally_shared_fun(*info);
}
{
Handle<Code> code = handle(
isolate()->builtins()->builtin(Builtins::kPromiseValueThunkFinally),
isolate());
Handle<SharedFunctionInfo> info = factory()->NewSharedFunctionInfo(
factory()->empty_string(), code, false);
info->set_internal_formal_parameter_count(0);
info->set_length(0);
native_context()->set_promise_value_thunk_finally_shared_fun(*info);
}
{
Handle<Code> code =
handle(isolate()->builtins()->builtin(Builtins::kPromiseThrowerFinally),
isolate());
Handle<SharedFunctionInfo> info = factory()->NewSharedFunctionInfo(
factory()->empty_string(), code, false);
info->set_internal_formal_parameter_count(0);
info->set_length(0);
native_context()->set_promise_thrower_finally_shared_fun(*info);
}
}
#ifdef V8_I18N_SUPPORT #ifdef V8_I18N_SUPPORT
void Genesis::InitializeGlobal_datetime_format_to_parts() { void Genesis::InitializeGlobal_datetime_format_to_parts() {
if (!FLAG_datetime_format_to_parts) return; if (!FLAG_datetime_format_to_parts) return;
...@@ -4153,6 +4214,7 @@ bool Genesis::InstallExperimentalNatives() { ...@@ -4153,6 +4214,7 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_object_rest_spread_natives[] = {nullptr}; static const char* harmony_object_rest_spread_natives[] = {nullptr};
static const char* harmony_async_iteration_natives[] = {nullptr}; static const char* harmony_async_iteration_natives[] = {nullptr};
static const char* harmony_dynamic_import_natives[] = {nullptr}; static const char* harmony_dynamic_import_natives[] = {nullptr};
static const char* harmony_promise_finally_natives[] = {nullptr};
for (int i = ExperimentalNatives::GetDebuggerCount(); for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) { i < ExperimentalNatives::GetBuiltinsCount(); i++) {
......
...@@ -438,7 +438,6 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen( ...@@ -438,7 +438,6 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen(
Bind(&if_onresolvenotcallable); Bind(&if_onresolvenotcallable);
{ {
Isolate* isolate = this->isolate();
Node* const default_resolve_handler_symbol = HeapConstant( Node* const default_resolve_handler_symbol = HeapConstant(
isolate->factory()->promise_default_resolve_handler_symbol()); isolate->factory()->promise_default_resolve_handler_symbol());
var_on_resolve.Bind(default_resolve_handler_symbol); var_on_resolve.Bind(default_resolve_handler_symbol);
...@@ -1563,5 +1562,221 @@ TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) { ...@@ -1563,5 +1562,221 @@ TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) {
Return(UndefinedConstant()); Return(UndefinedConstant());
} }
Node* PromiseBuiltinsAssembler::CreatePromiseFinallyContext(
Node* on_finally, Node* native_context) {
Node* const context =
CreatePromiseContext(native_context, kOnFinallyContextLength);
StoreContextElementNoWriteBarrier(context, kOnFinallySlot, on_finally);
return context;
}
std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions(
Node* on_finally, Node* native_context) {
Node* const promise_context =
CreatePromiseFinallyContext(on_finally, native_context);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const then_finally_info = LoadContextElement(
native_context, Context::PROMISE_THEN_FINALLY_SHARED_FUN);
Node* const then_finally = AllocateFunctionWithMapAndContext(
map, then_finally_info, promise_context);
Node* const catch_finally_info = LoadContextElement(
native_context, Context::PROMISE_CATCH_FINALLY_SHARED_FUN);
Node* const catch_finally = AllocateFunctionWithMapAndContext(
map, catch_finally_info, promise_context);
return std::make_pair(then_finally, catch_finally);
}
TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler) {
Node* const context = Parameter(3);
Node* const value = LoadContextElement(context, kOnFinallySlot);
Return(value);
}
Node* PromiseBuiltinsAssembler::CreateValueThunkFunctionContext(
Node* value, Node* native_context) {
Node* const context =
CreatePromiseContext(native_context, kOnFinallyContextLength);
StoreContextElementNoWriteBarrier(context, kOnFinallySlot, value);
return context;
}
Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value,
Node* native_context) {
Node* const value_thunk_context =
CreateValueThunkFunctionContext(value, native_context);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const value_thunk_info = LoadContextElement(
native_context, Context::PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN);
Node* const value_thunk = AllocateFunctionWithMapAndContext(
map, value_thunk_info, value_thunk_context);
return value_thunk;
}
TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
Node* const value = Parameter(1);
Node* const context = Parameter(4);
Node* const on_finally = LoadContextElement(context, kOnFinallySlot);
// 2.a Let result be ? Call(onFinally, undefined).
Callable call_callable = CodeFactory::Call(isolate());
Node* result =
CallJS(call_callable, context, on_finally, UndefinedConstant());
// 2.b Let promise be ! PromiseResolve( %Promise%, result).
Node* const promise = AllocateAndInitJSPromise(context);
InternalResolvePromise(context, promise, result);
// 2.c Let valueThunk be equivalent to a function that returns value.
Node* native_context = LoadNativeContext(context);
Node* const value_thunk = CreateValueThunkFunction(value, native_context);
// 2.d Let promiseCapability be ! NewPromiseCapability( %Promise%).
Node* const promise_capability = AllocateAndInitJSPromise(context, promise);
// 2.e Return PerformPromiseThen(promise, valueThunk, undefined,
// promiseCapability).
InternalPerformPromiseThen(context, promise, value_thunk, UndefinedConstant(),
promise_capability, UndefinedConstant(),
UndefinedConstant());
Return(promise_capability);
}
TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) {
Node* const context = Parameter(3);
Node* const reason = LoadContextElement(context, kOnFinallySlot);
CallRuntime(Runtime::kThrow, context, reason);
Return(UndefinedConstant());
}
Node* PromiseBuiltinsAssembler::CreateThrowerFunctionContext(
Node* reason, Node* native_context) {
Node* const context =
CreatePromiseContext(native_context, kOnFinallyContextLength);
StoreContextElementNoWriteBarrier(context, kOnFinallySlot, reason);
return context;
}
Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason,
Node* native_context) {
Node* const thrower_context =
CreateThrowerFunctionContext(reason, native_context);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const thrower_info = LoadContextElement(
native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN);
Node* const thrower =
AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context);
return thrower;
}
TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
Node* const reason = Parameter(1);
Node* const context = Parameter(4);
Node* const on_finally = LoadContextElement(context, kOnFinallySlot);
// 2.a Let result be ? Call(onFinally, undefined).
Callable call_callable = CodeFactory::Call(isolate());
Node* result =
CallJS(call_callable, context, on_finally, UndefinedConstant());
// 2.b Let promise be ! PromiseResolve( %Promise%, result).
Node* const promise = AllocateAndInitJSPromise(context);
InternalResolvePromise(context, promise, result);
// 2.c Let thrower be equivalent to a function that throws reason.
Node* native_context = LoadNativeContext(context);
Node* const thrower = CreateThrowerFunction(reason, native_context);
// 2.d Let promiseCapability be ! NewPromiseCapability( %Promise%).
Node* const promise_capability = AllocateAndInitJSPromise(context, promise);
// 2.e Return PerformPromiseThen(promise, thrower, undefined,
// promiseCapability).
InternalPerformPromiseThen(context, promise, thrower, UndefinedConstant(),
promise_capability, UndefinedConstant(),
UndefinedConstant());
Return(promise_capability);
}
TF_BUILTIN(PromiseFinally, PromiseBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
// 1. Let promise be the this value.
Node* const promise = Parameter(0);
Node* const on_finally = Parameter(1);
Node* const context = Parameter(4);
// 2. If IsPromise(promise) is false, throw a TypeError exception.
ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
"Promise.prototype.finally");
Variable var_then_finally(this, MachineRepresentation::kTagged),
var_catch_finally(this, MachineRepresentation::kTagged);
Label if_notcallable(this, Label::kDeferred), perform_finally(this);
// 3. Let thenFinally be ! CreateThenFinally(onFinally).
// 4. Let catchFinally be ! CreateCatchFinally(onFinally).
GotoIf(TaggedIsSmi(on_finally), &if_notcallable);
Node* const on_finally_map = LoadMap(on_finally);
GotoIfNot(IsCallableMap(on_finally_map), &if_notcallable);
Node* const native_context = LoadNativeContext(context);
Node* then_finally = nullptr;
Node* catch_finally = nullptr;
std::tie(then_finally, catch_finally) =
CreatePromiseFinallyFunctions(on_finally, native_context);
var_then_finally.Bind(then_finally);
var_catch_finally.Bind(catch_finally);
Goto(&perform_finally);
Bind(&if_notcallable);
{
var_then_finally.Bind(on_finally);
var_catch_finally.Bind(on_finally);
Goto(&perform_finally);
}
// 5. Return PerformPromiseThen(promise, valueThunk, undefined,
// promiseCapability).
Bind(&perform_finally);
Label if_nativepromise(this), if_custompromise(this, Label::kDeferred);
BranchIfFastPath(context, promise, &if_nativepromise, &if_custompromise);
Bind(&if_nativepromise);
{
Node* deferred_promise = AllocateAndInitJSPromise(context, promise);
InternalPerformPromiseThen(context, promise, var_then_finally.value(),
var_catch_finally.value(), deferred_promise,
UndefinedConstant(), UndefinedConstant());
Return(deferred_promise);
}
Bind(&if_custompromise);
{
Isolate* isolate = this->isolate();
Node* const then_str = HeapConstant(isolate->factory()->then_string());
Callable getproperty_callable = CodeFactory::GetProperty(isolate);
Node* const then =
CallStub(getproperty_callable, context, promise, then_str);
Callable call_callable = CodeFactory::Call(isolate);
// 5. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
Node* const result =
CallJS(call_callable, context, then, promise, var_then_finally.value(),
var_catch_finally.value());
Return(result);
}
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -36,6 +36,18 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -36,6 +36,18 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
kCapabilitiesContextLength, kCapabilitiesContextLength,
}; };
// This is used by the PromiseThenFinally and PromiseCatchFinally
// builtins to store the onFinally in the onFinallySlot.
//
// This is also used by the PromiseValueThunkFinally to store the
// value in the onFinallySlot and PromiseThrowerFinally to store the
// reason in the onFinallySlot.
enum PromiseFinallyContextSlot {
kOnFinallySlot = Context::MIN_CONTEXT_SLOTS,
kOnFinallyContextLength,
};
explicit PromiseBuiltinsAssembler(CodeAssemblerState* state) explicit PromiseBuiltinsAssembler(CodeAssemblerState* state)
: CodeStubAssembler(state) {} : CodeStubAssembler(state) {}
// These allocate and initialize a promise with pending state and // These allocate and initialize a promise with pending state and
...@@ -115,6 +127,15 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -115,6 +127,15 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
bool debug_event); bool debug_event);
void InternalPromiseReject(Node* context, Node* promise, Node* value, void InternalPromiseReject(Node* context, Node* promise, Node* value,
Node* debug_event); Node* debug_event);
std::pair<Node*, Node*> CreatePromiseFinallyFunctions(Node* on_finally,
Node* native_context);
Node* CreatePromiseFinallyContext(Node* on_finally, Node* native_context);
Node* CreateValueThunkFunction(Node* value, Node* native_context);
Node* CreateValueThunkFunctionContext(Node* value, Node* native_context);
Node* CreateThrowerFunctionContext(Node* reason, Node* native_context);
Node* CreateThrowerFunction(Node* reason, Node* native_context);
private: private:
Node* AllocateJSPromise(Node* context); Node* AllocateJSPromise(Node* context);
......
...@@ -666,6 +666,11 @@ class Isolate; ...@@ -666,6 +666,11 @@ class Isolate;
TFJ(PromiseResolve, 1) \ TFJ(PromiseResolve, 1) \
TFJ(PromiseReject, 1) \ TFJ(PromiseReject, 1) \
TFJ(InternalPromiseReject, 3) \ TFJ(InternalPromiseReject, 3) \
TFJ(PromiseFinally, 1) \
TFJ(PromiseThenFinally, 1) \
TFJ(PromiseCatchFinally, 1) \
TFJ(PromiseValueThunkFinally, 0) \
TFJ(PromiseThrowerFinally, 0) \
\ \
/* Proxy */ \ /* Proxy */ \
CPP(ProxyConstructor) \ CPP(ProxyConstructor) \
......
...@@ -290,6 +290,14 @@ enum ContextLookupFlags { ...@@ -290,6 +290,14 @@ enum ContextLookupFlags {
V(PROMISE_RESOLVE_SHARED_FUN, SharedFunctionInfo, \ V(PROMISE_RESOLVE_SHARED_FUN, SharedFunctionInfo, \
promise_resolve_shared_fun) \ promise_resolve_shared_fun) \
V(PROMISE_REJECT_SHARED_FUN, SharedFunctionInfo, promise_reject_shared_fun) \ V(PROMISE_REJECT_SHARED_FUN, SharedFunctionInfo, promise_reject_shared_fun) \
V(PROMISE_THEN_FINALLY_SHARED_FUN, SharedFunctionInfo, \
promise_then_finally_shared_fun) \
V(PROMISE_CATCH_FINALLY_SHARED_FUN, SharedFunctionInfo, \
promise_catch_finally_shared_fun) \
V(PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN, SharedFunctionInfo, \
promise_value_thunk_finally_shared_fun) \
V(PROMISE_THROWER_FINALLY_SHARED_FUN, SharedFunctionInfo, \
promise_thrower_finally_shared_fun) \
V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \ V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
......
...@@ -205,7 +205,8 @@ DEFINE_IMPLICATION(es_staging, move_object_start) ...@@ -205,7 +205,8 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
V(harmony_function_tostring, "harmony Function.prototype.toString") \ V(harmony_function_tostring, "harmony Function.prototype.toString") \
V(harmony_class_fields, "harmony public fields in class literals") \ V(harmony_class_fields, "harmony public fields in class literals") \
V(harmony_async_iteration, "harmony async iteration") \ V(harmony_async_iteration, "harmony async iteration") \
V(harmony_dynamic_import, "harmony dynamic import") V(harmony_dynamic_import, "harmony dynamic import") \
V(harmony_promise_finally, "harmony Promise.prototype.finally")
// Features that are complete (but still behind --harmony/es-staging flag). // Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) \ #define HARMONY_STAGED(V) \
......
...@@ -500,6 +500,7 @@ void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT ...@@ -500,6 +500,7 @@ void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT
os << "\n - fulfill_reactions = " << Brief(fulfill_reactions()); os << "\n - fulfill_reactions = " << Brief(fulfill_reactions());
os << "\n - reject_reactions = " << Brief(reject_reactions()); os << "\n - reject_reactions = " << Brief(reject_reactions());
os << "\n - has_handler = " << has_handler(); os << "\n - has_handler = " << has_handler();
os << "\n ";
} }
void JSRegExp::JSRegExpPrint(std::ostream& os) { // NOLINT void JSRegExp::JSRegExpPrint(std::ostream& os) { // NOLINT
......
This diff is collapsed.
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