| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include 'src/builtins/builtins-promise.h' |
| #include 'src/builtins/builtins-promise-gen.h' |
| |
| namespace promise { |
| |
| type PromiseValueThunkOrReasonContext extends FunctionContext; |
| extern enum PromiseValueThunkOrReasonContextSlot extends intptr |
| constexpr 'PromiseBuiltins::PromiseValueThunkOrReasonContextSlot' { |
| kValueSlot: Slot<PromiseValueThunkOrReasonContext, JSAny>, |
| kPromiseValueThunkOrReasonContextLength |
| } |
| |
| type PromiseFinallyContext extends FunctionContext; |
| extern enum PromiseFinallyContextSlot extends intptr |
| constexpr 'PromiseBuiltins::PromiseFinallyContextSlot' { |
| kOnFinallySlot: Slot<PromiseFinallyContext, Callable>, |
| kConstructorSlot: Slot<PromiseFinallyContext, Constructor>, |
| kPromiseFinallyContextLength |
| } |
| |
| transitioning javascript builtin |
| PromiseValueThunkFinally( |
| js-implicit context: Context, receiver: JSAny)(): JSAny { |
| const context = %RawDownCast<PromiseValueThunkOrReasonContext>(context); |
| return *ContextSlot( |
| context, PromiseValueThunkOrReasonContextSlot::kValueSlot); |
| } |
| |
| transitioning javascript builtin |
| PromiseThrowerFinally(js-implicit context: Context, receiver: JSAny)(): never { |
| const context = %RawDownCast<PromiseValueThunkOrReasonContext>(context); |
| const reason = |
| *ContextSlot(context, PromiseValueThunkOrReasonContextSlot::kValueSlot); |
| Throw(reason); |
| } |
| |
| macro CreateThrowerFunction(implicit context: Context)( |
| nativeContext: NativeContext, reason: JSAny): JSFunction { |
| const throwerContext = %RawDownCast<PromiseValueThunkOrReasonContext>( |
| AllocateSyntheticFunctionContext( |
| nativeContext, |
| PromiseValueThunkOrReasonContextSlot:: |
| kPromiseValueThunkOrReasonContextLength)); |
| InitContextSlot( |
| throwerContext, PromiseValueThunkOrReasonContextSlot::kValueSlot, reason); |
| const map = *ContextSlot( |
| nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); |
| const throwerInfo = PromiseThrowerFinallySharedFunConstant(); |
| return AllocateFunctionWithMapAndContext(map, throwerInfo, throwerContext); |
| } |
| |
| transitioning javascript builtin |
| PromiseCatchFinally( |
| js-implicit context: Context, receiver: JSAny)(reason: JSAny): JSAny { |
| const context = %RawDownCast<PromiseFinallyContext>(context); |
| // 1. Let onFinally be F.[[OnFinally]]. |
| // 2. Assert: IsCallable(onFinally) is true. |
| const onFinally: Callable = |
| *ContextSlot(context, PromiseFinallyContextSlot::kOnFinallySlot); |
| |
| // 3. Let result be ? Call(onFinally). |
| const result = Call(context, onFinally, Undefined); |
| |
| // 4. Let C be F.[[Constructor]]. |
| const constructor: Constructor = |
| *ContextSlot(context, PromiseFinallyContextSlot::kConstructorSlot); |
| |
| // 5. Assert: IsConstructor(C) is true. |
| assert(IsConstructor(constructor)); |
| |
| // 6. Let promise be ? PromiseResolve(C, result). |
| const promise = PromiseResolve(constructor, result); |
| |
| // 7. Let thrower be equivalent to a function that throws reason. |
| const nativeContext = LoadNativeContext(context); |
| const thrower = CreateThrowerFunction(nativeContext, reason); |
| |
| // 8. Return ? Invoke(promise, "then", « thrower »). |
| return UnsafeCast<JSAny>(InvokeThen(nativeContext, promise, thrower)); |
| } |
| |
| macro CreateValueThunkFunction(implicit context: Context)( |
| nativeContext: NativeContext, value: JSAny): JSFunction { |
| const valueThunkContext = %RawDownCast<PromiseValueThunkOrReasonContext>( |
| AllocateSyntheticFunctionContext( |
| nativeContext, |
| PromiseValueThunkOrReasonContextSlot:: |
| kPromiseValueThunkOrReasonContextLength)); |
| InitContextSlot( |
| valueThunkContext, PromiseValueThunkOrReasonContextSlot::kValueSlot, |
| value); |
| const map = *ContextSlot( |
| nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); |
| const valueThunkInfo = PromiseValueThunkFinallySharedFunConstant(); |
| return AllocateFunctionWithMapAndContext( |
| map, valueThunkInfo, valueThunkContext); |
| } |
| |
| transitioning javascript builtin |
| PromiseThenFinally( |
| js-implicit context: Context, receiver: JSAny)(value: JSAny): JSAny { |
| const context = %RawDownCast<PromiseFinallyContext>(context); |
| // 1. Let onFinally be F.[[OnFinally]]. |
| // 2. Assert: IsCallable(onFinally) is true. |
| const onFinally = |
| *ContextSlot(context, PromiseFinallyContextSlot::kOnFinallySlot); |
| |
| // 3. Let result be ? Call(onFinally). |
| const result = Call(context, onFinally, Undefined); |
| |
| // 4. Let C be F.[[Constructor]]. |
| const constructor = |
| *ContextSlot(context, PromiseFinallyContextSlot::kConstructorSlot); |
| |
| // 5. Assert: IsConstructor(C) is true. |
| assert(IsConstructor(constructor)); |
| |
| // 6. Let promise be ? PromiseResolve(C, result). |
| const promise = PromiseResolve(constructor, result); |
| |
| // 7. Let valueThunk be equivalent to a function that returns value. |
| const nativeContext = LoadNativeContext(context); |
| const valueThunk = CreateValueThunkFunction(nativeContext, value); |
| |
| // 8. Return ? Invoke(promise, "then", « valueThunk »). |
| return UnsafeCast<JSAny>(InvokeThen(nativeContext, promise, valueThunk)); |
| } |
| |
| struct PromiseFinallyFunctions { |
| then_finally: JSFunction; |
| catch_finally: JSFunction; |
| } |
| |
| macro CreatePromiseFinallyFunctions(implicit context: Context)( |
| nativeContext: NativeContext, onFinally: Callable, |
| constructor: Constructor): PromiseFinallyFunctions { |
| const promiseContext = |
| %RawDownCast<PromiseFinallyContext>(AllocateSyntheticFunctionContext( |
| nativeContext, |
| PromiseFinallyContextSlot::kPromiseFinallyContextLength)); |
| InitContextSlot( |
| promiseContext, PromiseFinallyContextSlot::kOnFinallySlot, onFinally); |
| InitContextSlot( |
| promiseContext, PromiseFinallyContextSlot::kConstructorSlot, constructor); |
| const map = *ContextSlot( |
| nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); |
| const thenFinallyInfo = PromiseThenFinallySharedFunConstant(); |
| const thenFinally = |
| AllocateFunctionWithMapAndContext(map, thenFinallyInfo, promiseContext); |
| const catchFinallyInfo = PromiseCatchFinallySharedFunConstant(); |
| const catchFinally = |
| AllocateFunctionWithMapAndContext(map, catchFinallyInfo, promiseContext); |
| return PromiseFinallyFunctions{ |
| then_finally: thenFinally, |
| catch_finally: catchFinally |
| }; |
| } |
| |
| transitioning javascript builtin |
| PromisePrototypeFinally( |
| js-implicit context: Context, receiver: JSAny)(onFinally: JSAny): JSAny { |
| // 1. Let promise be the this value. |
| // 2. If Type(promise) is not Object, throw a TypeError exception. |
| const jsReceiver = Cast<JSReceiver>(receiver) otherwise ThrowTypeError( |
| MessageTemplate::kCalledOnNonObject, 'Promise.prototype.finally'); |
| |
| // 3. Let C be ? SpeciesConstructor(promise, %Promise%). |
| const nativeContext = LoadNativeContext(context); |
| const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX); |
| |
| let constructor: Constructor = UnsafeCast<Constructor>(promiseFun); |
| const receiverMap = jsReceiver.map; |
| if (!IsJSPromiseMap(receiverMap) || |
| !IsPromiseSpeciesLookupChainIntact(nativeContext, receiverMap)) |
| deferred { |
| constructor = |
| UnsafeCast<Constructor>(SpeciesConstructor(jsReceiver, promiseFun)); |
| } |
| |
| // 4. Assert: IsConstructor(C) is true. |
| assert(IsConstructor(constructor)); |
| |
| // 5. If IsCallable(onFinally) is not true, |
| // a. Let thenFinally be onFinally. |
| // b. Let catchFinally be onFinally. |
| // 6. Else, |
| // a. Let thenFinally be a new built-in function object as defined |
| // in ThenFinally Function. |
| // b. Let catchFinally be a new built-in function object as |
| // defined in CatchFinally Function. |
| // c. Set thenFinally and catchFinally's [[Constructor]] internal |
| // slots to C. |
| // d. Set thenFinally and catchFinally's [[OnFinally]] internal |
| // slots to onFinally. |
| let thenFinally: JSAny; |
| let catchFinally: JSAny; |
| typeswitch (onFinally) { |
| case (onFinally: Callable): { |
| const pair = |
| CreatePromiseFinallyFunctions(nativeContext, onFinally, constructor); |
| thenFinally = pair.then_finally; |
| catchFinally = pair.catch_finally; |
| } |
| case (JSAny): deferred { |
| thenFinally = onFinally; |
| catchFinally = onFinally; |
| } |
| } |
| |
| // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). |
| return UnsafeCast<JSAny>( |
| InvokeThen(nativeContext, receiver, thenFinally, catchFinally)); |
| } |
| |
| extern macro PromiseCatchFinallySharedFunConstant(): SharedFunctionInfo; |
| extern macro PromiseThenFinallySharedFunConstant(): SharedFunctionInfo; |
| extern macro PromiseThrowerFinallySharedFunConstant(): SharedFunctionInfo; |
| extern macro PromiseValueThunkFinallySharedFunConstant(): SharedFunctionInfo; |
| } |