From a3bdd3655630676220fd086d4fdf8fba3fb92069 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Wed, 24 Feb 2021 08:51:23 +0100 Subject: [PATCH] Reduce memory consumption of Promise objects (#4607) No need to keep a reference to resolver functions. Unused resolvers are cleaned up sooner by GC. JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- jerry-core/api/jerry.c | 11 +- jerry-core/ecma/base/ecma-gc.c | 8 -- jerry-core/ecma/operations/ecma-jobqueue.c | 27 ++-- .../ecma/operations/ecma-promise-object.c | 116 ++++++++++-------- .../ecma/operations/ecma-promise-object.h | 6 +- 5 files changed, 81 insertions(+), 87 deletions(-) diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index e571ca16e..4abc3da80 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -4285,13 +4285,12 @@ jerry_resolve_or_reject_promise (jerry_value_t promise, /**< the promise value * return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (error_value_msg_p))); } - ecma_promise_object_t *promise_p = (ecma_promise_object_t *) ecma_get_object_from_value (promise); - ecma_value_t function = is_resolve ? promise_p->resolve : promise_p->reject; + if (is_resolve) + { + return ecma_fulfill_promise_with_checks (promise, argument); + } - return ecma_op_function_call (ecma_get_object_from_value (function), - ECMA_VALUE_UNDEFINED, - &argument, - 1); + return ecma_reject_promise_with_checks (promise, argument); #else /* !JERRY_BUILTIN_PROMISE */ JERRY_UNUSED (promise); JERRY_UNUSED (argument); diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 70bef914c..7db8e018a 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -353,14 +353,6 @@ ecma_gc_mark_promise_object (ecma_extended_object_t *ext_object_p) /**< extended /* Mark all reactions. */ ecma_promise_object_t *promise_object_p = (ecma_promise_object_t *) ext_object_p; - if (!ecma_is_value_empty (promise_object_p->resolve)) - { - JERRY_ASSERT (ecma_is_value_object (promise_object_p->resolve) - && ecma_is_value_object (promise_object_p->reject)); - ecma_gc_set_object_visited (ecma_get_object_from_value (promise_object_p->resolve)); - ecma_gc_set_object_visited (ecma_get_object_from_value (promise_object_p->reject)); - } - ecma_collection_t *collection_p = promise_object_p->reactions; if (collection_p != NULL) diff --git a/jerry-core/ecma/operations/ecma-jobqueue.c b/jerry-core/ecma/operations/ecma-jobqueue.c index 85286d331..05a324208 100644 --- a/jerry-core/ecma/operations/ecma-jobqueue.c +++ b/jerry-core/ecma/operations/ecma-jobqueue.c @@ -420,30 +420,17 @@ static ecma_value_t ecma_process_promise_resolve_thenable_job (ecma_job_promise_resolve_thenable_t *job_p) /**< the job to be operated */ { ecma_promise_object_t *promise_p = (ecma_promise_object_t *) ecma_get_object_from_value (job_p->promise); - ecma_promise_create_resolving_functions (promise_p); - uint16_t new_flags = (uint16_t) (promise_p->header.u.class_prop.extra_info & ~ECMA_PROMISE_ALREADY_RESOLVED); - promise_p->header.u.class_prop.extra_info = new_flags; + promise_p->header.u.class_prop.extra_info &= (uint16_t) ~ECMA_PROMISE_ALREADY_RESOLVED; - ecma_value_t argv[] = { promise_p->resolve, promise_p->reject }; - ecma_value_t ret; - ecma_value_t then_call_result = ecma_op_function_call (ecma_get_object_from_value (job_p->then), - job_p->thenable, - argv, - 2); + ecma_value_t ret = ecma_promise_run_executor ((ecma_object_t *) promise_p, job_p->then, job_p->thenable); - ret = then_call_result; - - if (ECMA_IS_VALUE_ERROR (then_call_result)) + if (ECMA_IS_VALUE_ERROR (ret)) { - then_call_result = jcontext_take_exception (); - - ret = ecma_op_function_call (ecma_get_object_from_value (promise_p->reject), - ECMA_VALUE_UNDEFINED, - &then_call_result, - 1); - - ecma_free_value (then_call_result); + ret = jcontext_take_exception (); + ecma_reject_promise_with_checks (job_p->promise, ret); + ecma_free_value (ret); + ret = ECMA_VALUE_UNDEFINED; } ecma_free_promise_resolve_thenable_job (job_p); diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index 0e442af99..5f88e676a 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -309,20 +309,16 @@ ecma_fulfill_promise (ecma_value_t promise, /**< promise */ } /* ecma_fulfill_promise */ /** - * Native handler for Promise Reject Function. + * Reject a Promise with a reason. Sanity checks are performed before the reject. * * See also: ES2015 25.4.1.3.1 * * @return ecma value of undefined. */ ecma_value_t -ecma_promise_reject_handler (ecma_object_t *function_obj_p, /**< function object */ - const ecma_value_t args_p[], /**< argument list */ - const uint32_t args_count) /**< argument number */ +ecma_reject_promise_with_checks (ecma_value_t promise, /**< promise */ + ecma_value_t reason) /**< reason for reject */ { - ecma_promise_resolver_t *function_p = (ecma_promise_resolver_t *) function_obj_p; - ecma_value_t promise = function_p->promise; - /* 1. */ ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_is_promise (promise_obj_p)); @@ -334,28 +330,23 @@ ecma_promise_reject_handler (ecma_object_t *function_obj_p, /**< function object ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; /* 6. */ - ecma_value_t reject_value = (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]; - ecma_reject_promise (promise, reject_value); + ecma_reject_promise (promise, reason); } return ECMA_VALUE_UNDEFINED; -} /* ecma_promise_reject_handler */ +} /* ecma_reject_promise_with_checks */ /** - * Native handler for Promise Resolve Function. + * Fulfill a Promise with a value. Sanity checks are performed before the resolve. * * See also: ES2015 25.4.1.3.2 * * @return ecma value of undefined. */ ecma_value_t -ecma_promise_resolve_handler (ecma_object_t *function_obj_p, /**< function object */ - const ecma_value_t args_p[], /**< argument list */ - const uint32_t args_count) /**< argument number */ +ecma_fulfill_promise_with_checks (ecma_value_t promise, /**< promise */ + ecma_value_t value) /**< fulfilled value */ { - ecma_promise_resolver_t *function_p = (ecma_promise_resolver_t *) function_obj_p; - ecma_value_t promise = function_p->promise; - /* 1. */ ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_is_promise (promise_obj_p)); @@ -366,10 +357,42 @@ ecma_promise_resolve_handler (ecma_object_t *function_obj_p, /**< function objec /* 5. */ ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; - ecma_fulfill_promise (promise, (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]); + ecma_fulfill_promise (promise, value); } return ECMA_VALUE_UNDEFINED; +} /* ecma_fulfill_promise_with_checks */ + +/** + * Native handler for Promise Reject Function. + * + * @return ecma value of undefined. + */ +ecma_value_t +ecma_promise_reject_handler (ecma_object_t *function_obj_p, /**< function object */ + const ecma_value_t args_p[], /**< argument list */ + const uint32_t args_count) /**< argument number */ +{ + ecma_promise_resolver_t *function_p = (ecma_promise_resolver_t *) function_obj_p; + + ecma_value_t reject_value = (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]; + return ecma_reject_promise_with_checks (function_p->promise, reject_value); +} /* ecma_promise_reject_handler */ + +/** + * Native handler for Promise Resolve Function. + * + * @return ecma value of undefined. + */ +ecma_value_t +ecma_promise_resolve_handler (ecma_object_t *function_obj_p, /**< function object */ + const ecma_value_t args_p[], /**< argument list */ + const uint32_t args_count) /**< argument number */ +{ + ecma_promise_resolver_t *function_p = (ecma_promise_resolver_t *) function_obj_p; + + ecma_value_t fulfilled_value = (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]; + return ecma_fulfill_promise_with_checks (function_p->promise, fulfilled_value); } /* ecma_promise_resolve_handler */ /** @@ -380,8 +403,8 @@ ecma_promise_resolve_handler (ecma_object_t *function_obj_p, /**< function objec * @return pointer to the resolving function */ static ecma_object_t * -ecma_promise_create_resolving_functions_helper (ecma_object_t *promise_p, /**< Promise Object */ - ecma_native_handler_id_t id) /**< Callback handler */ +ecma_promise_create_resolving_function (ecma_object_t *promise_p, /**< Promise Object */ + ecma_native_handler_id_t id) /**< Callback handler */ { ecma_object_t *func_obj_p = ecma_op_create_native_handler (id, sizeof (ecma_promise_resolver_t)); @@ -389,30 +412,35 @@ ecma_promise_create_resolving_functions_helper (ecma_object_t *promise_p, /**< P resolver_p->promise = ecma_make_object_value (promise_p); return func_obj_p; -} /* ecma_promise_create_resolving_functions_helper */ +} /* ecma_promise_create_resolving_function */ /** - * Perform PromiseCreateResolvingFunctions. + * Helper function for running an executor. * - * See also: ES2015 25.4.1.3 - * - * @return pointer to the resolving functions + * @return ecma value of the executor callable + * Returned value must be freed with ecma_free_value */ -void -ecma_promise_create_resolving_functions (ecma_promise_object_t *promise_p) /**< the promise object */ +ecma_value_t +ecma_promise_run_executor (ecma_object_t *promise_p, /**< Promise Object */ + ecma_value_t executor, /**< executor function */ + ecma_value_t this_value) /**< this value */ { - /* 2. - 7. */ - ecma_object_t *resolve_func_p = ecma_promise_create_resolving_functions_helper ((ecma_object_t *) promise_p, - ECMA_NATIVE_HANDLER_PROMISE_RESOLVE); - ecma_object_t *reject_func_p = ecma_promise_create_resolving_functions_helper ((ecma_object_t *) promise_p, - ECMA_NATIVE_HANDLER_PROMISE_REJECT); - - promise_p->resolve = ecma_make_object_value (resolve_func_p); - promise_p->reject = ecma_make_object_value (reject_func_p); + ecma_object_t *resolve_func_p, *reject_func_p; + resolve_func_p = ecma_promise_create_resolving_function (promise_p, + ECMA_NATIVE_HANDLER_PROMISE_RESOLVE); + reject_func_p = ecma_promise_create_resolving_function (promise_p, + ECMA_NATIVE_HANDLER_PROMISE_REJECT); + ecma_value_t argv[] = { ecma_make_object_value (resolve_func_p), ecma_make_object_value (reject_func_p) }; + ecma_value_t result = ecma_op_function_call (ecma_get_object_from_value (executor), + this_value, + argv, + 2); ecma_deref_object (resolve_func_p); ecma_deref_object (reject_func_p); -} /* ecma_promise_create_resolving_functions */ + + return result; +} /* ecma_promise_run_executor */ /** * Create a promise object. @@ -460,11 +488,6 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function /* 6-8. */ ecma_promise_object_t *promise_object_p = (ecma_promise_object_t *) object_p; promise_object_p->reactions = reactions; - /* Creating the resolving function may trigger a GC, so these need to be initialized. */ - promise_object_p->resolve = ECMA_VALUE_EMPTY; - promise_object_p->reject = ECMA_VALUE_EMPTY; - - ecma_promise_create_resolving_functions (promise_object_p); #if JERRY_PROMISE_CALLBACK if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) @@ -483,11 +506,7 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function { JERRY_ASSERT (ecma_op_is_callable (executor)); - ecma_value_t argv[] = { promise_object_p->resolve, promise_object_p->reject }; - completion = ecma_op_function_call (ecma_get_object_from_value (executor), - ECMA_VALUE_UNDEFINED, - argv, - 2); + completion = ecma_promise_run_executor (object_p, executor, ECMA_VALUE_UNDEFINED); } ecma_value_t status = ECMA_VALUE_EMPTY; @@ -496,10 +515,7 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function { /* 10.a. */ completion = jcontext_take_exception (); - status = ecma_op_function_call (ecma_get_object_from_value (promise_object_p->reject), - ECMA_VALUE_UNDEFINED, - &completion, - 1); + ecma_reject_promise_with_checks (ecma_make_object_value (object_p), completion); } ecma_free_value (completion); diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index b349f6c10..814aa9d27 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -54,8 +54,6 @@ typedef struct { ecma_extended_object_t header; /**< extended object part */ ecma_collection_t *reactions; /**< list of promise reactions */ - ecma_value_t resolve; /**< resolve function */ - ecma_value_t reject; /**< reject function */ } ecma_promise_object_t; /** @@ -101,6 +99,8 @@ uint16_t ecma_promise_get_flags (ecma_object_t *promise_p); ecma_value_t ecma_promise_get_result (ecma_object_t *promise_p); void ecma_reject_promise (ecma_value_t promise, ecma_value_t reason); void ecma_fulfill_promise (ecma_value_t promise, ecma_value_t value); +ecma_value_t ecma_reject_promise_with_checks (ecma_value_t promise, ecma_value_t reason); +ecma_value_t ecma_fulfill_promise_with_checks (ecma_value_t promise, ecma_value_t value); ecma_object_t *ecma_promise_new_capability (ecma_value_t constructor, ecma_value_t parent); ecma_value_t ecma_promise_reject_or_resolve (ecma_value_t this_arg, ecma_value_t value, bool is_resolve); ecma_value_t ecma_promise_then (ecma_value_t promise, ecma_value_t on_fulfilled, ecma_value_t on_rejected); @@ -125,7 +125,7 @@ ecma_value_t ecma_op_get_capabilities_executor_cb (ecma_object_t *function_obj_p ecma_value_t ecma_promise_finally (ecma_value_t promise, ecma_value_t on_finally); void ecma_promise_async_then (ecma_value_t promise, ecma_value_t executable_object); ecma_value_t ecma_promise_async_await (ecma_extended_object_t *async_generator_object_p, ecma_value_t value); -void ecma_promise_create_resolving_functions (ecma_promise_object_t *object_p); +ecma_value_t ecma_promise_run_executor (ecma_object_t *promise_p, ecma_value_t executor, ecma_value_t this_value); uint32_t ecma_promise_remaining_inc_or_dec (ecma_value_t remaining, bool is_inc);