From 633536729c84b4a87bb41dbb95f27487cb466386 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 1 Sep 2021 16:31:08 +0300 Subject: [PATCH] Introduced Async/Await implementation. This closes #419 issue on GitHub. --- auto/sources | 1 + src/njs_async.c | 245 +++++++++++++++++++++++ src/njs_async.h | 30 +++ src/njs_builtin.c | 12 +- src/njs_disassembler.c | 3 + src/njs_function.c | 161 +++++++++++++-- src/njs_function.h | 80 ++++++-- src/njs_generator.c | 49 ++++- src/njs_lexer.h | 3 + src/njs_main.h | 1 + src/njs_parser.c | 159 ++++++++++++--- src/njs_parser.h | 2 + src/njs_promise.c | 33 +-- src/njs_promise.h | 34 +++- src/njs_variable.c | 5 +- src/njs_vm.c | 4 +- src/njs_vm.h | 2 + src/njs_vmcode.c | 98 ++++++++- src/njs_vmcode.h | 9 + src/test/njs_unit_test.c | 76 +++++++ test/js/async_await_add.js | 7 + test/js/async_await_blank.js | 5 + test/js/async_await_catch.js | 5 + test/js/async_await_finally.js | 6 + test/js/async_await_for.js | 23 +++ test/js/async_await_inline.js | 11 + test/js/async_await_reject.js | 5 + test/js/async_await_stages.js | 28 +++ test/js/async_await_throw.js | 12 ++ test/js/async_await_throw_async.js | 15 ++ test/js/async_await_throw_catch.js | 12 ++ test/js/async_await_throw_catch_async.js | 15 ++ test/js/async_await_try_catch.js | 19 ++ test/js/async_await_try_finally.js | 20 ++ test/js/async_await_try_throw.js | 14 ++ test/js/async_await_try_throw_catch.js | 17 ++ test/njs_expect_test.exp | 63 ++++++ 37 files changed, 1189 insertions(+), 95 deletions(-) create mode 100644 src/njs_async.c create mode 100644 src/njs_async.h create mode 100644 test/js/async_await_add.js create mode 100644 test/js/async_await_blank.js create mode 100644 test/js/async_await_catch.js create mode 100644 test/js/async_await_finally.js create mode 100644 test/js/async_await_for.js create mode 100644 test/js/async_await_inline.js create mode 100644 test/js/async_await_reject.js create mode 100644 test/js/async_await_stages.js create mode 100644 test/js/async_await_throw.js create mode 100644 test/js/async_await_throw_async.js create mode 100644 test/js/async_await_throw_catch.js create mode 100644 test/js/async_await_throw_catch_async.js create mode 100644 test/js/async_await_try_catch.js create mode 100644 test/js/async_await_try_finally.js create mode 100644 test/js/async_await_try_throw.js create mode 100644 test/js/async_await_try_throw_catch.js diff --git a/auto/sources b/auto/sources index e2756186..4e5e65cc 100644 --- a/auto/sources +++ b/auto/sources @@ -60,6 +60,7 @@ NJS_LIB_SRCS=" \ src/njs_buffer.c \ src/njs_iterator.c \ src/njs_scope.c \ + src/njs_async.c \ " NJS_LIB_TEST_SRCS=" \ diff --git a/src/njs_async.c b/src/njs_async.c new file mode 100644 index 00000000..4d9b8104 --- /dev/null +++ b/src/njs_async.c @@ -0,0 +1,245 @@ + +/* + * Copyright (C) Alexander Borisov + * Copyright (C) Nginx, Inc. + */ + +#include + + +static void +njs_async_context_free(njs_vm_t *vm, njs_native_frame_t *frame); + + +njs_int_t +njs_async_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval) +{ + njs_int_t ret; + njs_value_t ctor; + njs_async_ctx_t *ctx; + njs_native_frame_t *frame; + + frame = vm->top_frame; + frame->retval = retval; + + ctx = frame->function->context; + + njs_set_function(&ctor, &vm->constructors[NJS_OBJ_TYPE_PROMISE]); + + ctx->capability = njs_promise_new_capability(vm, &ctor); + if (njs_slow_path(ctx->capability == NULL)) { + return NJS_ERROR; + } + + ret = njs_function_lambda_call(vm); + + if (ret == NJS_OK) { + ret = njs_function_call(vm, njs_function(&ctx->capability->resolve), + &njs_value_undefined, retval, 1, &vm->retval); + + } else if (ret == NJS_ERROR) { + if (njs_is_memory_error(vm, &vm->retval)) { + return NJS_ERROR; + } + + ret = njs_function_call(vm, njs_function(&ctx->capability->reject), + &njs_value_undefined, &vm->retval, 1, + &vm->retval); + } + + *retval = ctx->capability->promise; + + return ret; +} + + +njs_int_t +njs_await_fulfilled(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_int_t ret; + njs_value_t **cur_local, **cur_closures, **cur_temp, *value; + njs_frame_t *frame; + njs_async_ctx_t *ctx; + njs_native_frame_t *top, *async; + + ctx = vm->top_frame->function->context; + + value = njs_arg(args, nargs, 1); + if (njs_is_error(value)) { + goto failed; + } + + async = ctx->await; + + cur_local = vm->levels[NJS_LEVEL_LOCAL]; + cur_closures = vm->levels[NJS_LEVEL_CLOSURE]; + cur_temp = vm->levels[NJS_LEVEL_TEMP]; + top = vm->top_frame; + frame = vm->active_frame; + + vm->levels[NJS_LEVEL_LOCAL] = async->local; + vm->levels[NJS_LEVEL_CLOSURE] = njs_function_closures(async->function); + vm->levels[NJS_LEVEL_TEMP] = async->temp; + + vm->top_frame = async; + vm->active_frame = (njs_frame_t *) async; + + *njs_scope_value(vm, ctx->index) = *value; + vm->retval = *value; + + vm->top_frame->retval = &vm->retval; + + ret = njs_vmcode_interpreter(vm, async->pc); + + vm->levels[NJS_LEVEL_LOCAL] = cur_local; + vm->levels[NJS_LEVEL_CLOSURE] = cur_closures; + vm->levels[NJS_LEVEL_TEMP] = cur_temp; + + vm->top_frame = top; + vm->active_frame = frame; + + if (ret == NJS_OK) { + ret = njs_function_call(vm, njs_function(&ctx->capability->resolve), + &njs_value_undefined, &vm->retval, 1, &vm->retval); + + njs_async_context_free(vm, vm->top_frame); + + } else if (ret == NJS_ERROR) { + if (njs_is_memory_error(vm, &vm->retval)) { + return NJS_ERROR; + } + + value = &vm->retval; + + goto failed; + } + + return ret; + +failed: + + (void) njs_function_call(vm, njs_function(&ctx->capability->reject), + &njs_value_undefined, value, 1, &vm->retval); + + njs_async_context_free(vm, vm->top_frame); + + return NJS_ERROR; +} + + +njs_int_t +njs_await_rejected(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_value_t *value; + njs_async_ctx_t *ctx; + + ctx = vm->top_frame->function->context; + + value = njs_arg(args, nargs, 1); + + if (ctx->await->pc == ctx->pc) { + (void) njs_function_call(vm, njs_function(&ctx->capability->reject), + &njs_value_undefined, value, 1, &vm->retval); + + njs_async_context_free(vm, vm->top_frame); + + return NJS_ERROR; + } + + return njs_await_fulfilled(vm, args, nargs, unused); +} + + +static void +njs_async_context_free(njs_vm_t *vm, njs_native_frame_t *frame) +{ + njs_async_ctx_t *ctx; + + ctx = frame->function->context; + + njs_mp_free(vm->mem_pool, ctx->capability); + njs_mp_free(vm->mem_pool, ctx); + + frame->function->context = NULL; +} + + +static const njs_object_prop_t njs_async_constructor_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("AsyncFunction"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 1.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, +}; + + +const njs_object_init_t njs_async_constructor_init = { + njs_async_constructor_properties, + njs_nitems(njs_async_constructor_properties), +}; + + +static const njs_object_prop_t njs_async_prototype_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + .value = njs_string("AsyncFunction"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .configurable = 1, + }, +}; + + +const njs_object_init_t njs_async_prototype_init = { + njs_async_prototype_properties, + njs_nitems(njs_async_prototype_properties), +}; + + +const njs_object_type_init_t njs_async_function_type_init = { + .constructor = njs_native_ctor(njs_function_constructor, 1, 1), + .constructor_props = &njs_async_constructor_init, + .prototype_props = &njs_async_prototype_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +const njs_object_prop_t njs_async_function_instance_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("length"), + .value = njs_prop_handler(njs_function_instance_length), + .configurable = 1, + }, +}; + + +const njs_object_init_t njs_async_function_instance_init = { + njs_async_function_instance_properties, + njs_nitems(njs_async_function_instance_properties), +}; diff --git a/src/njs_async.h b/src/njs_async.h new file mode 100644 index 00000000..598abfbc --- /dev/null +++ b/src/njs_async.h @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Alexander Borisov + * Copyright (C) Nginx, Inc. + */ + +#ifndef _NJS_ASYNC_H_INCLUDED_ +#define _NJS_ASYNC_H_INCLUDED_ + + +typedef struct { + njs_promise_capability_t *capability; + njs_native_frame_t *await; + uintptr_t index; + u_char *pc; +} njs_async_ctx_t; + + +njs_int_t njs_async_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval); +njs_int_t njs_await_fulfilled(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused); +njs_int_t njs_await_rejected(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused); + + +extern const njs_object_type_init_t njs_async_function_type_init; +extern const njs_object_init_t njs_async_function_instance_init; + + +#endif /* _NJS_ASYNC_H_INCLUDED_ */ diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 3611a2a7..4e48d6ab 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -74,6 +74,7 @@ static const njs_object_type_init_t *const &njs_symbol_type_init, &njs_string_type_init, &njs_function_type_init, + &njs_async_function_type_init, &njs_regexp_type_init, &njs_date_type_init, &njs_promise_type_init, @@ -181,6 +182,12 @@ njs_builtin_objects_create(njs_vm_t *vm) return NJS_ERROR; } + ret = njs_object_hash_init(vm, &shared->async_function_instance_hash, + &njs_async_function_instance_init); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + ret = njs_object_hash_init(vm, &shared->arrow_instance_hash, &njs_arrow_instance_init); if (njs_slow_path(ret != NJS_OK)) { @@ -342,7 +349,7 @@ njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global) size_t size; njs_uint_t i; njs_object_t *object_prototype, *function_prototype, - *typed_array_prototype, *error_prototype, + *typed_array_prototype, *error_prototype, *async_prototype, *typed_array_ctor, *error_ctor; /* @@ -384,6 +391,9 @@ njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global) function_prototype = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; + async_prototype = &vm->prototypes[NJS_OBJ_TYPE_ASYNC_FUNCTION].object; + async_prototype->__proto__ = function_prototype; + for (i = NJS_OBJ_TYPE_OBJECT; i < NJS_OBJ_TYPE_NORMAL_MAX; i++) { vm->constructors[i].object.__proto__ = function_prototype; } diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index 010d53ef..4c93a04f 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -156,6 +156,9 @@ static njs_code_name_t code_names[] = { { NJS_VMCODE_DEBUGGER, sizeof(njs_vmcode_debugger_t), njs_str("DEBUGGER ") }, + + { NJS_VMCODE_AWAIT, sizeof(njs_vmcode_await_t), + njs_str("AWAIT ") }, }; diff --git a/src/njs_function.c b/src/njs_function.c index 087765d4..c3221177 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -9,9 +9,11 @@ njs_function_t * -njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda) +njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, + njs_bool_t async) { size_t size; + njs_object_t *proto; njs_function_t *function; size = sizeof(njs_function_t) + lambda->nclosures * sizeof(njs_value_t *); @@ -34,12 +36,23 @@ njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda) if (function->ctor) { function->object.shared_hash = vm->shared->function_instance_hash; + } else if (async) { + function->object.shared_hash = vm->shared->async_function_instance_hash; + } else { function->object.shared_hash = vm->shared->arrow_instance_hash; } - function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; + if (async) { + proto = &vm->prototypes[NJS_OBJ_TYPE_ASYNC_FUNCTION].object; + + } else { + proto = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; + } + + function->object.__proto__ = proto; function->object.type = NJS_FUNCTION; + function->object.extensible = 1; return function; @@ -73,7 +86,8 @@ njs_vm_function_alloc(njs_vm_t *vm, njs_function_native_t native) njs_function_t * njs_function_value_copy(njs_vm_t *vm, njs_value_t *value) { - njs_function_t *function, *copy; + njs_function_t *function, *copy; + njs_object_type_t type; function = njs_function(value); @@ -87,9 +101,14 @@ njs_function_value_copy(njs_vm_t *vm, njs_value_t *value) return NULL; } + type = njs_function_object_type(vm, function); + if (copy->ctor) { copy->object.shared_hash = vm->shared->function_instance_hash; + } else if (type == NJS_OBJ_TYPE_ASYNC_FUNCTION) { + copy->object.shared_hash = vm->shared->async_function_instance_hash; + } else { copy->object.shared_hash = vm->shared->arrow_instance_hash; } @@ -173,9 +192,10 @@ njs_function_name_set(njs_vm_t *vm, njs_function_t *function, njs_function_t * njs_function_copy(njs_vm_t *vm, njs_function_t *function) { - size_t size, n; - njs_value_t **from, **to; - njs_function_t *copy; + size_t size, n; + njs_value_t **from, **to; + njs_function_t *copy; + njs_object_type_t type; n = (function->native) ? 0 : function->u.lambda->nclosures; @@ -187,7 +207,10 @@ njs_function_copy(njs_vm_t *vm, njs_function_t *function) } *copy = *function; - copy->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; + + type = njs_function_object_type(vm, function); + + copy->object.__proto__ = &vm->prototypes[type].object; copy->object.shared = 0; if (n == 0) { @@ -404,6 +427,7 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, njs_value_t *value, *bound, **new, **temp; njs_frame_t *frame; njs_function_t *target; + njs_async_ctx_t *ctx; njs_native_frame_t *native_frame; njs_function_lambda_t *lambda; @@ -430,6 +454,17 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, lambda = target->u.lambda; } + if (njs_function_object_type(vm, target) == NJS_OBJ_TYPE_ASYNC_FUNCTION) { + ctx = njs_mp_alloc(vm->mem_pool, sizeof(njs_async_ctx_t)); + if (njs_slow_path(ctx == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + ctx->await = NULL; + target->context = ctx; + } + args_count = function->args_offset + njs_max(nargs, lambda->nargs); value_count = args_count + njs_max(args_count, lambda->nlocal); @@ -724,6 +759,29 @@ njs_function_native_call(njs_vm_t *vm) } +njs_int_t +njs_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval) +{ + njs_native_frame_t *frame; + + frame = vm->top_frame; + frame->retval = retval; + + if (njs_function_object_type(vm, frame->function) + == NJS_OBJ_TYPE_ASYNC_FUNCTION) + { + return njs_async_function_frame_invoke(vm, retval); + } + + if (frame->native) { + return njs_function_native_call(vm); + + } else { + return njs_function_lambda_call(vm); + } +} + + void njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native) { @@ -744,6 +802,69 @@ njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native) } +njs_int_t +njs_function_frame_save(njs_vm_t *vm, njs_native_frame_t *native, u_char *pc) +{ + size_t value_count, n; + njs_value_t *start, *end, *p, **new, *value, **local; + njs_function_t *function; + njs_native_frame_t *active; + + active = &vm->active_frame->native; + value_count = njs_function_frame_value_count(active); + + function = active->function; + + new = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE); + value = (njs_value_t *) (new + value_count + + function->u.lambda->temp); + + *native = *active; + + native->arguments = value; + native->arguments_offset = value + (function->args_offset - 1); + native->local = new + njs_function_frame_args_count(active); + native->temp = new + value_count; + native->pc = pc; + + start = njs_function_frame_values(active, &end); + p = native->arguments; + + while (start < end) { + *p = *start++; + *new++ = p++; + } + + /* Move all arguments. */ + + p = native->arguments; + local = native->local + function->args_offset; + + for (n = 0; n < function->args_count; n++) { + if (!njs_is_valid(p)) { + njs_set_undefined(p); + } + + *local++ = p++; + } + + return NJS_OK; +} + + +njs_object_type_t +njs_function_object_type(njs_vm_t *vm, njs_function_t *function) +{ + if (function->object.shared_hash.slot + == vm->shared->async_function_instance_hash.slot) + { + return NJS_OBJ_TYPE_ASYNC_FUNCTION; + } + + return NJS_OBJ_TYPE_FUNCTION; +} + + njs_int_t njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, njs_function_lambda_t *lambda) @@ -970,9 +1091,9 @@ njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop, } -static njs_int_t +njs_int_t njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused) + njs_index_t async) { njs_chb_t chain; njs_int_t ret; @@ -997,13 +1118,27 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, NJS_TOKEN_ILLEGAL }; + static const njs_token_type_t safe_ast_async[] = { + NJS_TOKEN_END, + NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION, + NJS_TOKEN_STATEMENT, + NJS_TOKEN_RETURN, + NJS_TOKEN_THIS, + NJS_TOKEN_ILLEGAL + }; + if (!vm->options.unsafe && nargs != 2) { goto fail; } njs_chb_init(&chain, vm->mem_pool); - njs_chb_append_literal(&chain, "(function("); + if (async) { + njs_chb_append_literal(&chain, "(async function("); + + } else { + njs_chb_append_literal(&chain, "(function("); + } for (i = 1; i < nargs - 1; i++) { ret = njs_value_to_chain(vm, &chain, njs_argument(args, i)); @@ -1055,7 +1190,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, */ node = parser.node; - type = &safe_ast[0]; + type = (async) ? &safe_ast_async[0] : &safe_ast[0]; for (; *type != NJS_TOKEN_ILLEGAL; type++, node = node->right) { if (node == NULL) { @@ -1097,7 +1232,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, lambda = ((njs_vmcode_function_t *) generator.code_start)->lambda; - function = njs_function_alloc(vm, lambda); + function = njs_function_alloc(vm, lambda, (njs_bool_t) async); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } @@ -1147,7 +1282,7 @@ const njs_object_init_t njs_function_constructor_init = { }; -static njs_int_t +njs_int_t njs_function_instance_length(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { diff --git a/src/njs_function.h b/src/njs_function.h index 7f4b814f..07e058bd 100644 --- a/src/njs_function.h +++ b/src/njs_function.h @@ -85,7 +85,8 @@ struct njs_frame_s { }; -njs_function_t *njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda); +njs_function_t *njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, + njs_bool_t async); njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value); njs_int_t njs_function_name_set(njs_vm_t *vm, njs_function_t *function, njs_value_t *name, const char *prefix); @@ -96,6 +97,10 @@ njs_int_t njs_function_rest_parameters_init(njs_vm_t *vm, njs_native_frame_t *frame); njs_int_t njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +njs_int_t njs_function_constructor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +njs_int_t njs_function_instance_length(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); njs_int_t njs_eval_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); njs_int_t njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, @@ -111,10 +116,15 @@ njs_int_t njs_function_lambda_call(njs_vm_t *vm); njs_int_t njs_function_native_call(njs_vm_t *vm); njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size); void njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *frame); +njs_int_t njs_function_frame_save(njs_vm_t *vm, njs_native_frame_t *native, + u_char *pc); +njs_object_type_t njs_function_object_type(njs_vm_t *vm, + njs_function_t *function); njs_int_t njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, njs_function_lambda_t *lambda); njs_int_t njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function); +njs_int_t njs_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval); njs_inline njs_function_lambda_t * @@ -161,23 +171,6 @@ njs_function_previous_frame(njs_native_frame_t *frame) } -njs_inline njs_int_t -njs_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval) -{ - njs_native_frame_t *frame; - - frame = vm->top_frame; - frame->retval = retval; - - if (frame->native) { - return njs_function_native_call(vm); - - } else { - return njs_function_lambda_call(vm); - } -} - - njs_inline njs_int_t njs_function_call(njs_vm_t *vm, njs_function_t *function, const njs_value_t *this, const njs_value_t *args, @@ -210,6 +203,57 @@ njs_function_closures(const njs_function_t *func) } +njs_inline size_t +njs_function_frame_size(njs_native_frame_t *frame) +{ + size_t size; + uintptr_t start; + + start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); + size = ((uintptr_t) frame->arguments - start) / sizeof(njs_value_t *); + + return NJS_FRAME_SIZE + (size * sizeof(njs_value_t *)) + + (size * sizeof(njs_value_t)); +} + + +njs_inline size_t +njs_function_frame_args_count(njs_native_frame_t *frame) +{ + uintptr_t start; + + start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); + + return ((uintptr_t) frame->local - start) / sizeof(njs_value_t *); +} + + +njs_inline size_t +njs_function_frame_value_count(njs_native_frame_t *frame) +{ + uintptr_t start; + + start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); + + return ((uintptr_t) frame->temp - start) / sizeof(njs_value_t *); +} + + +njs_inline njs_value_t * +njs_function_frame_values(njs_native_frame_t *frame, njs_value_t **end) +{ + size_t count; + uintptr_t start; + + start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); + count = ((uintptr_t) frame->arguments - start) / sizeof(njs_value_t *); + + *end = frame->arguments + count; + + return frame->arguments; +} + + extern const njs_object_type_init_t njs_function_type_init; extern const njs_object_init_t njs_function_instance_init; extern const njs_object_init_t njs_arrow_instance_init; diff --git a/src/njs_generator.c b/src/njs_generator.c index 6c31b199..eb32411c 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -325,6 +325,10 @@ static njs_int_t njs_generate_export_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_export_statement_end(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_await(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node); +static njs_int_t njs_generate_await_end(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_wo_dest(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_wo_dest_after(njs_vm_t *vm, @@ -658,6 +662,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) return njs_generate_array(vm, generator, node); case NJS_TOKEN_FUNCTION_EXPRESSION: + case NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION: return njs_generate_function_expression(vm, generator, node); case NJS_TOKEN_FUNCTION: @@ -679,6 +684,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) return njs_generate_name(vm, generator, node); case NJS_TOKEN_FUNCTION_DECLARATION: + case NJS_TOKEN_ASYNC_FUNCTION_DECLARATION: return njs_generate_function_declaration(vm, generator, node); case NJS_TOKEN_FUNCTION_CALL: @@ -702,6 +708,9 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_EXPORT: return njs_generate_export_statement(vm, generator, node); + case NJS_TOKEN_AWAIT: + return njs_generate_await(vm, generator, node); + default: njs_thread_log_debug("unknown token: %d", node->token); njs_internal_error(vm, "Generator failed: unknown token"); @@ -3055,6 +3064,7 @@ njs_generate_function_expression(njs_vm_t *vm, njs_generator_t *generator, njs_generate_code(generator, njs_vmcode_function_t, function, NJS_VMCODE_FUNCTION, 1, node); function->lambda = lambda; + function->async = (node->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION); node->index = njs_generate_object_dest_index(vm, generator, node); if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { @@ -3090,6 +3100,7 @@ njs_generate_function(njs_vm_t *vm, njs_generator_t *generator, njs_generate_code(generator, njs_vmcode_function_t, function, NJS_VMCODE_FUNCTION, 1, node); function->lambda = lambda; + function->async = 0; node->index = njs_generate_object_dest_index(vm, generator, node); if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { @@ -3555,6 +3566,7 @@ njs_generate_function_declaration(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { njs_int_t ret; + njs_bool_t async; njs_variable_t *var; njs_function_t *function; njs_function_lambda_t *lambda; @@ -3587,7 +3599,8 @@ njs_generate_function_declaration(njs_vm_t *vm, njs_generator_t *generator, return ret; } - function = njs_function_alloc(vm, lambda); + async = (node->token_type == NJS_TOKEN_ASYNC_FUNCTION_DECLARATION); + function = njs_function_alloc(vm, lambda, async); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } @@ -4655,6 +4668,40 @@ njs_generate_export_statement_end(njs_vm_t *vm, njs_generator_t *generator, } +static njs_int_t +njs_generate_await(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_generator_next(generator, njs_generate, node->right); + + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), node, + njs_generate_await_end, NULL, 0); +} + + +static njs_int_t +njs_generate_await_end(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_index_t index; + njs_vmcode_await_t *code; + + index = node->right->index; + + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + + njs_generate_code(generator, njs_vmcode_await_t, code, + NJS_VMCODE_AWAIT, 1, node); + code->retval = index; + node->index = index; + + return njs_generator_stack_pop(vm, generator, NULL); +} + + static njs_int_t njs_generate_wo_dest(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) diff --git a/src/njs_lexer.h b/src/njs_lexer.h index 32c0dda4..b20c5934 100644 --- a/src/njs_lexer.h +++ b/src/njs_lexer.h @@ -148,6 +148,9 @@ typedef enum { NJS_TOKEN_ARGUMENT, NJS_TOKEN_RETURN, + NJS_TOKEN_ASYNC_FUNCTION_DECLARATION, + NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION, + NJS_TOKEN_REGEXP, NJS_TOKEN_EXTERNAL, diff --git a/src/njs_main.h b/src/njs_main.h index 5b23d372..4e8722ff 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -73,6 +73,7 @@ #include #include #include +#include #include #include diff --git a/src/njs_parser.c b/src/njs_parser.c index 653e9081..b8e64f71 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -132,6 +132,10 @@ static njs_int_t njs_parser_unary_expression_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_unary_expression_next(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); +static njs_int_t njs_parser_await(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current); +static njs_int_t njs_parser_await_after(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_exponentiation_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); @@ -732,14 +736,6 @@ njs_parser_async_generator_expression(njs_parser_t *parser, } -static njs_int_t -njs_parser_async_function_expression(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current) -{ - return njs_parser_not_supported(parser, token); -} - - static njs_int_t njs_parser_generator_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -757,15 +753,11 @@ njs_parser_class_declaration(njs_parser_t *parser, njs_lexer_token_t *token, static njs_int_t -njs_parser_function_or_generator(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current) +njs_parser_function_or_generator_handler(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current, njs_bool_t is_async) { njs_parser_node_t *node, *cur; - if (token->type != NJS_TOKEN_FUNCTION) { - return NJS_DECLINED; - } - cur = parser->node; if (token->type == NJS_TOKEN_MULTIPLICATION) { @@ -773,7 +765,13 @@ njs_parser_function_or_generator(njs_parser_t *parser, njs_parser_next(parser, njs_parser_generator_declaration); } else { - node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_DECLARATION); + if (is_async) { + node = njs_parser_node_new(parser, + NJS_TOKEN_ASYNC_FUNCTION_DECLARATION); + } else { + node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_DECLARATION); + } + if (node == NULL) { return NJS_ERROR; } @@ -790,6 +788,18 @@ njs_parser_function_or_generator(njs_parser_t *parser, } +static njs_int_t +njs_parser_function_or_generator(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current) +{ + if (token->type != NJS_TOKEN_FUNCTION) { + return NJS_DECLINED; + } + + return njs_parser_function_or_generator_handler(parser, token, current, 0); +} + + static njs_int_t njs_parser_async_function_or_generator(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -807,13 +817,9 @@ njs_parser_async_function_or_generator(njs_parser_t *parser, return NJS_DECLINED; } - if (token->type == NJS_TOKEN_MULTIPLICATION) { - njs_parser_next(parser, njs_parser_generator_declaration); - } else { - njs_parser_next(parser, njs_parser_async_function_expression); - } + njs_lexer_consume_token(parser->lexer, 1); - return NJS_OK; + return njs_parser_function_or_generator_handler(parser, token, current, 1); } @@ -1078,6 +1084,8 @@ njs_parser_primary_expression_test(njs_parser_t *parser, goto reference; } + njs_lexer_consume_token(parser->lexer, 1); + next = njs_lexer_peek_token(parser->lexer, next, 0); if (njs_slow_path(next == NULL)) { return NJS_ERROR; @@ -1085,14 +1093,22 @@ njs_parser_primary_expression_test(njs_parser_t *parser, /* GeneratorExpression */ if (next->type == NJS_TOKEN_MULTIPLICATION) { - njs_lexer_consume_token(parser->lexer, 1); njs_parser_next(parser, njs_parser_async_generator_expression); } else { - njs_parser_next(parser, njs_parser_async_function_expression); + node = njs_parser_node_new(parser, + NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION); + if (node == NULL) { + return NJS_ERROR; + } + + node->token_line = next->line; + parser->node = node; + + njs_parser_next(parser, njs_parser_function_expression); } - return NJS_OK; + break; /* RegularExpressionLiteral */ case NJS_TOKEN_DIVISION: @@ -2707,6 +2723,8 @@ njs_parser_arguments(njs_parser_t *parser, njs_lexer_token_t *token, * ArgumentList , ) */ + parser->in_args = 1; + if (token->type == NJS_TOKEN_CLOSE_PARENTHESIS) { njs_lexer_consume_token(parser->lexer, 1); return njs_parser_stack_pop(parser); @@ -2723,6 +2741,8 @@ static njs_int_t njs_parser_parenthesis_or_comma(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { + parser->in_args = 0; + if (token->type == NJS_TOKEN_CLOSE_PARENTHESIS) { njs_lexer_consume_token(parser->lexer, 1); return njs_parser_stack_pop(parser); @@ -3337,7 +3357,8 @@ njs_parser_unary_expression(njs_parser_t *parser, njs_lexer_token_t *token, /* AwaitExpression */ case NJS_TOKEN_AWAIT: - return njs_parser_not_supported(parser, token); + njs_parser_next(parser, njs_parser_await); + return NJS_OK; default: njs_parser_next(parser, njs_parser_update_expression); @@ -3441,6 +3462,57 @@ njs_parser_unary_expression_next(njs_parser_t *parser, } +static njs_int_t +njs_parser_await(njs_parser_t *parser, njs_lexer_token_t *token, + njs_queue_link_t *current) +{ + njs_parser_node_t *node; + njs_parser_scope_t *scope; + + scope = njs_function_scope(parser->scope); + + if (!scope->async) { + njs_parser_syntax_error(parser, + "await is only valid in async functions"); + return NJS_ERROR; + } + + node = parser->node; + + if (parser->in_args) { + njs_parser_syntax_error(parser, "await in arguments not supported"); + return NJS_ERROR; + } + + node = njs_parser_node_new(parser, NJS_TOKEN_AWAIT); + if (njs_slow_path(node == NULL)) { + return NJS_ERROR; + } + + node->token_line = token->line; + + njs_lexer_consume_token(parser->lexer, 1); + + parser->node = NULL; + + njs_parser_next(parser, njs_parser_unary_expression); + + return njs_parser_after(parser, current, node, 0, + njs_parser_await_after); +} + + +static njs_int_t +njs_parser_await_after(njs_parser_t *parser, njs_lexer_token_t *token, + njs_queue_link_t *current) +{ + parser->target->right = parser->node; + parser->node = parser->target; + + return njs_parser_stack_pop(parser); +} + + /* * 12.6 Exponentiation Operator. */ @@ -4130,6 +4202,13 @@ njs_parser_match_arrow_expression(njs_parser_t *parser, { njs_bool_t rest_parameters; + if (token->type == NJS_TOKEN_ASYNC) { + token = njs_lexer_peek_token(parser->lexer, token, 1); + if (token == NULL) { + return NJS_ERROR; + } + } + if (token->type != NJS_TOKEN_OPEN_PARENTHESIS && !njs_lexer_token_is_binding_identifier(token)) { @@ -6573,6 +6652,7 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, { njs_int_t ret; uintptr_t unique_id; + njs_bool_t async; njs_variable_t *var; njs_parser_node_t *node; @@ -6619,6 +6699,9 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } + async = (node->token_type == NJS_TOKEN_ASYNC_FUNCTION_DECLARATION); + parser->scope->async = async; + njs_parser_next(parser, njs_parser_function_parse); return njs_parser_after(parser, current, node, 1, @@ -6675,6 +6758,7 @@ njs_parser_function_expression(njs_parser_t *parser, njs_lexer_token_t *token, { njs_int_t ret; uintptr_t unique_id; + njs_bool_t async; njs_variable_t *var; njs_function_lambda_t *lambda; @@ -6683,6 +6767,9 @@ njs_parser_function_expression(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } + async = (parser->node->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION); + parser->scope->async = async; + var = NULL; if (njs_lexer_token_is_binding_identifier(token)) { @@ -6725,7 +6812,7 @@ njs_parser_function_expression(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } - lambda = njs_function_lambda_alloc(parser->vm, 1); + lambda = njs_function_lambda_alloc(parser->vm, !async); if (lambda == NULL) { return NJS_ERROR; } @@ -6895,11 +6982,27 @@ njs_parser_arrow_function(njs_parser_t *parser, njs_lexer_token_t *token, { njs_int_t ret; uintptr_t unique_id; + njs_bool_t async; njs_variable_t *arg, *var; njs_parser_node_t *node, *name; njs_function_lambda_t *lambda; - node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); + if (token->type == NJS_TOKEN_ASYNC) { + njs_lexer_consume_token(parser->lexer, 1); + + token = njs_lexer_token(parser->lexer, 0); + if (token == NULL) { + return NJS_ERROR; + } + + async = 1; + node = njs_parser_node_new(parser, NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION); + + } else { + async = 0; + node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); + } + if (node == NULL) { return NJS_ERROR; } @@ -6912,6 +7015,8 @@ njs_parser_arrow_function(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } + parser->scope->async = async; + name = njs_parser_node_new(parser, NJS_TOKEN_NAME); if (name == NULL) { return NJS_ERROR; diff --git a/src/njs_parser.h b/src/njs_parser.h index 97a27441..399a489b 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -30,6 +30,7 @@ struct njs_parser_scope_s { uint8_t module; uint8_t arrow_function; uint8_t dest_disable; + uint8_t async; }; @@ -82,6 +83,7 @@ struct njs_parser_s { uintptr_t undefined_id; njs_bool_t strict_semicolon; uint32_t line; + njs_bool_t in_args; }; diff --git a/src/njs_promise.c b/src/njs_promise.c index 34e48e10..49c3bb83 100644 --- a/src/njs_promise.c +++ b/src/njs_promise.c @@ -7,12 +7,6 @@ #include -typedef enum { - NJS_PROMISE_PENDING = 0, - NJS_PROMISE_FULFILL, - NJS_PROMISE_REJECTED -} njs_promise_type_t; - typedef enum { NJS_PROMISE_HANDLE = 0, NJS_PROMISE_REJECT @@ -24,20 +18,6 @@ typedef enum { NJS_PROMISE_ANY } njs_promise_function_type_t; -typedef struct { - njs_promise_type_t state; - njs_value_t result; - njs_queue_t fulfill_queue; - njs_queue_t reject_queue; - njs_bool_t is_handled; -} njs_promise_data_t; - -typedef struct { - njs_value_t promise; - njs_value_t resolve; - njs_value_t reject; -} njs_promise_capability_t; - typedef struct { njs_promise_capability_t *capability; njs_promise_type_t type; @@ -84,13 +64,8 @@ static njs_int_t njs_promise_host_rejection_tracker(njs_vm_t *vm, njs_promise_t *promise, njs_promise_rejection_type_t operation); static njs_int_t njs_promise_resolve_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t retval); -static njs_promise_t *njs_promise_resolve(njs_vm_t *vm, - njs_value_t *constructor, njs_value_t *x); static njs_int_t njs_promise_reject_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t retval); -static njs_int_t njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value, - njs_value_t *fulfilled, njs_value_t *rejected, - njs_promise_capability_t *capability); static njs_int_t njs_promise_then_finally_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); static njs_int_t njs_promise_then_finally_return(njs_vm_t *vm, @@ -254,7 +229,7 @@ njs_promise_constructor_call(njs_vm_t *vm, njs_function_t *function) } -static njs_function_t * +njs_function_t * njs_promise_create_function(njs_vm_t *vm, size_t context_size) { njs_function_t *function; @@ -332,7 +307,7 @@ njs_promise_create_resolving_functions(njs_vm_t *vm, njs_promise_t *promise, } -static njs_promise_capability_t * +njs_promise_capability_t * njs_promise_new_capability(njs_vm_t *vm, njs_value_t *constructor) { njs_int_t ret; @@ -791,7 +766,7 @@ njs_promise_object_resolve(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } -static njs_promise_t * +njs_promise_t * njs_promise_resolve(njs_vm_t *vm, njs_value_t *constructor, njs_value_t *x) { njs_int_t ret; @@ -946,7 +921,7 @@ failed: } -static njs_int_t +njs_int_t njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value, njs_value_t *fulfilled, njs_value_t *rejected, njs_promise_capability_t *capability) diff --git a/src/njs_promise.h b/src/njs_promise.h index bb645f34..3d7a70dc 100644 --- a/src/njs_promise.h +++ b/src/njs_promise.h @@ -7,9 +7,37 @@ #define _NJS_PROMISE_H_INCLUDED_ -njs_int_t -njs_promise_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused); +typedef enum { + NJS_PROMISE_PENDING = 0, + NJS_PROMISE_FULFILL, + NJS_PROMISE_REJECTED +} njs_promise_type_t; + +typedef struct { + njs_value_t promise; + njs_value_t resolve; + njs_value_t reject; +} njs_promise_capability_t; + +typedef struct { + njs_promise_type_t state; + njs_value_t result; + njs_queue_t fulfill_queue; + njs_queue_t reject_queue; + njs_bool_t is_handled; +} njs_promise_data_t; + + +njs_int_t njs_promise_constructor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +njs_promise_capability_t *njs_promise_new_capability(njs_vm_t *vm, + njs_value_t *constructor); +njs_function_t *njs_promise_create_function(njs_vm_t *vm, size_t context_size); +njs_int_t njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value, + njs_value_t *fulfilled, njs_value_t *rejected, + njs_promise_capability_t *capability); +njs_promise_t *njs_promise_resolve(njs_vm_t *vm, njs_value_t *constructor, + njs_value_t *x); extern const njs_object_type_init_t njs_promise_type_init; diff --git a/src/njs_variable.c b/src/njs_variable.c index e48196f6..811886c7 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -38,6 +38,7 @@ njs_variable_t * njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type) { + njs_bool_t ctor; njs_value_t **declr; njs_variable_t *var; njs_parser_scope_t *root; @@ -61,7 +62,9 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, return NULL; } - lambda = njs_function_lambda_alloc(parser->vm, 1); + ctor = parser->node->token_type != NJS_TOKEN_ASYNC_FUNCTION_DECLARATION; + + lambda = njs_function_lambda_alloc(parser->vm, ctor); if (lambda == NULL) { return NULL; } diff --git a/src/njs_vm.c b/src/njs_vm.c index 4ccc5771..1f07d32a 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -465,7 +465,9 @@ njs_vm_start(njs_vm_t *vm) return ret; } - return njs_vmcode_interpreter(vm, vm->start); + ret = njs_vmcode_interpreter(vm, vm->start); + + return (ret == NJS_ERROR) ? NJS_ERROR : NJS_OK; } diff --git a/src/njs_vm.h b/src/njs_vm.h index 1539a285..2efa859d 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -43,6 +43,7 @@ typedef enum { NJS_OBJ_TYPE_SYMBOL, NJS_OBJ_TYPE_STRING, NJS_OBJ_TYPE_FUNCTION, + NJS_OBJ_TYPE_ASYNC_FUNCTION, NJS_OBJ_TYPE_REGEXP, NJS_OBJ_TYPE_DATE, NJS_OBJ_TYPE_PROMISE, @@ -224,6 +225,7 @@ struct njs_vm_shared_s { njs_lvlhsh_t array_instance_hash; njs_lvlhsh_t string_instance_hash; njs_lvlhsh_t function_instance_hash; + njs_lvlhsh_t async_function_instance_hash; njs_lvlhsh_t arrow_instance_hash; njs_lvlhsh_t arguments_object_instance_hash; njs_lvlhsh_t regexp_instance_hash; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index c24a599c..8c3d439f 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -42,6 +42,8 @@ static njs_jump_off_t njs_vmcode_debugger(njs_vm_t *vm); static njs_jump_off_t njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval); +static njs_jump_off_t njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await); + static njs_jump_off_t njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset, u_char *pc); static njs_jump_off_t njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *value, @@ -90,6 +92,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc) njs_value_t numeric1, numeric2, primitive1, primitive2; njs_frame_t *frame; njs_jump_off_t ret; + njs_vmcode_await_t *await; njs_native_frame_t *previous, *native; njs_property_next_t *next; njs_vmcode_finally_t *finally; @@ -821,6 +824,10 @@ next: break; + case NJS_VMCODE_AWAIT: + await = (njs_vmcode_await_t *) pc; + return njs_vmcode_await(vm, await); + case NJS_VMCODE_TRY_START: ret = njs_vmcode_try_start(vm, value1, value2, pc); if (njs_slow_path(ret == NJS_ERROR)) { @@ -1066,7 +1073,7 @@ njs_vmcode_function(njs_vm_t *vm, u_char *pc) code = (njs_vmcode_function_t *) pc; lambda = code->lambda; - function = njs_function_alloc(vm, lambda); + function = njs_function_alloc(vm, lambda, code->async); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } @@ -1807,6 +1814,95 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) } +static njs_jump_off_t +njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await) +{ + size_t size; + njs_int_t ret; + njs_frame_t *frame; + njs_value_t ctor, val, on_fulfilled, on_rejected, *value; + njs_promise_t *promise; + njs_function_t *fulfilled, *rejected; + njs_async_ctx_t *ctx; + njs_native_frame_t *active; + + active = &vm->active_frame->native; + ctx = active->function->context; + + value = njs_scope_valid_value(vm, await->retval); + if (njs_slow_path(value == NULL)) { + return NJS_ERROR; + } + + njs_set_function(&ctor, &vm->constructors[NJS_OBJ_TYPE_PROMISE]); + + promise = njs_promise_resolve(vm, &ctor, value); + if (njs_slow_path(promise == NULL)) { + return NJS_ERROR; + } + + if (ctx->await == NULL) { + size = njs_function_frame_size(active); + + fulfilled = njs_promise_create_function(vm, size); + if (njs_slow_path(fulfilled == NULL)) { + return NJS_ERROR; + } + + ctx->await = fulfilled->context; + + ret = njs_function_frame_save(vm, ctx->await, NULL); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + } else { + fulfilled = njs_promise_create_function(vm, 0); + if (njs_slow_path(fulfilled == NULL)) { + return NJS_ERROR; + } + } + + ctx->pc = (u_char *) await + sizeof(njs_vmcode_await_t); + ctx->index = await->retval; + + frame = (njs_frame_t *) active; + + if (frame->exception.catch != NULL) { + ctx->await->pc = frame->exception.catch; + + } else { + ctx->await->pc = ctx->pc; + } + + fulfilled->context = ctx; + fulfilled->args_count = 1; + fulfilled->u.native = njs_await_fulfilled; + + rejected = njs_promise_create_function(vm, 0); + if (njs_slow_path(rejected == NULL)) { + return NJS_ERROR; + } + + rejected->context = ctx; + rejected->args_count = 1; + rejected->u.native = njs_await_rejected; + + njs_set_object(&val, &promise->object); + njs_set_function(&on_fulfilled, fulfilled); + njs_set_function(&on_rejected, rejected); + + ret = njs_promise_perform_then(vm, &val, &on_fulfilled, &on_rejected, NULL); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + (void) njs_vmcode_return(vm, NULL, &vm->retval); + + return NJS_AGAIN; +} + + /* * njs_vmcode_try_start() is set on the start of a "try" block to create * a "try" block, to set a catch address to the start of a "catch" or diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 6a2fc26e..c15a4bd9 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -50,6 +50,8 @@ enum { NJS_VMCODE_ARGUMENTS, NJS_VMCODE_PROTO_INIT, + NJS_VMCODE_AWAIT, + NJS_VMCODE_TRY_START, NJS_VMCODE_THROW, NJS_VMCODE_TRY_BREAK, @@ -210,6 +212,7 @@ typedef struct { njs_vmcode_t code; njs_index_t retval; njs_function_lambda_t *lambda; + njs_bool_t async; } njs_vmcode_function_t; @@ -428,6 +431,12 @@ typedef struct { } njs_vmcode_debugger_t; +typedef struct { + njs_vmcode_t code; + njs_index_t retval; +} njs_vmcode_await_t; + + njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc); njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index facab421..9875ba88 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -20435,6 +20435,82 @@ static njs_unit_test_t njs_test[] = { njs_str("const const"), njs_str("SyntaxError: Unexpected token \"const\" in 1") }, + + /* Async/Await */ + + { njs_str("async function f() {}; f.prototype"), + njs_str("undefined") }, + + { njs_str("async function f() {await 1}"), + njs_str("undefined") }, + + { njs_str("function f() {await 1}"), + njs_str("SyntaxError: await is only valid in async functions in 1") }, + + { njs_str("async function f() {function a() {await 1}}"), + njs_str("SyntaxError: await is only valid in async functions in 1") }, + + { njs_str("async function f() {() => {await 1}}"), + njs_str("SyntaxError: await is only valid in async functions in 1") }, + + { njs_str("function f() {async () => {await 1}}"), + njs_str("undefined") }, + + { njs_str("let f = async () => {await 1}"), + njs_str("undefined") }, + + { njs_str("let f = () => {await 1}"), + njs_str("SyntaxError: await is only valid in async functions in 1") }, + + { njs_str("(async function() {await 1})"), + njs_str("[object AsyncFunction]") }, + + { njs_str("(function() {await 1})"), + njs_str("SyntaxError: await is only valid in async functions in 1") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "ctor"), + njs_str("[object Function]") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "ctor()"), + njs_str("[object AsyncFunction]") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "new ctor();"), + njs_str("[object AsyncFunction]") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "let f = new ctor(); f()"), + njs_str("[object Promise]") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "let f = new ctor('x', 'await 1; return x'); f(1)"), + njs_str("[object Promise]") }, + + { njs_str("let f = new Function('x', 'await 1; return x'); f(1)"), + njs_str("SyntaxError: await is only valid in async functions in runtime:1") }, + + { njs_str("new AsyncFunction()"), + njs_str("ReferenceError: \"AsyncFunction\" is not defined") }, + + { njs_str("(async function() {console.log(await 111)})"), + njs_str("SyntaxError: await in arguments not supported in 1") }, + + { njs_str("(async function() {console.log('Number: ' + await 111)})"), + njs_str("SyntaxError: await in arguments not supported in 1") }, + + { njs_str("function f(a) {}" + "(async function() {f(await 111)})"), + njs_str("SyntaxError: await in arguments not supported in 1") }, + + { njs_str("function f(a, b, c) {}" + "(async function() {f(1, 'a', await 111)})"), + njs_str("SyntaxError: await in arguments not supported in 1") }, + + { njs_str("function f(a) {}" + "(async function() {f('Number: ' + await 111)})"), + njs_str("SyntaxError: await in arguments not supported in 1") }, }; diff --git a/test/js/async_await_add.js b/test/js/async_await_add.js new file mode 100644 index 00000000..62d46e7e --- /dev/null +++ b/test/js/async_await_add.js @@ -0,0 +1,7 @@ +async function af(x) { + const y = await new Promise(resolve => {resolve(x + 10)}); + + return x + y; +} + +af(50).then(v => console.log(v)); diff --git a/test/js/async_await_blank.js b/test/js/async_await_blank.js new file mode 100644 index 00000000..deaa8cfd --- /dev/null +++ b/test/js/async_await_blank.js @@ -0,0 +1,5 @@ +async function af(x) { + return x; +} + +af(12345).then(v => console.log(v)); diff --git a/test/js/async_await_catch.js b/test/js/async_await_catch.js new file mode 100644 index 00000000..c47a512b --- /dev/null +++ b/test/js/async_await_catch.js @@ -0,0 +1,5 @@ +async function add(x) { + return await new Promise((resolve, reject) => {reject(x)}).catch(v => v + 1); +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_finally.js b/test/js/async_await_finally.js new file mode 100644 index 00000000..5c2ae73c --- /dev/null +++ b/test/js/async_await_finally.js @@ -0,0 +1,6 @@ +async function add(x) { + return await new Promise((resolve, reject) => {reject(x)}) + .finally(() => console.log(x + 1)); +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_for.js b/test/js/async_await_for.js new file mode 100644 index 00000000..1256a068 --- /dev/null +++ b/test/js/async_await_for.js @@ -0,0 +1,23 @@ +let stage = []; + +async function f() { + let sum = 0; + + stage.push(2); + + for (let x = 4; x < 14; x++) { + sum += await new Promise((resolve, reject) => {resolve(x)}); + + stage.push(x); + } + + stage.push("end"); + + return sum; +} + +stage.push(1); + +f().then(v => {console.log(v, stage.join(", "))}) + +stage.push(3); diff --git a/test/js/async_await_inline.js b/test/js/async_await_inline.js new file mode 100644 index 00000000..2901738b --- /dev/null +++ b/test/js/async_await_inline.js @@ -0,0 +1,11 @@ +function pr(x) { + return new Promise(resolve => {resolve(x)}); +} + +async function add(x) { + const a = pr(20); + const b = pr(50); + return await a + await b; +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_reject.js b/test/js/async_await_reject.js new file mode 100644 index 00000000..eeaec34d --- /dev/null +++ b/test/js/async_await_reject.js @@ -0,0 +1,5 @@ +async function add(x) { + return await new Promise((resolve, reject) => {reject(x)}); +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_stages.js b/test/js/async_await_stages.js new file mode 100644 index 00000000..e3ff18e4 --- /dev/null +++ b/test/js/async_await_stages.js @@ -0,0 +1,28 @@ +function pr(x) { + return new Promise(resolve => {resolve(x)}) + .then(v => v).then(v => v); +} + +let stage = []; + +async function f() { + let sum = 0; + + stage.push(2); + + const a1 = await pr(10); + + stage.push(4); + + const a2 = await pr(20); + + stage.push(5); + + return a1 + a2; +} + +stage.push(1); + +f().then(v => {console.log(v, stage.join(", "))}) + +stage.push(3); diff --git a/test/js/async_await_throw.js b/test/js/async_await_throw.js new file mode 100644 index 00000000..8923e5d4 --- /dev/null +++ b/test/js/async_await_throw.js @@ -0,0 +1,12 @@ +function pr(x) { + return new Promise(resolve => {resolve(x)}).then(v => {throw v}); +} + +async function add(x) { + const a = await pr(x); + const b = await pr(x); + + return a + b; +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_throw_async.js b/test/js/async_await_throw_async.js new file mode 100644 index 00000000..b5b6bb27 --- /dev/null +++ b/test/js/async_await_throw_async.js @@ -0,0 +1,15 @@ +function pr(x) { + return new Promise(resolve => {resolve(x)}); +} + +async function add(x) { + const a = await pr(x); + + throw a + 1; + + const b = await pr(x + 10); + + return a + b; +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_throw_catch.js b/test/js/async_await_throw_catch.js new file mode 100644 index 00000000..5e9025f3 --- /dev/null +++ b/test/js/async_await_throw_catch.js @@ -0,0 +1,12 @@ +function pr(x) { + return new Promise(resolve => {resolve(x)}).then(v => {throw v}).catch(v => v); +} + +async function add(x) { + const a = await pr(x); + const b = await pr(x + 10); + + return a + b; +} + +add(50).then(v => {console.log(v)}); diff --git a/test/js/async_await_throw_catch_async.js b/test/js/async_await_throw_catch_async.js new file mode 100644 index 00000000..e7c51031 --- /dev/null +++ b/test/js/async_await_throw_catch_async.js @@ -0,0 +1,15 @@ +function pr(x) { + return new Promise(resolve => {resolve(x)}); +} + +async function add(x) { + const a = await pr(x); + + throw a + 1; + + const b = await pr(x + 10); + + return a + b; +} + +add(50).then(v => {console.log(v - 1)}).catch(v => console.log(v)); diff --git a/test/js/async_await_try_catch.js b/test/js/async_await_try_catch.js new file mode 100644 index 00000000..5c11ec51 --- /dev/null +++ b/test/js/async_await_try_catch.js @@ -0,0 +1,19 @@ +async function af() { + try { + await new Promise(function(resolve, reject) { + reject("reject"); + }); + + console.log("shouldn't happen"); + } + catch (v) { + console.log(v); + } + finally { + console.log("finally"); + } + + return "end"; +}; + +af().then(v => console.log(v)); diff --git a/test/js/async_await_try_finally.js b/test/js/async_await_try_finally.js new file mode 100644 index 00000000..9feb1b5f --- /dev/null +++ b/test/js/async_await_try_finally.js @@ -0,0 +1,20 @@ +async function af() { + try { + await new Promise(function(resolve, reject) { + reject("reject"); + }); + + console.log("shouldn't happen: try"); + } + finally { + await new Promise(function(resolve, reject) { + reject("finally reject"); + }); + + console.log("shouldn't happen: finally"); + } + + return "shouldn't happen: end"; +}; + +af().then(v => console.log(v)); diff --git a/test/js/async_await_try_throw.js b/test/js/async_await_try_throw.js new file mode 100644 index 00000000..d5f51e53 --- /dev/null +++ b/test/js/async_await_try_throw.js @@ -0,0 +1,14 @@ +async function af() { + try { + throw "try"; + + console.log("shouldn't happen: try"); + } + finally { + console.log("finally"); + } + + return "shouldn't happen: end"; +}; + +af().then(v => console.log(v)); diff --git a/test/js/async_await_try_throw_catch.js b/test/js/async_await_try_throw_catch.js new file mode 100644 index 00000000..d4bd5b3e --- /dev/null +++ b/test/js/async_await_try_throw_catch.js @@ -0,0 +1,17 @@ +async function af() { + try { + throw "try"; + + console.log("shouldn't happen: try"); + } + catch (v) { + console.log(v); + } + finally { + console.log("finally"); + } + + return "end"; +}; + +af().then(v => console.log(v)); diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp index 5c5ed2da..d505098a 100644 --- a/test/njs_expect_test.exp +++ b/test/njs_expect_test.exp @@ -1108,3 +1108,66 @@ njs_run {"./test/js/promise_race.js"} \ njs_run {"./test/js/promise_race_throw.js"} \ "rejected:one" + +# Async/Await + +njs_run {"./test/js/async_await_inline.js"} \ +"70" + +njs_run {"./test/js/async_await_add.js"} \ +"110" + +njs_run {"./test/js/async_await_stages.js"} \ +"30 1, 2, 3, 4, 5" + +njs_run {"./test/js/async_await_for.js"} \ +"85 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, end" + +njs_run {"./test/js/async_await_blank.js"} \ +"12345" + +njs_run {"./test/js/async_await_reject.js"} \ +"Thrown: +Error: unhandled promise rejection: 50" + +njs_run {"./test/js/async_await_catch.js"} \ +"51" + +njs_run {"./test/js/async_await_finally.js"} \ +"51 +Thrown: +Error: unhandled promise rejection: 50" + +njs_run {"./test/js/async_await_throw.js"} \ +"Thrown: +Error: unhandled promise rejection: 50" + +njs_run {"./test/js/async_await_throw_catch.js"} \ +"110" + +njs_run {"./test/js/async_await_throw_async.js"} \ +"Thrown: +Error: unhandled promise rejection: 51" + +njs_run {"./test/js/async_await_throw_catch_async.js"} \ +"51" + +njs_run {"./test/js/async_await_try_catch.js"} \ +"reject +finally +end" + +njs_run {"./test/js/async_await_try_finally.js"} \ +"Thrown: +Error: unhandled promise rejection: finally reject" + +njs_run {"./test/js/async_await_try_throw.js"} \ +"finally +Thrown: +Error: unhandled promise rejection: try" + +njs_run {"./test/js/async_await_try_throw_catch.js"} \ +"try +finally +end" + -- 2.47.3