1/* Copyright JS Foundation and other contributors, http://js.foundation
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecma-function-object.h"
17#include "ecma-globals.h"
18#include "ecma-helpers.h"
19#include "ecma-jobqueue.h"
20#include "ecma-objects.h"
21#include "ecma-promise-object.h"
22#include "jcontext.h"
23
24#if ENABLED (JERRY_ES2015_BUILTIN_PROMISE)
25
26/**
27 * Mask for job queue type.
28 */
29#define ECMA_JOB_QUEURE_TYPE_MASK ((uintptr_t) 0x07)
30
31/** \addtogroup ecma ECMA
32 * @{
33 *
34 * \addtogroup ecmajobqueue ECMA Job Queue related routines
35 * @{
36 */
37
38/**
39 * Description of the PromiseReactionJob
40 */
41typedef struct
42{
43  ecma_job_queue_item_t header; /**< job queue item header */
44  ecma_value_t capability; /**< capability object */
45  ecma_value_t handler; /**< handler function */
46  ecma_value_t argument; /**< argument for the reaction */
47} ecma_job_promise_reaction_t;
48
49/**
50 * Description of the PromiseResolveThenableJob
51 */
52typedef struct
53{
54  ecma_job_queue_item_t header; /**< job queue item header */
55  ecma_value_t promise; /**< promise to be resolved */
56  ecma_value_t thenable; /**< thenable object */
57  ecma_value_t then; /**< 'then' function */
58} ecma_job_promise_resolve_thenable_t;
59
60/**
61 * Initialize the jobqueue.
62 */
63void ecma_job_queue_init (void)
64{
65  JERRY_CONTEXT (job_queue_head_p) = NULL;
66  JERRY_CONTEXT (job_queue_tail_p) = NULL;
67} /* ecma_job_queue_init */
68
69/**
70 * Get the type of the job.
71 *
72 * @return type of the job
73 */
74static inline ecma_job_queue_item_type_t JERRY_ATTR_ALWAYS_INLINE
75ecma_job_queue_get_type (ecma_job_queue_item_t *job_p) /**< the job */
76{
77  return (ecma_job_queue_item_type_t) (job_p->next_and_type & ECMA_JOB_QUEURE_TYPE_MASK);
78} /* ecma_job_queue_get_type */
79
80/**
81 * Get the next job of the job queue.
82 *
83 * @return next job
84 */
85static inline ecma_job_queue_item_t *JERRY_ATTR_ALWAYS_INLINE
86ecma_job_queue_get_next (ecma_job_queue_item_t *job_p) /**< the job */
87{
88  return (ecma_job_queue_item_t *) (job_p->next_and_type & ~ECMA_JOB_QUEURE_TYPE_MASK);
89} /* ecma_job_queue_get_next */
90
91/**
92 * Free the heap and the member of the PromiseReactionJob.
93 */
94static void
95ecma_free_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< points to the PromiseReactionJob */
96{
97  JERRY_ASSERT (job_p != NULL);
98
99  ecma_free_value (job_p->capability);
100  ecma_free_value (job_p->handler);
101  ecma_free_value (job_p->argument);
102
103  jmem_heap_free_block (job_p, sizeof (ecma_job_promise_reaction_t));
104} /* ecma_free_promise_reaction_job */
105
106/**
107 * Free the heap and the member of the PromiseResolveThenableJob.
108 */
109static void
110ecma_free_promise_resolve_thenable_job (ecma_job_promise_resolve_thenable_t *job_p) /**< points to the
111                                                                                     *   PromiseResolveThenableJob */
112{
113  JERRY_ASSERT (job_p != NULL);
114
115  ecma_free_value (job_p->promise);
116  ecma_free_value (job_p->thenable);
117  ecma_free_value (job_p->then);
118
119  jmem_heap_free_block (job_p, sizeof (ecma_job_promise_resolve_thenable_t));
120} /* ecma_free_promise_resolve_thenable_job */
121
122/**
123 * The processor for PromiseReactionJob.
124 *
125 * See also: ES2015 25.4.2.1
126 *
127 * @return ecma value
128 *         Returned value must be freed with ecma_free_value
129 */
130static ecma_value_t
131ecma_process_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< the job to be operated */
132{
133  ecma_string_t *resolve_str_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_RESOLVE);
134  ecma_string_t *reject_str_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_REJECT);
135
136  /* 2. */
137  ecma_value_t capability = job_p->capability;
138  /* 3. */
139  ecma_value_t handler = job_p->handler;
140
141  JERRY_ASSERT (ecma_is_value_boolean (handler) || ecma_op_is_callable (handler));
142
143  ecma_value_t handler_result;
144
145  if (ecma_is_value_boolean (handler))
146  {
147    /* 4-5. True indicates "identity" and false indicates "thrower" */
148    handler_result = ecma_copy_value (job_p->argument);
149  }
150  else
151  {
152    /* 6. */
153    handler_result = ecma_op_function_call (ecma_get_object_from_value (handler),
154                                            ECMA_VALUE_UNDEFINED,
155                                            &(job_p->argument),
156                                            1);
157  }
158
159  ecma_value_t status;
160
161  if (ecma_is_value_false (handler) || ECMA_IS_VALUE_ERROR (handler_result))
162  {
163    if (ECMA_IS_VALUE_ERROR (handler_result))
164    {
165      handler_result = jcontext_take_exception ();
166    }
167
168    /* 7. */
169    ecma_value_t reject = ecma_op_object_get (ecma_get_object_from_value (capability), reject_str_p);
170
171    JERRY_ASSERT (ecma_op_is_callable (reject));
172
173    status = ecma_op_function_call (ecma_get_object_from_value (reject),
174                                    ECMA_VALUE_UNDEFINED,
175                                    &handler_result,
176                                    1);
177    ecma_free_value (reject);
178  }
179  else
180  {
181    /* 8. */
182    ecma_value_t resolve = ecma_op_object_get (ecma_get_object_from_value (capability), resolve_str_p);
183
184    JERRY_ASSERT (ecma_op_is_callable (resolve));
185
186    status = ecma_op_function_call (ecma_get_object_from_value (resolve),
187                                    ECMA_VALUE_UNDEFINED,
188                                    &handler_result,
189                                    1);
190    ecma_free_value (resolve);
191  }
192
193  ecma_free_value (handler_result);
194  ecma_free_promise_reaction_job (job_p);
195
196  return status;
197} /* ecma_process_promise_reaction_job */
198
199/**
200 * Process the PromiseResolveThenableJob.
201 *
202 * See also: ES2015 25.4.2.2
203 *
204 * @return ecma value
205 *         Returned value must be freed with ecma_free_value
206 */
207static ecma_value_t
208ecma_process_promise_resolve_thenable_job (ecma_job_promise_resolve_thenable_t *job_p) /**< the job to be operated */
209{
210  ecma_object_t *promise_p = ecma_get_object_from_value (job_p->promise);
211  ecma_promise_resolving_functions_t funcs;
212  ecma_promise_create_resolving_functions (promise_p, &funcs, true);
213
214  ecma_string_t *str_resolve_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_RESOLVE_FUNCTION);
215  ecma_string_t *str_reject_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_REJECT_FUNCTION);
216
217  ecma_op_object_put (promise_p,
218                      str_resolve_p,
219                      funcs.resolve,
220                      false);
221  ecma_op_object_put (promise_p,
222                      str_reject_p,
223                      funcs.reject,
224                      false);
225
226  ecma_value_t argv[] = { funcs.resolve, funcs.reject };
227  ecma_value_t ret;
228  ecma_value_t then_call_result = ecma_op_function_call (ecma_get_object_from_value (job_p->then),
229                                                         job_p->thenable,
230                                                         argv,
231                                                         2);
232
233  ret = then_call_result;
234
235  if (ECMA_IS_VALUE_ERROR (then_call_result))
236  {
237    then_call_result = jcontext_take_exception ();
238
239    ret = ecma_op_function_call (ecma_get_object_from_value (funcs.reject),
240                                 ECMA_VALUE_UNDEFINED,
241                                 &then_call_result,
242                                 1);
243
244    ecma_free_value (then_call_result);
245  }
246
247  ecma_promise_free_resolving_functions (&funcs);
248  ecma_free_promise_resolve_thenable_job (job_p);
249
250  return ret;
251} /* ecma_process_promise_resolve_thenable_job */
252
253/**
254 * Enqueue a Promise job into the jobqueue.
255 */
256static void
257ecma_enqueue_job (ecma_job_queue_item_t *job_p) /**< the job */
258{
259  JERRY_ASSERT (job_p->next_and_type <= ECMA_JOB_QUEURE_TYPE_MASK);
260
261  if (JERRY_CONTEXT (job_queue_head_p) == NULL)
262  {
263    JERRY_CONTEXT (job_queue_head_p) = job_p;
264    JERRY_CONTEXT (job_queue_tail_p) = job_p;
265  }
266  else
267  {
268    JERRY_ASSERT ((JERRY_CONTEXT (job_queue_tail_p)->next_and_type & ~ECMA_JOB_QUEURE_TYPE_MASK) == 0);
269
270    JERRY_CONTEXT (job_queue_tail_p)->next_and_type |= (uintptr_t) job_p;
271    JERRY_CONTEXT (job_queue_tail_p) = job_p;
272  }
273} /* ecma_enqueue_job */
274
275/**
276 * Enqueue a PromiseReactionJob into the jobqueue.
277 */
278void
279ecma_enqueue_promise_reaction_job (ecma_value_t capability, /**< capability object */
280                                   ecma_value_t handler, /**< handler function */
281                                   ecma_value_t argument) /**< argument for the reaction */
282{
283  ecma_job_promise_reaction_t *job_p;
284  job_p = (ecma_job_promise_reaction_t *) jmem_heap_alloc_block (sizeof (ecma_job_promise_reaction_t));
285  job_p->header.next_and_type = ECMA_JOB_PROMISE_REACTION;
286  job_p->capability = ecma_copy_value (capability);
287  job_p->handler = ecma_copy_value (handler);
288  job_p->argument = ecma_copy_value (argument);
289
290  ecma_enqueue_job (&job_p->header);
291} /* ecma_enqueue_promise_reaction_job */
292
293/**
294 * Enqueue a PromiseResolveThenableJob into the jobqueue.
295 */
296void
297ecma_enqueue_promise_resolve_thenable_job (ecma_value_t promise, /**< promise to be resolved */
298                                           ecma_value_t thenable, /**< thenable object */
299                                           ecma_value_t then) /**< 'then' function */
300{
301  JERRY_ASSERT (ecma_is_promise (ecma_get_object_from_value (promise)));
302  JERRY_ASSERT (ecma_is_value_object (thenable));
303  JERRY_ASSERT (ecma_op_is_callable (then));
304
305  ecma_job_promise_resolve_thenable_t *job_p;
306  job_p = (ecma_job_promise_resolve_thenable_t *) jmem_heap_alloc_block (sizeof (ecma_job_promise_resolve_thenable_t));
307  job_p->header.next_and_type = ECMA_JOB_PROMISE_THENABLE;
308  job_p->promise = ecma_copy_value (promise);
309  job_p->thenable = ecma_copy_value (thenable);
310  job_p->then = ecma_copy_value (then);
311
312  ecma_enqueue_job (&job_p->header);
313} /* ecma_enqueue_promise_resolve_thenable_job */
314
315/**
316 * Process enqueued Promise jobs until the first thrown error or until the
317 * jobqueue becomes empty.
318 *
319 * @return result of the last processed job - if the jobqueue was non-empty,
320 *         undefined - otherwise.
321 */
322ecma_value_t
323ecma_process_all_enqueued_jobs (void)
324{
325  ecma_value_t ret = ECMA_VALUE_UNDEFINED;
326
327  while (JERRY_CONTEXT (job_queue_head_p) != NULL && !ECMA_IS_VALUE_ERROR (ret))
328  {
329    ecma_job_queue_item_t *job_p = JERRY_CONTEXT (job_queue_head_p);
330    JERRY_CONTEXT (job_queue_head_p) = ecma_job_queue_get_next (job_p);
331
332    ecma_fast_free_value (ret);
333
334    switch (ecma_job_queue_get_type (job_p))
335    {
336      case ECMA_JOB_PROMISE_REACTION:
337      {
338        ret = ecma_process_promise_reaction_job ((ecma_job_promise_reaction_t *) job_p);
339        break;
340      }
341      default:
342      {
343        JERRY_ASSERT (ecma_job_queue_get_type (job_p) == ECMA_JOB_PROMISE_THENABLE);
344
345        ret = ecma_process_promise_resolve_thenable_job ((ecma_job_promise_resolve_thenable_t *) job_p);
346        break;
347      }
348    }
349  }
350
351  return ret;
352} /* ecma_process_all_enqueued_jobs */
353
354/**
355 * Release enqueued Promise jobs.
356 */
357void
358ecma_free_all_enqueued_jobs (void)
359{
360  while (JERRY_CONTEXT (job_queue_head_p) != NULL)
361  {
362    ecma_job_queue_item_t *job_p = JERRY_CONTEXT (job_queue_head_p);
363    JERRY_CONTEXT (job_queue_head_p) = ecma_job_queue_get_next (job_p);
364
365    switch (ecma_job_queue_get_type (job_p))
366    {
367      case ECMA_JOB_PROMISE_REACTION:
368      {
369        ecma_free_promise_reaction_job ((ecma_job_promise_reaction_t *) job_p);
370        break;
371      }
372      default:
373      {
374        JERRY_ASSERT (ecma_job_queue_get_type (job_p) == ECMA_JOB_PROMISE_THENABLE);
375
376        ecma_free_promise_resolve_thenable_job ((ecma_job_promise_resolve_thenable_t *) job_p);
377        break;
378      }
379    }
380  }
381} /* ecma_free_all_enqueued_jobs */
382
383/**
384 * @}
385 * @}
386 */
387#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */
388