 Chromium Code Reviews
 Chromium Code Reviews Issue 2695753002:
  [ESnext] Implement Promise.prototype.finally  (Closed)
    
  
    Issue 2695753002:
  [ESnext] Implement Promise.prototype.finally  (Closed) 
  | Index: src/builtins/builtins-promise.cc | 
| diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc | 
| index 2805d611720e206cff4f031875cc2d6331672730..b0fa0d1f469fb84018e024e572056639b231fd9c 100644 | 
| --- a/src/builtins/builtins-promise.cc | 
| +++ b/src/builtins/builtins-promise.cc | 
| @@ -438,7 +438,6 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen( | 
| Bind(&if_onresolvenotcallable); | 
| { | 
| - Isolate* isolate = this->isolate(); | 
| Node* const default_resolve_handler_symbol = HeapConstant( | 
| isolate->factory()->promise_default_resolve_handler_symbol()); | 
| var_on_resolve.Bind(default_resolve_handler_symbol); | 
| @@ -1564,5 +1563,218 @@ TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) { | 
| 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, | 
| 
neis
2017/02/15 12:40:39
nit: s/Function//
 
gsathya
2017/02/16 15:05:29
Leaving as such to be consistent with CreatePromis
 | 
| + 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) { | 
| + Node* const parent = Parameter(0); | 
| 
neis
2017/02/15 12:40:39
This will typically be undefined.
 
gsathya
2017/02/16 15:05:29
Done.
 | 
| + Node* const value = Parameter(1); | 
| + Node* const context = Parameter(4); | 
| 
neis
2017/02/15 12:40:39
Maybe use CSA_ASSERT_JS_ARGC_EQ here and elsewhere
 
gsathya
2017/02/16 15:05:30
Done.
 | 
| + | 
| + 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, parent); | 
| + 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) { | 
| 
neis
2017/02/15 12:40:39
nit: s/Function//
 
gsathya
2017/02/16 15:05:29
Same as CreateValueThunkFunction
 | 
| + 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) { | 
| + Node* const parent = Parameter(0); | 
| + Node* const value = Parameter(1); | 
| 
neis
2017/02/15 12:40:39
nit: s/value/reason/
 
gsathya
2017/02/16 15:05:30
Done.
 | 
| + 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, parent); | 
| + 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(value, 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(result); | 
| 
neis
2017/02/15 12:40:39
Ouch! Return(promise_capability)
This suggests a
 
gsathya
2017/02/16 15:05:29
Done.
 | 
| +} | 
| + | 
| +TF_BUILTIN(PromiseFinally, PromiseBuiltinsAssembler) { | 
| + // 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); | 
| + | 
| + GotoIf(TaggedIsSmi(on_finally), &if_notcallable); | 
| + Node* const on_finally_map = LoadMap(on_finally); | 
| + GotoUnless(IsCallableMap(on_finally_map), &if_notcallable); | 
| + | 
| + // 3. Let thenFinally be ! CreateThenFinally(onFinally). | 
| 
neis
2017/02/15 12:40:39
The few lines above are already part of steps 3 an
 
gsathya
2017/02/16 15:05:29
Done.
 | 
| + // 4. Let catchFinally be ! CreateCatchFinally(onFinally). | 
| + 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); | 
| + { | 
| + // 1. If IsCallable(onFinally) is not true, return onFinally. | 
| + var_then_finally.Bind(on_finally); | 
| + // 1. If IsCallable(onFinally) is not true, return onFinally. | 
| 
neis
2017/02/15 12:40:39
I find these comments here confusing, since they a
 
gsathya
2017/02/16 15:05:29
Yeah, that's fair. Removed
 | 
| + var_catch_finally.Bind(on_finally); | 
| + Goto(&perform_finally); | 
| + } | 
| + | 
| + Bind(&perform_finally); | 
| + Label if_nativepromise(this), if_custompromise(this, Label::kDeferred); | 
| + GotoIf(TaggedIsSmi(promise), &if_custompromise); | 
| 
neis
2017/02/15 12:40:39
We already know that promise is a JS_PROMISE objec
 
gsathya
2017/02/16 15:05:29
Done.
 | 
| + BranchIfFastPath(context, promise, &if_nativepromise, &if_custompromise); | 
| + | 
| 
neis
2017/02/15 12:40:39
The step 5 comment should appear here.
 
gsathya
2017/02/16 15:05:29
Done.
 | 
| + 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 v8 |