1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include 'src/builtins/builtins-promise.h'
6#include 'src/builtins/builtins-promise-gen.h'
7
8namespace promise {
9
10type PromiseValueThunkOrReasonContext extends FunctionContext;
11extern enum PromiseValueThunkOrReasonContextSlot extends intptr
12constexpr 'PromiseBuiltins::PromiseValueThunkOrReasonContextSlot' {
13  kValueSlot: Slot<PromiseValueThunkOrReasonContext, JSAny>,
14  kPromiseValueThunkOrReasonContextLength
15}
16
17type PromiseFinallyContext extends FunctionContext;
18extern enum PromiseFinallyContextSlot extends intptr
19constexpr 'PromiseBuiltins::PromiseFinallyContextSlot' {
20  kOnFinallySlot: Slot<PromiseFinallyContext, Callable>,
21  kConstructorSlot: Slot<PromiseFinallyContext, Constructor>,
22  kPromiseFinallyContextLength
23}
24
25transitioning javascript builtin
26PromiseValueThunkFinally(
27    js-implicit context: Context, receiver: JSAny)(): JSAny {
28  const context = %RawDownCast<PromiseValueThunkOrReasonContext>(context);
29  return *ContextSlot(
30      context, PromiseValueThunkOrReasonContextSlot::kValueSlot);
31}
32
33transitioning javascript builtin
34PromiseThrowerFinally(js-implicit context: Context, receiver: JSAny)(): never {
35  const context = %RawDownCast<PromiseValueThunkOrReasonContext>(context);
36  const reason =
37      *ContextSlot(context, PromiseValueThunkOrReasonContextSlot::kValueSlot);
38  Throw(reason);
39}
40
41macro CreateThrowerFunction(implicit context: Context)(
42    nativeContext: NativeContext, reason: JSAny): JSFunction {
43  const throwerContext = %RawDownCast<PromiseValueThunkOrReasonContext>(
44      AllocateSyntheticFunctionContext(
45          nativeContext,
46          PromiseValueThunkOrReasonContextSlot::
47              kPromiseValueThunkOrReasonContextLength));
48  InitContextSlot(
49      throwerContext, PromiseValueThunkOrReasonContextSlot::kValueSlot, reason);
50  const map = *ContextSlot(
51      nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
52  const throwerInfo = PromiseThrowerFinallySharedFunConstant();
53  return AllocateFunctionWithMapAndContext(map, throwerInfo, throwerContext);
54}
55
56transitioning javascript builtin
57PromiseCatchFinally(
58    js-implicit context: Context, receiver: JSAny)(reason: JSAny): JSAny {
59  const context = %RawDownCast<PromiseFinallyContext>(context);
60  // 1. Let onFinally be F.[[OnFinally]].
61  // 2. Assert: IsCallable(onFinally) is true.
62  const onFinally: Callable =
63      *ContextSlot(context, PromiseFinallyContextSlot::kOnFinallySlot);
64
65  // 3. Let result be ? Call(onFinally).
66  const result = Call(context, onFinally, Undefined);
67
68  // 4. Let C be F.[[Constructor]].
69  const constructor: Constructor =
70      *ContextSlot(context, PromiseFinallyContextSlot::kConstructorSlot);
71
72  // 5. Assert: IsConstructor(C) is true.
73  dcheck(IsConstructor(constructor));
74
75  // 6. Let promise be ? PromiseResolve(C, result).
76  const promise = PromiseResolve(constructor, result);
77
78  // 7. Let thrower be equivalent to a function that throws reason.
79  const nativeContext = LoadNativeContext(context);
80  const thrower = CreateThrowerFunction(nativeContext, reason);
81
82  // 8. Return ? Invoke(promise, "then", « thrower »).
83  return UnsafeCast<JSAny>(InvokeThen(nativeContext, promise, thrower));
84}
85
86macro CreateValueThunkFunction(implicit context: Context)(
87    nativeContext: NativeContext, value: JSAny): JSFunction {
88  const valueThunkContext = %RawDownCast<PromiseValueThunkOrReasonContext>(
89      AllocateSyntheticFunctionContext(
90          nativeContext,
91          PromiseValueThunkOrReasonContextSlot::
92              kPromiseValueThunkOrReasonContextLength));
93  InitContextSlot(
94      valueThunkContext, PromiseValueThunkOrReasonContextSlot::kValueSlot,
95      value);
96  const map = *ContextSlot(
97      nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
98  const valueThunkInfo = PromiseValueThunkFinallySharedFunConstant();
99  return AllocateFunctionWithMapAndContext(
100      map, valueThunkInfo, valueThunkContext);
101}
102
103transitioning javascript builtin
104PromiseThenFinally(
105    js-implicit context: Context, receiver: JSAny)(value: JSAny): JSAny {
106  const context = %RawDownCast<PromiseFinallyContext>(context);
107  // 1. Let onFinally be F.[[OnFinally]].
108  // 2.  Assert: IsCallable(onFinally) is true.
109  const onFinally =
110      *ContextSlot(context, PromiseFinallyContextSlot::kOnFinallySlot);
111
112  // 3. Let result be ?  Call(onFinally).
113  const result = Call(context, onFinally, Undefined);
114
115  // 4. Let C be F.[[Constructor]].
116  const constructor =
117      *ContextSlot(context, PromiseFinallyContextSlot::kConstructorSlot);
118
119  // 5. Assert: IsConstructor(C) is true.
120  dcheck(IsConstructor(constructor));
121
122  // 6. Let promise be ? PromiseResolve(C, result).
123  const promise = PromiseResolve(constructor, result);
124
125  // 7. Let valueThunk be equivalent to a function that returns value.
126  const nativeContext = LoadNativeContext(context);
127  const valueThunk = CreateValueThunkFunction(nativeContext, value);
128
129  // 8. Return ? Invoke(promise, "then", « valueThunk »).
130  return UnsafeCast<JSAny>(InvokeThen(nativeContext, promise, valueThunk));
131}
132
133struct PromiseFinallyFunctions {
134  then_finally: JSFunction;
135  catch_finally: JSFunction;
136}
137
138macro CreatePromiseFinallyFunctions(implicit context: Context)(
139    nativeContext: NativeContext, onFinally: Callable,
140    constructor: Constructor): PromiseFinallyFunctions {
141  const promiseContext =
142      %RawDownCast<PromiseFinallyContext>(AllocateSyntheticFunctionContext(
143          nativeContext,
144          PromiseFinallyContextSlot::kPromiseFinallyContextLength));
145  InitContextSlot(
146      promiseContext, PromiseFinallyContextSlot::kOnFinallySlot, onFinally);
147  InitContextSlot(
148      promiseContext, PromiseFinallyContextSlot::kConstructorSlot, constructor);
149  const map = *ContextSlot(
150      nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
151  const thenFinallyInfo = PromiseThenFinallySharedFunConstant();
152  const thenFinally =
153      AllocateFunctionWithMapAndContext(map, thenFinallyInfo, promiseContext);
154  const catchFinallyInfo = PromiseCatchFinallySharedFunConstant();
155  const catchFinally =
156      AllocateFunctionWithMapAndContext(map, catchFinallyInfo, promiseContext);
157  return PromiseFinallyFunctions{
158    then_finally: thenFinally,
159    catch_finally: catchFinally
160  };
161}
162
163// https://tc39.es/ecma262/#sec-promise.prototype.finally
164transitioning javascript builtin
165PromisePrototypeFinally(
166    js-implicit context: Context, receiver: JSAny)(onFinally: JSAny): JSAny {
167  // 1. Let promise be the this value.
168  // 2. If Type(promise) is not Object, throw a TypeError exception.
169  const jsReceiver = Cast<JSReceiver>(receiver) otherwise ThrowTypeError(
170      MessageTemplate::kCalledOnNonObject, 'Promise.prototype.finally');
171
172  // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
173  // This builtin is attached to JSFunction created by the bootstrapper so
174  // `context` is the native context.
175  check(Is<NativeContext>(context));
176  const nativeContext = UnsafeCast<NativeContext>(context);
177  const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);
178
179  let constructor: Constructor = UnsafeCast<Constructor>(promiseFun);
180  const receiverMap = jsReceiver.map;
181  if (!IsJSPromiseMap(receiverMap) ||
182      !IsPromiseSpeciesLookupChainIntact(nativeContext, receiverMap))
183    deferred {
184      constructor =
185          UnsafeCast<Constructor>(SpeciesConstructor(jsReceiver, promiseFun));
186    }
187
188  // 4. Assert: IsConstructor(C) is true.
189  dcheck(IsConstructor(constructor));
190
191  // 5. If IsCallable(onFinally) is not true,
192  //    a. Let thenFinally be onFinally.
193  //    b. Let catchFinally be onFinally.
194  // 6. Else,
195  //   a. Let thenFinally be a new built-in function object as defined
196  //   in ThenFinally Function.
197  //   b. Let catchFinally be a new built-in function object as
198  //   defined in CatchFinally Function.
199  //   c. Set thenFinally and catchFinally's [[Constructor]] internal
200  //   slots to C.
201  //   d. Set thenFinally and catchFinally's [[OnFinally]] internal
202  //   slots to onFinally.
203  let thenFinally: JSAny;
204  let catchFinally: JSAny;
205  typeswitch (onFinally) {
206    case (onFinally: Callable): {
207      const pair =
208          CreatePromiseFinallyFunctions(nativeContext, onFinally, constructor);
209      thenFinally = pair.then_finally;
210      catchFinally = pair.catch_finally;
211    }
212    case (JSAny): deferred {
213      thenFinally = onFinally;
214      catchFinally = onFinally;
215    }
216  }
217
218  // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
219  return UnsafeCast<JSAny>(
220      InvokeThen(nativeContext, receiver, thenFinally, catchFinally));
221}
222
223extern macro PromiseCatchFinallySharedFunConstant(): SharedFunctionInfo;
224extern macro PromiseThenFinallySharedFunConstant(): SharedFunctionInfo;
225extern macro PromiseThrowerFinallySharedFunConstant(): SharedFunctionInfo;
226extern macro PromiseValueThunkFinallySharedFunConstant(): SharedFunctionInfo;
227}
228