From f88489beef86c8e4142496bf5cbafe04c6dfd70a Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Thu, 25 Jun 2020 17:11:16 +0200 Subject: [PATCH] Implement yield* operator (#3923) Missing features: - caching next() method (this also true for normal generators) - automatic sync to async generator conversion JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- jerry-core/ecma/base/ecma-gc.c | 6 - jerry-core/ecma/base/ecma-globals.h | 4 + .../ecma-builtin-generator-prototype.c | 7 +- .../ecma-builtin-intrinsic.inc.h | 4 + .../builtin-objects/ecma-builtin-promise.c | 5 +- .../builtin-objects/ecma-builtin-symbol.inc.h | 5 + .../operations/ecma-async-generator-object.c | 302 +++++++++++++- .../operations/ecma-async-generator-object.h | 64 +++ .../ecma/operations/ecma-container-object.c | 2 +- .../ecma/operations/ecma-iterator-object.c | 22 +- .../ecma/operations/ecma-iterator-object.h | 3 + jerry-core/ecma/operations/ecma-jobqueue.c | 44 +- .../ecma/operations/ecma-promise-object.c | 24 ++ .../ecma/operations/ecma-promise-object.h | 1 + jerry-core/lit/lit-magic-strings.h | 1 + jerry-core/lit/lit-magic-strings.inc.h | 7 +- jerry-core/lit/lit-magic-strings.ini | 1 + jerry-core/parser/js/byte-code.c | 2 +- jerry-core/parser/js/byte-code.h | 2 + jerry-core/parser/js/js-parser-expr.c | 9 +- jerry-core/vm/opcodes.c | 34 +- jerry-core/vm/opcodes.h | 3 + jerry-core/vm/vm-defines.h | 6 + jerry-core/vm/vm.c | 83 ++-- jerry-core/vm/vm.h | 2 + tests/jerry/es.next/function-async-gen1.js | 40 +- tests/jerry/es.next/function-async-gen2.js | 304 ++++++++++++++ tests/jerry/es.next/function-async-gen3.js | 382 ++++++++++++++++++ 28 files changed, 1258 insertions(+), 111 deletions(-) create mode 100644 tests/jerry/es.next/function-async-gen2.js create mode 100644 tests/jerry/es.next/function-async-gen3.js diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index ac7aab014..a203274e0 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -432,12 +432,6 @@ ecma_gc_mark_executable_object (ecma_object_t *object_p) /**< object */ return; } - if (executable_object_p->extended_object.u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD) - { - ecma_value_t iterator = executable_object_p->extended_object.u.class_prop.u.value; - ecma_gc_set_object_visited (ecma_get_object_from_value (iterator)); - } - ecma_gc_set_object_visited (executable_object_p->frame_ctx.lex_env_p); if (ecma_is_value_object (executable_object_p->frame_ctx.this_binding)) diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index c88c05f91..712495dc5 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -208,6 +208,8 @@ enum ECMA_VALUE_UNINITIALIZED = ECMA_MAKE_VALUE (10), /**< a special value for uninitialized let/const declarations */ ECMA_VALUE_SPREAD_ELEMENT = ECMA_MAKE_VALUE (11), /**< a special value for spread elements in array initialization * or function call argument list */ + ECMA_VALUE_SYNC_ITERATOR = ECMA_MAKE_VALUE (12), /**< option for ecma_op_get_iterator: sync iterator is requested */ + ECMA_VALUE_ASYNC_ITERATOR = ECMA_MAKE_VALUE (13), /**< option for ecma_op_get_iterator: async iterator is requested */ }; #if !ENABLED (JERRY_NUMBER_TYPE_FLOAT64) @@ -1865,6 +1867,8 @@ typedef enum /* Generator specific flags. */ ECMA_GENERATOR_ITERATE_AND_YIELD = (1u << 2), /**< the generator performs a yield* operation */ ECMA_ASYNC_GENERATOR_CALLED = (1u << 3), /**< the async generator was executed before */ + /* This must be the last generator specific flag. */ + ECMA_ASYNC_YIELD_ITERATOR_STATE_SHIFT = 4, /**< shift for async yield iterator state */ } ecma_executable_object_flags_t; /** diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-generator-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-generator-prototype.c index 2cd7620bb..2db82969c 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-generator-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-generator-prototype.c @@ -75,7 +75,7 @@ ecma_builtin_generator_prototype_object_do (vm_executable_object_t *executable_o { if (executable_object_p->extended_object.u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD) { - ecma_value_t iterator = executable_object_p->extended_object.u.class_prop.u.value; + ecma_value_t iterator = executable_object_p->frame_ctx.block_result; bool done = false; ecma_value_t result = ecma_op_iterator_do (resume_mode, iterator, arg, &done); @@ -102,6 +102,7 @@ ecma_builtin_generator_prototype_object_do (vm_executable_object_t *executable_o } executable_object_p->extended_object.u.class_prop.extra_info &= (uint16_t) ~ECMA_GENERATOR_ITERATE_AND_YIELD; + executable_object_p->frame_ctx.block_result = ECMA_VALUE_UNDEFINED; if (ECMA_IS_VALUE_ERROR (arg)) { @@ -137,7 +138,7 @@ ecma_builtin_generator_prototype_object_do (vm_executable_object_t *executable_o if (byte_code_p[-1] == CBC_EXT_YIELD_ITERATOR) { - ecma_value_t iterator = ecma_op_get_iterator (value, ECMA_VALUE_EMPTY); + ecma_value_t iterator = ecma_op_get_iterator (value, ECMA_VALUE_SYNC_ITERATOR); ecma_free_value (value); if (ECMA_IS_VALUE_ERROR (iterator)) @@ -149,7 +150,7 @@ ecma_builtin_generator_prototype_object_do (vm_executable_object_t *executable_o ecma_deref_object (ecma_get_object_from_value (iterator)); executable_object_p->extended_object.u.class_prop.extra_info |= ECMA_GENERATOR_ITERATE_AND_YIELD; - executable_object_p->extended_object.u.class_prop.u.value = iterator; + executable_object_p->frame_ctx.block_result = iterator; arg = ECMA_VALUE_UNDEFINED; continue; } diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-intrinsic.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-intrinsic.inc.h index 511806cf9..3a9ecc49e 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-intrinsic.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-intrinsic.inc.h @@ -21,6 +21,10 @@ #if ENABLED (JERRY_ESNEXT) +/* ECMA-262 v10, 19.4.2.1 */ +SYMBOL_VALUE (LIT_GLOBAL_SYMBOL_ASYNC_ITERATOR, + LIT_MAGIC_STRING_ASYNC_ITERATOR) + /* ECMA-262 v6, 19.4.2.2 */ SYMBOL_VALUE (LIT_GLOBAL_SYMBOL_HAS_INSTANCE, LIT_MAGIC_STRING_HAS_INSTANCE) diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c b/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c index 8cfa2b43f..33b489e8b 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c @@ -527,8 +527,9 @@ ecma_builtin_promise_race_or_all (ecma_value_t this_arg, /**< 'this' argument */ return capability; } - ecma_value_t iterator = ecma_builtin_promise_reject_abrupt (ecma_op_get_iterator (iterable, ECMA_VALUE_EMPTY), - capability); + ecma_value_t iterator; + iterator = ecma_builtin_promise_reject_abrupt (ecma_op_get_iterator (iterable, ECMA_VALUE_SYNC_ITERATOR), + capability); if (ECMA_IS_VALUE_ERROR (iterator)) { diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-symbol.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-symbol.inc.h index 06808ff36..7d871f548 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-symbol.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-symbol.inc.h @@ -41,6 +41,11 @@ OBJECT_VALUE (LIT_MAGIC_STRING_PROTOTYPE, ECMA_BUILTIN_ID_SYMBOL_PROTOTYPE, ECMA_PROPERTY_FIXED) +/* ECMA-262 v10, 19.4.2.1 */ +INTRINSIC_PROPERTY (LIT_MAGIC_STRING_ASYNC_ITERATOR, + LIT_GLOBAL_SYMBOL_ASYNC_ITERATOR, + ECMA_PROPERTY_FIXED) + /* ECMA-262 v6, 19.4.2.2 */ INTRINSIC_PROPERTY (LIT_MAGIC_STRING_HAS_INSTANCE, LIT_GLOBAL_SYMBOL_HAS_INSTANCE, diff --git a/jerry-core/ecma/operations/ecma-async-generator-object.c b/jerry-core/ecma/operations/ecma-async-generator-object.c index b35941b95..b9884701d 100644 --- a/jerry-core/ecma/operations/ecma-async-generator-object.c +++ b/jerry-core/ecma/operations/ecma-async-generator-object.c @@ -16,9 +16,13 @@ #include "ecma-alloc.h" #include "ecma-async-generator-object.h" #include "ecma-builtins.h" +#include "ecma-exceptions.h" +#include "ecma-function-object.h" +#include "ecma-gc.h" #include "ecma-globals.h" #include "ecma-helpers.h" #include "ecma-iterator-object.h" +#include "ecma-objects.h" #include "ecma-promise-object.h" #include "jcontext.h" #include "opcodes.h" @@ -87,6 +91,98 @@ ecma_async_generator_enqueue (vm_executable_object_t *async_generator_object_p, return result; } /* ecma_async_generator_enqueue */ +/** + * Call a function and await its return value + * + * @return ECMA_VALUE_UNDEFINED on success, error otherwise + */ +static ecma_value_t +ecma_async_yield_call (ecma_value_t function, /**< function (takes reference) */ + vm_executable_object_t *async_generator_object_p, /**< async generator */ + ecma_value_t argument, /**< argument passed to the function */ + const char *error_msg_p) /**< error message when the function is not callable */ +{ + if (!ecma_is_value_object (function) || !ecma_op_is_callable (function)) + { + ecma_free_value (function); + return ecma_raise_type_error (error_msg_p); + } + + ecma_object_t *return_obj_p = ecma_get_object_from_value (function); + ecma_value_t iterator = async_generator_object_p->frame_ctx.block_result; + ecma_value_t result; + + if (argument == ECMA_VALUE_EMPTY) + { + result = ecma_op_function_call (return_obj_p, iterator, NULL, 0); + } + else + { + result = ecma_op_function_call (return_obj_p, iterator, &argument, 1); + } + + ecma_deref_object (return_obj_p); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + return ecma_promise_async_await ((ecma_extended_object_t *) async_generator_object_p, result); +} /* ecma_async_yield_call */ + +/** + * Perform an exception throw and call the approprite handler + */ +static ecma_value_t +ecma_async_yield_throw (vm_executable_object_t *async_generator_object_p, /**< async generator */ + ecma_value_t value) /**< thrown value */ +{ + ecma_object_t *obj_p = ecma_get_object_from_value (async_generator_object_p->frame_ctx.block_result); + ecma_value_t result = ecma_op_object_get_by_magic_id (obj_p, LIT_MAGIC_STRING_THROW); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + if (result == ECMA_VALUE_UNDEFINED) + { + result = ecma_op_object_get_by_magic_id (obj_p, LIT_MAGIC_STRING_RETURN); + + if (result == ECMA_VALUE_UNDEFINED) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("Iterator throw() is not available.")); + } + + result = ecma_async_yield_call (result, + async_generator_object_p, + ECMA_VALUE_EMPTY, + ECMA_ERR_MSG ("Iterator return() is not callable.")); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (async_generator_object_p, OPERATION, CLOSE); + return ECMA_VALUE_UNDEFINED; + } + + result = ecma_async_yield_call (result, + async_generator_object_p, + value, + ECMA_ERR_MSG ("Iterator throw() is not callable.")); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (async_generator_object_p, OPERATION, NEXT); + return ECMA_VALUE_UNDEFINED; +} /* ecma_async_yield_throw */ + /** * Execute the next task in the command queue of the async generator */ @@ -99,26 +195,91 @@ ecma_async_generator_run (vm_executable_object_t *async_generator_object_p) /**< ecma_value_t head = async_generator_object_p->extended_object.u.class_prop.u.head; ecma_async_generator_task_t *task_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_async_generator_task_t, head); + ecma_value_t result; - if (task_p->operation_type == ECMA_ASYNC_GENERATOR_DO_RETURN) - { - async_generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_return; - } - else if (task_p->operation_type == ECMA_ASYNC_GENERATOR_DO_THROW) + if (async_generator_object_p->extended_object.u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD) { + switch (task_p->operation_type) + { + case ECMA_ASYNC_GENERATOR_DO_NEXT: + { + result = ecma_op_iterator_next (async_generator_object_p->frame_ctx.block_result, task_p->operation_value); + + if (ECMA_IS_VALUE_ERROR (result)) + { + break; + } + + result = ecma_promise_async_await ((ecma_extended_object_t *) async_generator_object_p, result); + + if (ECMA_IS_VALUE_ERROR (result)) + { + break; + } + + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (async_generator_object_p, OPERATION, NEXT); + break; + } + case ECMA_ASYNC_GENERATOR_DO_THROW: + { + result = ecma_async_yield_throw (async_generator_object_p, task_p->operation_value); + break; + } + default: + { + JERRY_ASSERT (task_p->operation_type == ECMA_ASYNC_GENERATOR_DO_RETURN); + + result = ecma_copy_value (task_p->operation_value); + result = ecma_promise_async_await ((ecma_extended_object_t *) async_generator_object_p, result); + + if (ECMA_IS_VALUE_ERROR (result)) + { + break; + } + + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (async_generator_object_p, OPERATION, RETURN); + break; + } + } + + ecma_free_value_if_not_object (task_p->operation_value); + task_p->operation_value = ECMA_VALUE_UNDEFINED; + + if (result == ECMA_VALUE_UNDEFINED) + { + return; + } + + JERRY_ASSERT (ECMA_IS_VALUE_ERROR (result)); + + ECMA_ASYNC_YIELD_ITERATOR_END (async_generator_object_p); + async_generator_object_p->frame_ctx.block_result = ECMA_VALUE_UNDEFINED; async_generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw; + + result = jcontext_take_exception (); + } + else + { + if (task_p->operation_type == ECMA_ASYNC_GENERATOR_DO_RETURN) + { + async_generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_return; + } + else if (task_p->operation_type == ECMA_ASYNC_GENERATOR_DO_THROW) + { + async_generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw; + } + + result = task_p->operation_value; + ecma_ref_if_object (result); + task_p->operation_value = ECMA_VALUE_UNDEFINED; } - ecma_value_t value = task_p->operation_value; - ecma_ref_if_object (value); - task_p->operation_value = ECMA_VALUE_UNDEFINED; - - value = opfunc_resume_executable_object (async_generator_object_p, value); + result = opfunc_resume_executable_object (async_generator_object_p, result); if (async_generator_object_p->extended_object.u.class_prop.extra_info & ECMA_EXECUTABLE_OBJECT_COMPLETED) { JERRY_ASSERT (head == async_generator_object_p->extended_object.u.class_prop.u.head); - ecma_async_generator_finalize (async_generator_object_p, value); + ecma_async_generator_finalize (async_generator_object_p, result); } } /* ecma_async_generator_run */ @@ -132,8 +293,6 @@ ecma_async_generator_finalize (vm_executable_object_t *async_generator_object_p, ecma_value_t next = async_generator_object_p->extended_object.u.class_prop.u.head; ecma_async_generator_task_t *task_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_async_generator_task_t, next); - ECMA_SET_INTERNAL_VALUE_ANY_POINTER (async_generator_object_p->extended_object.u.class_prop.u.head, NULL); - if (ECMA_IS_VALUE_ERROR (value)) { value = jcontext_take_exception (); @@ -149,6 +308,7 @@ ecma_async_generator_finalize (vm_executable_object_t *async_generator_object_p, ecma_free_value (value); next = task_p->next; + async_generator_object_p->extended_object.u.class_prop.u.head = next; jmem_heap_free_block (task_p, sizeof (ecma_async_generator_task_t)); while (!ECMA_IS_INTERNAL_VALUE_NULL (next)) @@ -169,10 +329,126 @@ ecma_async_generator_finalize (vm_executable_object_t *async_generator_object_p, ecma_free_value_if_not_object (task_p->operation_value); next = task_p->next; + async_generator_object_p->extended_object.u.class_prop.u.head = next; jmem_heap_free_block (task_p, sizeof (ecma_async_generator_task_t)); } } /* ecma_async_generator_finalize */ +/** + * Continue after an await operation is completed. + * + * @return an updated value for the value argument + */ +ecma_value_t +ecma_async_yield_continue_await (vm_executable_object_t *async_generator_object_p, /**< async generator */ + ecma_value_t value) /**< job value (takes reference) */ +{ + switch (ECMA_ASYNC_YIELD_ITERATOR_GET_STATE (async_generator_object_p)) + { + case ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT: + case ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT_RETURN: + { + if (!ecma_is_value_object (value)) + { + ecma_free_value (value); + return ecma_raise_type_error (ECMA_ERR_MSG ("Value received by yield* is not Object.")); + } + + ecma_object_t *result_obj_p = ecma_get_object_from_value (value); + ecma_value_t result = ecma_op_object_get_by_magic_id (result_obj_p, LIT_MAGIC_STRING_DONE); + + if (ECMA_IS_VALUE_ERROR (result)) + { + ecma_deref_object (result_obj_p); + return result; + } + + bool done = ecma_op_to_boolean (result); + ecma_free_value (result); + result = ecma_op_object_get_by_magic_id (result_obj_p, LIT_MAGIC_STRING_VALUE); + ecma_deref_object (result_obj_p); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + if (!done) + { + ECMA_ASYNC_YIELD_ITERATOR_SET_STATE (async_generator_object_p, NEXT_VALUE); + return ecma_promise_async_await ((ecma_extended_object_t *) async_generator_object_p, result); + } + + if (ECMA_ASYNC_YIELD_ITERATOR_GET_STATE (async_generator_object_p) == ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT) + { + ECMA_ASYNC_YIELD_ITERATOR_END (async_generator_object_p); + return result; + } + + ECMA_ASYNC_YIELD_ITERATOR_END (async_generator_object_p); + async_generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_return; + return result; + } + case ECMA_ASYNC_YIELD_ITERATOR_AWAIT_RETURN: + { + ecma_object_t *obj_p = ecma_get_object_from_value (async_generator_object_p->frame_ctx.block_result); + ecma_value_t result = ecma_op_object_get_by_magic_id (obj_p, LIT_MAGIC_STRING_RETURN); + + if (ECMA_IS_VALUE_ERROR (result)) + { + ecma_free_value (value); + return result; + } + + if (result == ECMA_VALUE_UNDEFINED) + { + ECMA_ASYNC_YIELD_ITERATOR_END (async_generator_object_p); + async_generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_return; + return value; + } + + result = ecma_async_yield_call (result, + async_generator_object_p, + value, + ECMA_ERR_MSG ("Iterator return() is not callable.")); + ecma_free_value (value); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + JERRY_ASSERT (result == ECMA_VALUE_UNDEFINED); + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (async_generator_object_p, RETURN, NEXT_RETURN); + return ECMA_VALUE_UNDEFINED; + } + case ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT_VALUE: + { + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (async_generator_object_p, NEXT_VALUE, OPERATION); + opfunc_async_generator_yield ((ecma_extended_object_t *) async_generator_object_p, value); + return ECMA_VALUE_UNDEFINED; + } + case ECMA_ASYNC_YIELD_ITERATOR_AWAIT_OPERATION: + { + /* Currently this is always a throw exception case. */ + ecma_value_t result = ecma_async_yield_throw (async_generator_object_p, value); + ecma_free_value (value); + return result; + } + default: + { + JERRY_ASSERT (ECMA_ASYNC_YIELD_ITERATOR_GET_STATE (async_generator_object_p) + == ECMA_ASYNC_YIELD_ITERATOR_AWAIT_CLOSE); + + const char *msg_p = (ecma_is_value_object (value) ? ECMA_ERR_MSG ("Iterator throw() is not available.") + : ECMA_ERR_MSG ("Value received by yield* is not Object.")); + + ecma_free_value (value); + return ecma_raise_type_error (msg_p); + } + } +} /* ecma_async_yield_continue_await */ + #endif /* ENABLED (JERRY_ESNEXT) */ /** diff --git a/jerry-core/ecma/operations/ecma-async-generator-object.h b/jerry-core/ecma/operations/ecma-async-generator-object.h index f535a6e29..0f7c7760b 100644 --- a/jerry-core/ecma/operations/ecma-async-generator-object.h +++ b/jerry-core/ecma/operations/ecma-async-generator-object.h @@ -38,12 +38,76 @@ typedef enum ECMA_ASYNC_GENERATOR_DO_RETURN, /**< async generator return operation */ } ecma_async_generator_operation_type_t; +/** + * AsyncGenerator yield iterator states. + */ +typedef enum +{ + ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT, /**< wait for an iterator result object */ + ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT_RETURN, /**< wait for an iterator result object after a return operation */ + ECMA_ASYNC_YIELD_ITERATOR_AWAIT_RETURN, /**< wait for the argument passed to return operation */ + ECMA_ASYNC_YIELD_ITERATOR_AWAIT_NEXT_VALUE, /**< wait for the value property of an iterator result object */ + ECMA_ASYNC_YIELD_ITERATOR_AWAIT_OPERATION, /**< wait for the generator operation (next/throw/return) */ + ECMA_ASYNC_YIELD_ITERATOR_AWAIT_CLOSE, /**< wait for the result of iterator close operation */ +} ecma_async_yield_iterator_states_t; + +/** + * Get the state of an async yield iterator. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_GET_STATE(async_generator_object_p) \ + ((async_generator_object_p)->extended_object.u.class_prop.extra_info >> ECMA_ASYNC_YIELD_ITERATOR_STATE_SHIFT) + +/** + * Set the state of an async yield iterator. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_SET_STATE(async_generator_object_p, to) \ + do \ + { \ + uint16_t extra_info = (async_generator_object_p)->extended_object.u.class_prop.extra_info; \ + extra_info &= ((1 << ECMA_ASYNC_YIELD_ITERATOR_STATE_SHIFT) - 1); \ + extra_info |= (ECMA_ASYNC_YIELD_ITERATOR_AWAIT_ ## to) << ECMA_ASYNC_YIELD_ITERATOR_STATE_SHIFT; \ + (async_generator_object_p)->extended_object.u.class_prop.extra_info = extra_info; \ + } \ + while (false) + +/** + * Helper value for ECMA_ASYNC_YIELD_ITERATOR_END. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_END_MASK \ + (((1 << ECMA_ASYNC_YIELD_ITERATOR_STATE_SHIFT) - 1) - ECMA_GENERATOR_ITERATE_AND_YIELD) + +/** + * Return from yield iterator. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_END(async_generator_object_p) \ + ((async_generator_object_p)->extended_object.u.class_prop.extra_info &= ECMA_ASYNC_YIELD_ITERATOR_END_MASK) + +/** + * Helper macro for ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_CS1(from, to) \ + ((ECMA_ASYNC_YIELD_ITERATOR_AWAIT_ ## from) ^ (ECMA_ASYNC_YIELD_ITERATOR_AWAIT_ ## to)) + +/** + * Helper macro for ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_CS2(from, to) \ + (ECMA_ASYNC_YIELD_ITERATOR_CS1(from, to) << ECMA_ASYNC_YIELD_ITERATOR_STATE_SHIFT) + +/** + * Change the state of an async yield iterator. + */ +#define ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE(async_generator_object_p, from, to) \ + ((async_generator_object_p)->extended_object.u.class_prop.extra_info ^= ECMA_ASYNC_YIELD_ITERATOR_CS2 (from, to)) + ecma_value_t ecma_async_generator_enqueue (vm_executable_object_t *async_generator_object_p, ecma_async_generator_operation_type_t operation, ecma_value_t value); void ecma_async_generator_run (vm_executable_object_t *async_generator_object_p); void ecma_async_generator_finalize (vm_executable_object_t *async_generator_object_p, ecma_value_t value); +ecma_value_t ecma_async_yield_continue_await (vm_executable_object_t *async_generator_object_p, ecma_value_t value); + #endif /* ENABLED (JERRY_ESNEXT) */ /** diff --git a/jerry-core/ecma/operations/ecma-container-object.c b/jerry-core/ecma/operations/ecma-container-object.c index 0ac118335..17741635d 100644 --- a/jerry-core/ecma/operations/ecma-container-object.c +++ b/jerry-core/ecma/operations/ecma-container-object.c @@ -439,7 +439,7 @@ ecma_op_container_create (const ecma_value_t *arguments_list_p, /**< arguments l ecma_object_t *adder_func_p = ecma_get_object_from_value (result); - result = ecma_op_get_iterator (iterable, ECMA_VALUE_EMPTY); + result = ecma_op_get_iterator (iterable, ECMA_VALUE_SYNC_ITERATOR); if (ECMA_IS_VALUE_ERROR (result)) { diff --git a/jerry-core/ecma/operations/ecma-iterator-object.c b/jerry-core/ecma/operations/ecma-iterator-object.c index b64a2319a..df6560088 100644 --- a/jerry-core/ecma/operations/ecma-iterator-object.c +++ b/jerry-core/ecma/operations/ecma-iterator-object.c @@ -181,12 +181,13 @@ ecma_op_get_iterator (ecma_value_t value, /**< value to get iterator from */ return value; } - /* 2. */ - bool has_method = !ecma_is_value_empty (method); + bool has_method = false; - if (!has_method) + /* 2. */ + if (method == ECMA_VALUE_SYNC_ITERATOR) { /* 2.a */ + has_method = true; method = ecma_op_get_method_by_symbol_id (value, LIT_GLOBAL_SYMBOL_ITERATOR); /* 2.b */ @@ -195,6 +196,17 @@ ecma_op_get_iterator (ecma_value_t value, /**< value to get iterator from */ return method; } } + else if (method == ECMA_VALUE_ASYNC_ITERATOR) + { + /* TODO: CreateAsyncFromSyncIterator should be supported. */ + has_method = true; + method = ecma_op_get_method_by_symbol_id (value, LIT_GLOBAL_SYMBOL_ASYNC_ITERATOR); + + if (ECMA_IS_VALUE_ERROR (method)) + { + return method; + } + } /* 3. */ if (!ecma_is_value_object (method) || !ecma_op_is_callable (method)) @@ -206,7 +218,7 @@ ecma_op_get_iterator (ecma_value_t value, /**< value to get iterator from */ ecma_object_t *method_obj_p = ecma_get_object_from_value (method); ecma_value_t iterator = ecma_op_function_call (method_obj_p, value, NULL, 0); - if (!has_method) + if (has_method) { ecma_deref_object (method_obj_p); } @@ -239,7 +251,7 @@ ecma_op_get_iterator (ecma_value_t value, /**< value to get iterator from */ * @return iterator result object - if success * raised error - otherwise */ -static ecma_value_t +ecma_value_t ecma_op_iterator_next (ecma_value_t iterator, /**< iterator value */ ecma_value_t value) /**< the routines's value argument */ { diff --git a/jerry-core/ecma/operations/ecma-iterator-object.h b/jerry-core/ecma/operations/ecma-iterator-object.h index 2c11a9f1d..c5daf3df6 100644 --- a/jerry-core/ecma/operations/ecma-iterator-object.h +++ b/jerry-core/ecma/operations/ecma-iterator-object.h @@ -59,6 +59,9 @@ ecma_op_get_iterator (ecma_value_t value, ecma_value_t method); ecma_value_t ecma_op_iterator_value (ecma_value_t iter_result); +ecma_value_t +ecma_op_iterator_next (ecma_value_t iterator, ecma_value_t value); + ecma_value_t ecma_op_iterator_close (ecma_value_t iterator); diff --git a/jerry-core/ecma/operations/ecma-jobqueue.c b/jerry-core/ecma/operations/ecma-jobqueue.c index 653042a06..7a13771ec 100644 --- a/jerry-core/ecma/operations/ecma-jobqueue.c +++ b/jerry-core/ecma/operations/ecma-jobqueue.c @@ -260,7 +260,49 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ if (ecma_job_queue_get_type (&job_p->header) == ECMA_JOB_PROMISE_ASYNC_REACTION_REJECTED) { - executable_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw; + if (!(executable_object_p->extended_object.u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD)) + { + executable_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw; + } + else if (ECMA_ASYNC_YIELD_ITERATOR_GET_STATE (executable_object_p) == ECMA_ASYNC_YIELD_ITERATOR_AWAIT_RETURN) + { + /* Unlike other operations, return captures rejected promises as well. */ + ECMA_ASYNC_YIELD_ITERATOR_CHANGE_STATE (executable_object_p, RETURN, OPERATION); + } + else + { + /* Exception: Abort iterators, clear all status. */ + ECMA_ASYNC_YIELD_ITERATOR_END (executable_object_p); + + JERRY_ASSERT (ecma_is_value_object (executable_object_p->frame_ctx.block_result)); + executable_object_p->frame_ctx.block_result = ECMA_VALUE_UNDEFINED; + executable_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw; + } + } + + if (executable_object_p->extended_object.u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD) + { + job_p->argument = ecma_async_yield_continue_await (executable_object_p, job_p->argument); + + if (ECMA_IS_VALUE_ERROR (job_p->argument)) + { + job_p->argument = jcontext_take_exception (); + executable_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw; + } + else if (executable_object_p->extended_object.u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD) + { + /* Continue iteration. */ + JERRY_ASSERT (job_p->argument == ECMA_VALUE_UNDEFINED); + + ecma_free_promise_async_reaction_job (job_p); + return ECMA_VALUE_UNDEFINED; + } + + /* End of yield*, clear all status. */ + ECMA_ASYNC_YIELD_ITERATOR_END (executable_object_p); + + JERRY_ASSERT (ecma_is_value_object (executable_object_p->frame_ctx.block_result)); + executable_object_p->frame_ctx.block_result = ECMA_VALUE_UNDEFINED; } ecma_value_t result = opfunc_resume_executable_object (executable_object_p, job_p->argument); diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index 361ca3d4c..518747cba 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -979,6 +979,30 @@ ecma_promise_async_then (ecma_value_t promise, /**< promise object */ ecma_free_value (value); } /* ecma_promise_async_then */ +/** + * Resolves the value and resume the execution of an async function after the resolve is completed + * + * @return ECMA_VALUE_UNDEFINED if not error is occured, an error otherwise + */ +ecma_value_t +ecma_promise_async_await (ecma_extended_object_t *async_generator_object_p, /**< async generator function */ + ecma_value_t value) /**< value to be resolved (takes the reference) */ +{ + ecma_value_t promise = ecma_make_object_value (ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE)); + ecma_value_t result = ecma_promise_reject_or_resolve (promise, value, true); + + ecma_free_value (value); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + ecma_promise_async_then (result, ecma_make_object_value ((ecma_object_t *) async_generator_object_p)); + ecma_free_value (result); + return ECMA_VALUE_UNDEFINED; +} /* ecma_promise_async_await */ + /** * @} * @} diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index 311ecf8c4..a32ef6502 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -92,6 +92,7 @@ ecma_value_t ecma_promise_new_capability (ecma_value_t constructor); 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); 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_object_t *object_p, ecma_promise_resolving_functions_t *funcs, bool create_already_resolved); void ecma_promise_free_resolving_functions (ecma_promise_resolving_functions_t *funcs); diff --git a/jerry-core/lit/lit-magic-strings.h b/jerry-core/lit/lit-magic-strings.h index 990264e6a..24a65724c 100644 --- a/jerry-core/lit/lit-magic-strings.h +++ b/jerry-core/lit/lit-magic-strings.h @@ -55,6 +55,7 @@ typedef enum LIT_GLOBAL_SYMBOL_HAS_INSTANCE, /**< @@hasInstance well known symbol */ LIT_GLOBAL_SYMBOL_IS_CONCAT_SPREADABLE, /**< @@isConcatSpreadable well known symbol */ LIT_GLOBAL_SYMBOL_ITERATOR, /**< @@iterator well known symbol */ + LIT_GLOBAL_SYMBOL_ASYNC_ITERATOR, /**< @@asyncIterator well known symbol */ LIT_GLOBAL_SYMBOL_MATCH, /**< @@match well known symbol */ LIT_GLOBAL_SYMBOL_REPLACE, /**< @@replace well known symbol */ LIT_GLOBAL_SYMBOL_SEARCH, /**< @@search well known symbol */ diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index 7c48754cd..ec96aed80 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -789,6 +789,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_IS_EXTENSIBLE, "isExtensible") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_TO_DATE_STRING_UL, "toDateString") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_TO_TIME_STRING_UL, "toTimeString") #endif +#if ENABLED (JERRY_ESNEXT) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ASYNC_ITERATOR, "asyncIterator") +#endif #if ENABLED (JERRY_BUILTIN_STRING) && ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_FROM_CODE_POINT_UL, "fromCodePoint") #endif @@ -997,7 +1000,9 @@ LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE (12, LIT_MAGIC_STRING_SET_ITERATOR_UL) #else LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE (12, LIT_MAGIC_STRING_CONFIGURABLE) #endif -#if ENABLED (JERRY_BUILTIN_STRING) && ENABLED (JERRY_ESNEXT) +#if ENABLED (JERRY_ESNEXT) +LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE (13, LIT_MAGIC_STRING_ASYNC_ITERATOR) +#elif ENABLED (JERRY_BUILTIN_STRING) && ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE (13, LIT_MAGIC_STRING_FROM_CODE_POINT_UL) #elif ENABLED (JERRY_BUILTIN_DATE) LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE (13, LIT_MAGIC_STRING_GET_UTC_MINUTES_UL) diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index 312f5a804..bc9ac6a39 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -319,6 +319,7 @@ LIT_MAGIC_STRING_FROM_CODE_POINT_UL = "fromCodePoint" LIT_MAGIC_STRING_IS_EXTENSIBLE = "isExtensible" LIT_MAGIC_STRING_TO_DATE_STRING_UL = "toDateString" LIT_MAGIC_STRING_TO_TIME_STRING_UL = "toTimeString" +LIT_MAGIC_STRING_ASYNC_ITERATOR = "asyncIterator" LIT_MAGIC_STRING_GET_UTC_MINUTES_UL = "getUTCMinutes" LIT_MAGIC_STRING_GET_UTC_SECONDS_UL = "getUTCSeconds" LIT_MAGIC_STRING_IS_PROTOTYPE_OF_UL = "isPrototypeOf" diff --git a/jerry-core/parser/js/byte-code.c b/jerry-core/parser/js/byte-code.c index ef671057b..aa26ede5c 100644 --- a/jerry-core/parser/js/byte-code.c +++ b/jerry-core/parser/js/byte-code.c @@ -27,7 +27,7 @@ JERRY_STATIC_ASSERT ((sizeof (cbc_uint16_arguments_t) % sizeof (jmem_cpointer_t) */ JERRY_STATIC_ASSERT (CBC_END == 238, number_of_cbc_opcodes_changed); -JERRY_STATIC_ASSERT (CBC_EXT_END == 121, +JERRY_STATIC_ASSERT (CBC_EXT_END == 122, number_of_cbc_ext_opcodes_changed); #if ENABLED (JERRY_PARSER) diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index f71785eb1..e1a410071 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -736,6 +736,8 @@ VM_OC_YIELD) \ CBC_OPCODE (CBC_EXT_ASYNC_YIELD, CBC_NO_FLAG, 0, \ VM_OC_ASYNC_YIELD) \ + CBC_OPCODE (CBC_EXT_ASYNC_YIELD_ITERATOR, CBC_NO_FLAG, 0, \ + VM_OC_ASYNC_YIELD_ITERATOR) \ CBC_OPCODE (CBC_EXT_AWAIT, CBC_NO_FLAG, 0, \ VM_OC_AWAIT) \ CBC_OPCODE (CBC_EXT_GENERATOR_AWAIT, CBC_NO_FLAG, 0, \ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 2ff3bd10f..20661d3df 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -1971,13 +1971,8 @@ parser_parse_unary_expression (parser_context_t *context_p, /**< context */ if (context_p->token.type == LEXER_MULTIPLY) { lexer_next_token (context_p); - opcode = CBC_EXT_YIELD_ITERATOR; - - /* TODO: support yield * in async generator. Currently a meaningless error is thrown. */ - if (context_p->status_flags & PARSER_IS_ASYNC_FUNCTION) - { - parser_raise_error (context_p, PARSER_ERR_INVALID_CHARACTER); - } + opcode = ((context_p->status_flags & PARSER_IS_ASYNC_FUNCTION) ? CBC_EXT_ASYNC_YIELD_ITERATOR + : CBC_EXT_YIELD_ITERATOR); } parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); diff --git a/jerry-core/vm/opcodes.c b/jerry-core/vm/opcodes.c index f08da36a0..362bf76df 100644 --- a/jerry-core/vm/opcodes.c +++ b/jerry-core/vm/opcodes.c @@ -339,7 +339,7 @@ opfunc_append_to_spread_array (ecma_value_t *stack_top_p, /**< current stack top ecma_value_t ret_value = ECMA_VALUE_ERROR; ecma_value_t spread_value = stack_top_p[i]; - ecma_value_t iterator = ecma_op_get_iterator (spread_value, ECMA_VALUE_EMPTY); + ecma_value_t iterator = ecma_op_get_iterator (spread_value, ECMA_VALUE_SYNC_ITERATOR); if (!ECMA_IS_VALUE_ERROR (iterator)) { @@ -432,7 +432,7 @@ opfunc_spread_arguments (ecma_value_t *stack_top_p, /**< pointer to the current ecma_value_t spread_value = *stack_top_p++; i++; - ecma_value_t iterator = ecma_op_get_iterator (spread_value, ECMA_VALUE_EMPTY); + ecma_value_t iterator = ecma_op_get_iterator (spread_value, ECMA_VALUE_SYNC_ITERATOR); if (!ECMA_IS_VALUE_ERROR (iterator)) { @@ -806,6 +806,36 @@ opfunc_resume_executable_object (vm_executable_object_t *executable_object_p, /* return result; } /* opfunc_resume_executable_object */ +/** + * Fulfill the next promise of the async generator with the value + */ +void +opfunc_async_generator_yield (ecma_extended_object_t *async_generator_object_p, /**< async generator object */ + ecma_value_t value) /**< value (takes the reference) */ +{ + ecma_async_generator_task_t *task_p; + task_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_async_generator_task_t, + async_generator_object_p->u.class_prop.u.head); + + ecma_value_t iter_result = ecma_create_iter_result_object (value, ECMA_VALUE_FALSE); + ecma_fulfill_promise (task_p->promise, iter_result); + + ecma_free_value (iter_result); + ecma_free_value (value); + + ecma_value_t next = task_p->next; + async_generator_object_p->u.class_prop.u.head = next; + + JERRY_ASSERT (task_p->operation_value == ECMA_VALUE_UNDEFINED); + jmem_heap_free_block (task_p, sizeof (ecma_async_generator_task_t)); + + if (!ECMA_IS_INTERNAL_VALUE_NULL (next)) + { + ecma_value_t executable_object = ecma_make_object_value ((ecma_object_t *) async_generator_object_p); + ecma_enqueue_promise_async_generator_job (executable_object); + } +} /* opfunc_async_generator_yield */ + /** * Implicit class constructor handler when the classHeritage is not present. * diff --git a/jerry-core/vm/opcodes.h b/jerry-core/vm/opcodes.h index e82967438..62ec4e71d 100644 --- a/jerry-core/vm/opcodes.h +++ b/jerry-core/vm/opcodes.h @@ -135,6 +135,9 @@ extern const uint8_t opfunc_resume_executable_object_with_return[]; ecma_value_t opfunc_resume_executable_object (vm_executable_object_t *executable_object_p, ecma_value_t value); +void +opfunc_async_generator_yield (ecma_extended_object_t *async_generator_object_p, ecma_value_t value); + ecma_value_t opfunc_create_implicit_class_constructor (uint8_t opcode); diff --git a/jerry-core/vm/vm-defines.h b/jerry-core/vm/vm-defines.h index d20fe348f..8ff163cc5 100644 --- a/jerry-core/vm/vm-defines.h +++ b/jerry-core/vm/vm-defines.h @@ -71,6 +71,12 @@ typedef struct vm_frame_ctx_t */ #define VM_GET_REGISTER(frame_ctx_p, i) (((ecma_value_t *) ((frame_ctx_p) + 1))[i]) +/** + * Get the executable object. + */ +#define VM_GET_EXECUTABLE_OBJECT(frame_ctx_p) \ + ((ecma_extended_object_t *) ((uintptr_t) (frame_ctx_p) - (uintptr_t) offsetof (vm_executable_object_t, frame_ctx))) + /** * Generator frame context. */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 0437a700d..7d5b3286f 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -2041,7 +2041,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ } case VM_OC_GET_ITERATOR: { - result = ecma_op_get_iterator (stack_top_p[-1], ECMA_VALUE_EMPTY); + result = ecma_op_get_iterator (stack_top_p[-1], ECMA_VALUE_SYNC_ITERATOR); if (ECMA_IS_VALUE_ERROR (result)) { @@ -2197,35 +2197,54 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ } case VM_OC_ASYNC_YIELD: { - const uintptr_t object_offset = (uintptr_t) offsetof (vm_executable_object_t, frame_ctx); - ecma_extended_object_t *async_generator_object_p; - async_generator_object_p = (ecma_extended_object_t *) (((uintptr_t) frame_ctx_p) - object_offset); + ecma_extended_object_t *async_generator_object_p = VM_GET_EXECUTABLE_OBJECT (frame_ctx_p); - ecma_async_generator_task_t *task_p; - task_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_async_generator_task_t, - async_generator_object_p->u.class_prop.u.head); - - ecma_value_t iter_result = ecma_create_iter_result_object (stack_top_p[-1], ECMA_VALUE_FALSE); - ecma_fulfill_promise (task_p->promise, iter_result); - - ecma_free_value (iter_result); - ecma_free_value (stack_top_p[-1]); - async_generator_object_p->u.class_prop.u.head = task_p->next; - - if (!ECMA_IS_INTERNAL_VALUE_NULL (task_p->next)) - { - ecma_value_t executable_object = ecma_make_object_value ((ecma_object_t *) async_generator_object_p); - ecma_enqueue_promise_async_generator_job (executable_object); - } - - JERRY_ASSERT (task_p->operation_value == ECMA_VALUE_UNDEFINED); - jmem_heap_free_block (task_p, sizeof (ecma_async_generator_task_t)); + opfunc_async_generator_yield (async_generator_object_p, stack_top_p[-1]); frame_ctx_p->call_operation = VM_EXEC_RETURN; frame_ctx_p->byte_code_p = byte_code_p; frame_ctx_p->stack_top_p = --stack_top_p; return ECMA_VALUE_UNDEFINED; } + case VM_OC_ASYNC_YIELD_ITERATOR: + { + ecma_extended_object_t *async_generator_object_p = VM_GET_EXECUTABLE_OBJECT (frame_ctx_p); + + JERRY_ASSERT (!(async_generator_object_p->u.class_prop.extra_info & ECMA_GENERATOR_ITERATE_AND_YIELD)); + + /* Byte code is executed at the first time. */ + left_value = *(--stack_top_p); + result = ecma_op_get_iterator (left_value, ECMA_VALUE_ASYNC_ITERATOR); + + if (ECMA_IS_VALUE_ERROR (result)) + { + goto error; + } + + ecma_free_value (left_value); + left_value = result; + result = ecma_op_iterator_next (left_value, ECMA_VALUE_UNDEFINED); + + if (ECMA_IS_VALUE_ERROR (result)) + { + goto error; + } + + result = ecma_promise_async_await (async_generator_object_p, result); + + if (ECMA_IS_VALUE_ERROR (result)) + { + goto error; + } + + async_generator_object_p->u.class_prop.extra_info |= ECMA_GENERATOR_ITERATE_AND_YIELD; + frame_ctx_p->block_result = left_value; + + frame_ctx_p->call_operation = VM_EXEC_RETURN; + frame_ctx_p->byte_code_p = byte_code_p; + frame_ctx_p->stack_top_p = stack_top_p; + return ECMA_VALUE_UNDEFINED; + } case VM_OC_AWAIT: { ecma_value_t promise = ecma_make_object_value (ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE)); @@ -2264,9 +2283,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ } else { - const uintptr_t object_offset = (uintptr_t) offsetof (vm_executable_object_t, frame_ctx); - - ecma_object_t *object_p = (ecma_object_t *) (((uintptr_t) frame_ctx_p) - object_offset); + ecma_object_t *object_p = (ecma_object_t *) VM_GET_EXECUTABLE_OBJECT (frame_ctx_p); ecma_promise_async_then (result, ecma_make_object_value (object_p)); ecma_free_value (result); @@ -2277,11 +2294,9 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ } case VM_OC_GENERATOR_AWAIT: { - ecma_value_t promise = ecma_make_object_value (ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE)); - ecma_value_t argument = *(--stack_top_p); + ecma_extended_object_t *async_generator_object_p = VM_GET_EXECUTABLE_OBJECT (frame_ctx_p); - result = ecma_promise_reject_or_resolve (promise, argument, true); - ecma_free_value (argument); + result = ecma_promise_async_await (async_generator_object_p, *(--stack_top_p)); if (ECMA_IS_VALUE_ERROR (result)) { @@ -2291,12 +2306,6 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ frame_ctx_p->call_operation = VM_EXEC_RETURN; frame_ctx_p->byte_code_p = byte_code_p; frame_ctx_p->stack_top_p = stack_top_p; - - const uintptr_t object_offset = (uintptr_t) offsetof (vm_executable_object_t, frame_ctx); - - ecma_object_t *object_p = (ecma_object_t *) (((uintptr_t) frame_ctx_p) - object_offset); - ecma_promise_async_then (result, ecma_make_object_value (object_p)); - ecma_free_value (result); return ECMA_VALUE_UNDEFINED; } case VM_OC_EXT_RETURN: @@ -3777,7 +3786,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ JERRY_ASSERT (VM_GET_REGISTERS (frame_ctx_p) + register_end + frame_ctx_p->context_depth == stack_top_p); - ecma_value_t iterator = ecma_op_get_iterator (value, ECMA_VALUE_EMPTY); + ecma_value_t iterator = ecma_op_get_iterator (value, ECMA_VALUE_SYNC_ITERATOR); ecma_free_value (value); diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 9d06a73a3..df4870cf1 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -272,6 +272,7 @@ typedef enum VM_OC_CREATE_GENERATOR, /**< create a generator object */ VM_OC_YIELD, /**< yield operation */ VM_OC_ASYNC_YIELD, /**< async yield operation */ + VM_OC_ASYNC_YIELD_ITERATOR, /**< async yield iterator operation */ VM_OC_AWAIT, /**< await operation */ VM_OC_GENERATOR_AWAIT, /**< generator await operation */ VM_OC_EXT_RETURN, /**< return which also clears the stack */ @@ -343,6 +344,7 @@ typedef enum VM_OC_CREATE_GENERATOR = VM_OC_NONE, /**< create a generator object */ VM_OC_YIELD = VM_OC_NONE, /**< yield operation */ VM_OC_ASYNC_YIELD = VM_OC_NONE, /**< async yield operation */ + VM_OC_ASYNC_YIELD_ITERATOR = VM_OC_NONE, /**< async yield iterator operation */ VM_OC_AWAIT = VM_OC_NONE, /**< await operation */ VM_OC_GENERATOR_AWAIT = VM_OC_NONE, /**< generator await operation */ VM_OC_EXT_RETURN = VM_OC_NONE, /**< return which also clears the stack */ diff --git a/tests/jerry/es.next/function-async-gen1.js b/tests/jerry/es.next/function-async-gen1.js index 5d67a3502..ccec5e3ca 100644 --- a/tests/jerry/es.next/function-async-gen1.js +++ b/tests/jerry/es.next/function-async-gen1.js @@ -114,58 +114,34 @@ check_rejected(gen.throw("End"), "End") // Test 4 async function *f4() { - if (state === 0) { - state = 1; - } - + assert(++state === 1) await 1 - - if (state === 3) { - state = 4; - } + assert(++state === 4) } var state = 0 gen = f4() gen.next() -if (state === 1) { - state = 2 -} - +assert(++state === 2) gen.next() - -if (state === 2) { - state = 3 -} +assert(++state === 3) // Test 5 async function *f5() { - if (state2 === 0) { - state2 = 1; - } - + assert(++state2 === 1) yield 1 - - if (state2 === 3) { - state2 = 4; - } + assert(++state2 === 4) } var state2 = 0 gen = f5() gen.next() -if (state2 === 1) { - state2 = 2 -} - +assert(++state2 === 2) gen.next() - -if (state2 === 2) { - state2 = 3 -} +assert(++state2 === 3) // Test 6 diff --git a/tests/jerry/es.next/function-async-gen2.js b/tests/jerry/es.next/function-async-gen2.js new file mode 100644 index 000000000..b5ffc5ed6 --- /dev/null +++ b/tests/jerry/es.next/function-async-gen2.js @@ -0,0 +1,304 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var successCount = 0 + +function check_fulfilled(p, value, done) +{ + assert(p instanceof Promise) + + p.then(function(v) { + assert(v.value === value) + assert(v.done === done) + successCount++ + }, function() { + assert(false) + }) +} + +function check_rejected(p, value) +{ + assert(p instanceof Promise) + + p.then(function(v) { + assert(false) + }, function(v) { + assert(v === value) + successCount++ + }) +} + +// Test 1 + +var o1 = {} +var arr1 = [] +var async1 = { + [Symbol.asyncIterator]() { + arr1 = [] + var i = 0 + return { + next(v) { + var res + + if (i == 0) { + assert(v === undefined) + res = { value:"Res", done:false } + } else if (i == 1) { + assert(v === "B") + res = { value:{}, done:false } + } else if (i == 2) { + assert(v === o1) + res = Promise.resolve("Nested") + res = { value:res, done:false } + } else { + assert(v === -1.5) + res = { value:3.5, done:true } + } + i++ + + arr1.push(res) + return Promise.resolve(res) + } + } + } +} + +async function *f1() { + successCount++ + assert((yield *async1) === 3.5) + successCount++ + return "End" +} + +async function f1_run() { + var gen = f1() + + var res = await gen.next("A") + assert(res != arr1[0]) + assert(res.value === "Res") + assert(arr1[0].value === "Res") + assert(res.done === false) + assert(arr1[0].done === false) + successCount++ + + var res = await gen.next("B") + assert(res != arr1[1]) + assert(res.value === arr1[1].value) + assert(res.done === false) + assert(arr1[1].done === false) + successCount++ + + var res = await gen.next(o1) + assert(res != arr1[2]) + assert(res.value === "Nested") + assert(arr1[2].value instanceof Promise) + assert(res.done === false) + assert(arr1[2].done === false) + successCount++ + + var res = await gen.next(-1.5) + assert(res.value === "End") + assert(res.done === true) + successCount++ +} + +f1_run() + +// Test 2 + +var o2 = {} +var async2 = { + [Symbol.asyncIterator]() { + return { + next() { + throw "Except" + } + } + } +} + +async function *f2() { + successCount++ + try { + try { + yield *async2 + assert(false) + } finally { + successCount++ + } + assert(false) + } catch (e) { + assert(e === "Except") + successCount++ + throw o2 + } + assert(false) +} + +var gen = f2() +check_rejected(gen.next(), o2) + +// Test 3 + +var o3 = {} +var async3 = { + [Symbol.asyncIterator]() { + var i = 0 + return { + next() { + if (i == 0) { + i++ + return { value:6.25, done:false } + } + throw o3 + } + } + } +} + +async function *f3() { + successCount++ + try { + try { + yield *async3 + assert(false) + } finally { + successCount++ + } + assert(false) + } catch (e) { + assert(e === o3) + successCount++ + return o3 + } + assert(false) +} + +var gen = f3() +check_fulfilled(gen.next(), 6.25, false) +check_fulfilled(gen.next(), o3, true) + +// Test 4 + +var async4 = { + [Symbol.asyncIterator]() { + return { + next() { + /* Returns with a promise which fails. */ + return { value:Promise.reject("Failed!"), done:false } + } + } + } +} + +async function *f4() { + successCount++ + try { + try { + yield *async4 + assert(false) + } finally { + successCount++ + } + assert(false) + } catch (e) { + assert(e === "Failed!") + successCount++ + return + } + assert(false) +} + +var gen = f4() +check_fulfilled(gen.next(), undefined, true) + +// Test 5 + +var async5 = { + [Symbol.asyncIterator]() { + return { + next() { + /* Returns with a promise which fails. */ + return { value:Promise.reject("FailedAndDone!"), done:true } + } + } + } +} + +async function *f5() { + successCount++ + try { + var p = yield *async5 + assert(p instanceof Promise) + check_rejected(p, "FailedAndDone!") + successCount++ + } catch (e) { + assert(false) + } +} + +var gen = f5() +check_fulfilled(gen.next(), undefined, true) + +// Test 6 + +var state = 0 + +var o6 = {} +var async6 = { + [Symbol.asyncIterator]() { + var i = 0 + assert(++state === 2) + + return { + next() { + i++ + if (i == 1) { + assert(++state === 3) + return { value:5.75, done:false } + } else if (i == 2) { + assert(++state === 7) + return { value:o6, done:false } + } else if (i == 3) { + assert(++state === 8) + return { value:"Val", done:true } + } + } + } + } +} + +async function *f6() { + assert(++state === 1) + + assert((yield *async6) === "Val") + + assert(++state === 9) + return "End" +} + +var gen = f6() +check_fulfilled(gen.next(), 5.75, false) +assert(++state === 4) +check_fulfilled(gen.next(), o6, false) +assert(++state === 5) +check_fulfilled(gen.next(), "End", true) +assert(++state === 6) + +// END + +function __checkAsync() { + assert(successCount === 26) + assert(state === 9) +} diff --git a/tests/jerry/es.next/function-async-gen3.js b/tests/jerry/es.next/function-async-gen3.js new file mode 100644 index 000000000..f816cf10c --- /dev/null +++ b/tests/jerry/es.next/function-async-gen3.js @@ -0,0 +1,382 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var successCount = 0 + +function check_fulfilled(p, value, done) +{ + assert(p instanceof Promise) + + p.then(function(v) { + assert(v.value === value) + assert(v.done === done) + successCount++ + }, function() { + assert(false) + }) +} + +function check_rejected(p, value) +{ + assert(p instanceof Promise) + + p.then(function(v) { + assert(false) + }, function(v) { + assert(v === value) + successCount++ + }) +} + +// Test 1 + +var o1 = Promise.reject("Err") +var async1 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:0, done:false } + }, + throw(v) { + assert(v === "Except") + /* Failed result result. */ + throw o1 + } + } + } +} + +async function *f1() { + successCount++ + try { + try { + yield *async1 + assert(false) + } finally { + successCount++ + } + assert(false) + } catch (e) { + assert(e === o1) + successCount++ + return + } + assert(false) +} + +var gen = f1() +check_fulfilled(gen.next(), 0, false) +check_fulfilled(gen.throw("Except"), undefined, true) + +// Test 2 + +var o2 = Promise.resolve("Message") +var async2 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:1, done:false } + }, + throw(v) { + assert(v === o2) + /* Successful result. */ + return { value:o2, done:false } + } + } + } +} + +async function *f2() { + successCount++ + try { + yield *async2 + } finally { + /* Never completes. */ + assert(false) + } +} + +var gen = f2() +check_fulfilled(gen.next(), 1, false) +check_fulfilled(gen.throw(o2), "Message", false) + +// Test 3 + +var o3 = Promise.resolve("Message") +var async3 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:2, done:false } + }, + throw(v) { + assert(v === -2.5) + /* Successful result. */ + return { value:o3, done:true } + } + } + } +} + +async function *f3() { + successCount++ + assert((yield *async3) === o3) + successCount++ + return -4.25 +} + +var gen = f3() +check_fulfilled(gen.next(), 2, false) +check_fulfilled(gen.throw(-2.5), -4.25, true) + +// Test 4 + +var async4 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:3, done:false } + } + } + } +} + +async function *f4() { + successCount++ + try { + yield *async4 + assert(false) + } catch (e) { + assert(e instanceof TypeError) + successCount++ + } +} + +var gen = f4() +check_fulfilled(gen.next(), 3, false) +check_fulfilled(gen.throw(), undefined, true) + +// Test 5 + +var async5 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:4, done:false } + }, + return(v) { + assert(v === undefined) + throw "Close!" + } + } + } +} + +async function *f5() { + successCount++ + try { + yield *async5 + assert(false) + } catch (e) { + assert(e === "Close!") + successCount++ + } +} + +var gen = f5() +check_fulfilled(gen.next(), 4, false) +check_fulfilled(gen.throw(1), undefined, true) + +// Test 6 + +var o6 = Promise.resolve("Return!") +var async6 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:5, done:false } + } + } + } +} + +async function *f6() { + successCount++ + try { + yield *async6 + assert(false) + } finally { + successCount++ + } +} + +var gen = f6() +check_fulfilled(gen.next(), 5, false) +check_fulfilled(gen.return(o6), "Return!", true) + +// Test 7 + +var arr = [] +var o7 = Promise.resolve(arr) +var async7 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:6, done:false } + }, + return(v) { + assert(v === arr) + /* Successful result. */ + return { value:o7, done:false } + } + } + } +} + +async function *f7() { + successCount++ + try { + yield *async7 + } finally { + /* Never completes. */ + assert(false) + } +} + +var gen = f7() +check_fulfilled(gen.next(), 6, false) +check_fulfilled(gen.return(o7), arr, false) + +// Test 8 + +var o8 = Promise.resolve(6.75) +var async8 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:7, done:false } + }, + return(v) { + assert(v === 6.75) + /* Successful result. */ + return { value:o8, done:true } + } + } + } +} + +async function *f8() { + successCount++ + try { + yield *async8 + } finally { + successCount++ + } + /* Return skips this code path. */ + assert(false) +} + +var gen = f8() +check_fulfilled(gen.next(), 7, false) +check_fulfilled(gen.return(o8), o8, true) + +// Test 9 + +var o9 = Promise.reject("reject") +var async9 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:8, done:false } + }, + throw(v) { + assert(v === "reject") + throw "End" + }, + get return() { + assert(false) + } + } + } +} + +async function *f9() { + successCount++ + try { + yield *async9 + } catch (e) { + successCount++ + assert(e === "End") + throw e + } + /* Throw skips this code path. */ + assert(false) +} + +var gen = f9() +check_fulfilled(gen.next(), 8, false) +check_rejected(gen.return(o9), "End") + +// Test 10 + +var o10 = Promise.reject(arr) +var async10 = { + [Symbol.asyncIterator]() { + return { + next(v) { + assert(v === undefined) + return { value:9, done:false } + }, + get return() { + successCount++ + return function() { + /* Only called during iterator close. */ + assert(arguments.length === 0) + successCount++ + } + } + } + } +} + +async function *f10() { + successCount++ + try { + yield *async10 + } catch (e) { + successCount++ + assert(e instanceof TypeError) + throw -3.25 + } + assert(false) +} + +var gen = f10() +check_fulfilled(gen.next(), 9, false) +check_rejected(gen.return(o10), -3.25) + +// END + +function __checkAsync() { + assert(successCount == 41) +}