From bf208048283f56435daa8abc2357fdd42976ba18 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Wed, 25 Dec 2019 15:59:01 +0300 Subject: [PATCH] Introduced TypedArray objects. Added the global constructors: Uint8Array, Int8Array, Uint8ClampedArray, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array. Implemented: - TypedArray(), - TypedArray(length), - TypedArray(buffer, [start, [end]]), - TypedArray(object), - TypedArray(typedarray), - TypedArray.BYTES_PER_ELEMENT. - %TypedArray%.prototype.constructor - get %TypedArray%.prototype.buffer - get %TypedArray%.prototype.byteLength - get %TypedArray%.prototype.byteOffset - get %TypedArray%.prototype.length - %TypedArray%.prototype[@@toStringTag] - %TypedArray%.prototype.set(array [, start]) - %TypedArray%.prototype.slice([start, [end]]) - %TypedArray%.prototype.toString() - %TypedArray%.prototype.join(separator) - %TypedArray%.prototype.fill(). In collaboration with Tiago Natel de Moura. This closes #264 issue on Github. --- auto/sources | 1 + src/njs.h | 2 +- src/njs_array.c | 2 +- src/njs_array.h | 2 + src/njs_array_buffer.c | 21 +- src/njs_builtin.c | 139 ++- src/njs_json.c | 84 +- src/njs_main.h | 1 + src/njs_number.c | 15 +- src/njs_number.h | 13 +- src/njs_object.c | 65 +- src/njs_object_hash.h | 139 +++ src/njs_object_prop.c | 30 + src/njs_string.c | 73 +- src/njs_typed_array.c | 1751 ++++++++++++++++++++++++++++++++++++ src/njs_typed_array.h | 163 ++++ src/njs_value.c | 94 +- src/njs_value.h | 78 +- src/njs_value_conversion.h | 4 +- src/njs_vm.h | 24 +- src/njs_vmcode.c | 10 +- src/test/njs_benchmark.c | 20 + src/test/njs_unit_test.c | 605 ++++++++++++- 23 files changed, 3234 insertions(+), 102 deletions(-) create mode 100644 src/njs_typed_array.c create mode 100644 src/njs_typed_array.h diff --git a/auto/sources b/auto/sources index d7b39ece..dbad78c6 100644 --- a/auto/sources +++ b/auto/sources @@ -53,6 +53,7 @@ NJS_LIB_SRCS=" \ src/njs_generator.c \ src/njs_disassembler.c \ src/njs_array_buffer.c \ + src/njs_typed_array.c \ src/njs_promise.c \ " diff --git a/src/njs.h b/src/njs.h index 9421af0c..0012325d 100644 --- a/src/njs.h +++ b/src/njs.h @@ -274,7 +274,7 @@ NJS_EXPORT njs_int_t njs_vm_value_string(njs_vm_t *vm, njs_str_t *dst, NJS_EXPORT njs_int_t njs_vm_retval_string(njs_vm_t *vm, njs_str_t *dst); NJS_EXPORT njs_int_t njs_vm_value_dump(njs_vm_t *vm, njs_str_t *dst, - const njs_value_t *value, njs_uint_t console, njs_uint_t indent); + njs_value_t *value, njs_uint_t console, njs_uint_t indent); NJS_EXPORT njs_int_t njs_vm_retval_dump(njs_vm_t *vm, njs_str_t *dst, njs_uint_t indent); diff --git a/src/njs_array.c b/src/njs_array.c index e6016e8c..75e94a52 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -1089,7 +1089,7 @@ njs_array_prototype_reverse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } -static njs_int_t +njs_int_t njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { diff --git a/src/njs_array.h b/src/njs_array.h index 1fe18472..92f5f5b4 100644 --- a/src/njs_array.h +++ b/src/njs_array.h @@ -21,6 +21,8 @@ njs_int_t njs_array_string_add(njs_vm_t *vm, njs_array_t *array, const u_char *start, size_t size, size_t length); njs_int_t njs_array_expand(njs_vm_t *vm, njs_array_t *array, uint32_t prepend, uint32_t append); +njs_int_t njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); extern const njs_object_init_t njs_array_instance_init; diff --git a/src/njs_array_buffer.c b/src/njs_array_buffer.c index 6b200f31..c04bc6f9 100644 --- a/src/njs_array_buffer.c +++ b/src/njs_array_buffer.c @@ -59,7 +59,7 @@ static njs_int_t njs_array_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { - uint32_t size; + uint64_t size; njs_int_t ret; njs_value_t *value; njs_array_buffer_t *array; @@ -98,6 +98,16 @@ njs_array_buffer_get_this(njs_vm_t *vm, njs_value_t *args, } +static njs_int_t +njs_array_buffer_is_view(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_set_boolean(&vm->retval, njs_is_typed_array(njs_arg(args, nargs, 1))); + + return NJS_OK; +} + + static const njs_object_prop_t njs_array_buffer_constructor_properties[] = { /* ArrayBuffer.name == "ArrayBuffer". */ @@ -134,6 +144,15 @@ static const njs_object_prop_t njs_array_buffer_constructor_properties[] = .configurable = 1, .enumerable = 0, }, + + /* ArrayBuffer.isView(new Uint8Array()) === true */ + { + .type = NJS_PROPERTY, + .name = njs_string("isView"), + .value = njs_native_function(njs_array_buffer_is_view, 1), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 4825a581..e544dc16 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -58,7 +58,6 @@ static const njs_object_type_init_t *const &njs_obj_type_init, &njs_array_type_init, - &njs_array_buffer_type_init, &njs_boolean_type_init, &njs_number_type_init, &njs_symbol_type_init, @@ -67,14 +66,27 @@ static const njs_object_type_init_t *const &njs_regexp_type_init, &njs_date_type_init, &njs_promise_type_init, + &njs_array_buffer_type_init, /* Hidden types. */ &njs_hash_type_init, &njs_hmac_type_init, + &njs_typed_array_type_init, - /* Error types. */ + /* TypedArray types. */ + &njs_typed_array_u8_type_init, + &njs_typed_array_u8clamped_type_init, + &njs_typed_array_i8_type_init, + &njs_typed_array_u16_type_init, + &njs_typed_array_i16_type_init, + &njs_typed_array_u32_type_init, + &njs_typed_array_i32_type_init, + &njs_typed_array_f32_type_init, + &njs_typed_array_f64_type_init, + + /* Error types. */ &njs_error_type_init, &njs_eval_error_type_init, &njs_internal_error_type_init, @@ -303,8 +315,9 @@ 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, *error_prototype, - *error_constructor; + njs_object_t *object_prototype, *function_prototype, + *typed_array_prototype, *error_prototype, + *typed_array_ctor, *error_ctor; /* * Copy both prototypes and constructors arrays by one memcpy() @@ -317,11 +330,21 @@ njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global) object_prototype = &vm->prototypes[NJS_OBJ_TYPE_OBJECT].object; - for (i = NJS_OBJ_TYPE_ARRAY; i < NJS_OBJ_TYPE_EVAL_ERROR; i++) { + for (i = NJS_OBJ_TYPE_ARRAY; i < NJS_OBJ_TYPE_NORMAL_MAX; i++) { vm->prototypes[i].object.__proto__ = object_prototype; } + typed_array_prototype = &vm->prototypes[NJS_OBJ_TYPE_TYPED_ARRAY].object; + + for (i = NJS_OBJ_TYPE_TYPED_ARRAY_MIN; + i < NJS_OBJ_TYPE_TYPED_ARRAY_MAX; + i++) + { + vm->prototypes[i].object.__proto__ = typed_array_prototype; + } + error_prototype = &vm->prototypes[NJS_OBJ_TYPE_ERROR].object; + error_prototype->__proto__ = object_prototype; for (i = NJS_OBJ_TYPE_EVAL_ERROR; i < NJS_OBJ_TYPE_MAX; i++) { vm->prototypes[i].object.__proto__ = error_prototype; @@ -329,14 +352,24 @@ njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global) function_prototype = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; - for (i = NJS_OBJ_TYPE_OBJECT; i < NJS_OBJ_TYPE_EVAL_ERROR; i++) { + for (i = NJS_OBJ_TYPE_OBJECT; i < NJS_OBJ_TYPE_NORMAL_MAX; i++) { vm->constructors[i].object.__proto__ = function_prototype; } - error_constructor = &vm->constructors[NJS_OBJ_TYPE_ERROR].object; + typed_array_ctor = &vm->constructors[NJS_OBJ_TYPE_TYPED_ARRAY].object; + + for (i = NJS_OBJ_TYPE_TYPED_ARRAY_MIN; + i < NJS_OBJ_TYPE_TYPED_ARRAY_MAX; + i++) + { + vm->constructors[i].object.__proto__ = typed_array_ctor; + } + + error_ctor = &vm->constructors[NJS_OBJ_TYPE_ERROR].object; + error_ctor->__proto__ = function_prototype; for (i = NJS_OBJ_TYPE_EVAL_ERROR; i < NJS_OBJ_TYPE_MAX; i++) { - vm->constructors[i].object.__proto__ = error_constructor; + vm->constructors[i].object.__proto__ = error_ctor; } vm->global_object.__proto__ = object_prototype; @@ -1109,6 +1142,96 @@ static const njs_object_prop_t njs_global_this_object_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Uint8Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_UINT8_ARRAY, + NJS_UINT8ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Uint16Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_UINT16_ARRAY, + NJS_UINT16ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Uint32Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_UINT32_ARRAY, + NJS_UINT32ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Int8Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_INT8_ARRAY, + NJS_INT8ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Int16Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_INT16_ARRAY, + NJS_INT16ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Int32Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_INT32_ARRAY, + NJS_INT32ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Float32Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_FLOAT32_ARRAY, + NJS_FLOAT32ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Float64Array"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_FLOAT64_ARRAY, + NJS_FLOAT64ARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_long_string("Uint8ClampedArray"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY, + NJS_UINT8CLAMPEDARRAY_HASH), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY_HANDLER, .name = njs_string("Boolean"), diff --git a/src/njs_json.c b/src/njs_json.c index f8e10eeb..595a2b90 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -1087,12 +1087,15 @@ njs_json_pop_stringify_state(njs_json_stringify_t *stringify) } -#define njs_json_is_object(value) \ - (((value)->type == NJS_OBJECT) \ - || ((value)->type == NJS_OBJECT_SYMBOL) \ - || ((value)->type == NJS_EXTERNAL) \ - || ((value)->type == NJS_ARRAY) \ - || ((value)->type >= NJS_REGEXP)) +njs_inline njs_bool_t +njs_json_is_object(const njs_value_t *value) +{ + return (((value)->type == NJS_OBJECT) + || ((value)->type == NJS_ARRAY) + || ((value)->type == NJS_OBJECT_SYMBOL) + || ((value)->type == NJS_EXTERNAL) + || ((value)->type >= NJS_REGEXP)); +} #define njs_json_stringify_indent(times) \ @@ -1717,12 +1720,14 @@ const njs_object_init_t njs_json_object_init = { static njs_int_t -njs_dump_value(njs_json_stringify_t *stringify, njs_chb_t *chain, - const njs_value_t *value, njs_uint_t console) +njs_dump_terminal(njs_json_stringify_t *stringify, njs_chb_t *chain, + njs_value_t *value, njs_uint_t console) { - njs_str_t str; - njs_int_t ret; - njs_value_t str_val; + njs_str_t str; + njs_int_t ret; + njs_value_t str_val, tag; + njs_typed_array_t *array; + njs_string_prop_t string; njs_int_t (*to_string)(njs_vm_t *, njs_value_t *, const njs_value_t *); @@ -1836,6 +1841,27 @@ njs_dump_value(njs_json_stringify_t *stringify, njs_chb_t *chain, break; + case NJS_TYPED_ARRAY: + array = njs_typed_array(value); + ret = njs_object_string_tag(stringify->vm, value, &tag); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (ret == NJS_OK) { + (void) njs_string_prop(&string, &tag); + njs_chb_append(chain, string.start, string.size); + njs_chb_append_literal(chain, " "); + } + + njs_chb_append_literal(chain, "["); + + (void) njs_typed_array_to_chain(stringify->vm, chain, array, NULL); + + njs_chb_append_literal(chain, "]"); + + break; + case NJS_NUMBER: if (njs_slow_path(njs_number(value) == 0.0 && signbit(njs_number(value)))) @@ -1899,13 +1925,11 @@ njs_dump_is_external_object(const njs_value_t *value) njs_inline njs_bool_t -njs_dump_is_object(const njs_value_t *value) +njs_dump_is_recursive(const njs_value_t *value) { return (value->type == NJS_OBJECT && !njs_object(value)->error_data) || (value->type == NJS_ARRAY) - || (value->type == NJS_ARRAY_BUFFER) - || (value->type == NJS_PROMISE) - || (value->type == NJS_OBJECT_VALUE) + || (value->type >= NJS_OBJECT_SPECIAL_MAX) || njs_dump_is_external_object(value); } @@ -1916,7 +1940,7 @@ static const njs_value_t string_get_set = njs_long_string("[Getter/Setter]"); njs_int_t -njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, +njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, njs_uint_t console, njs_uint_t indent) { njs_int_t i; @@ -1934,13 +1958,11 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, stringify->vm = vm; stringify->depth = 0; - njs_set_undefined(&stringify->replacer); - stringify->keys_type = NJS_ENUM_STRING | NJS_ENUM_SYMBOL; njs_chb_init(&chain, vm->mem_pool); - if (!njs_dump_is_object(value)) { - ret = njs_dump_value(stringify, &chain, value, console); + if (!njs_dump_is_recursive(value)) { + ret = njs_dump_terminal(stringify, &chain, value, console); if (njs_slow_path(ret != NJS_OK)) { goto memory_error; } @@ -1948,6 +1970,8 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, goto done; } + njs_set_undefined(&stringify->replacer); + stringify->keys_type = NJS_ENUM_STRING | NJS_ENUM_SYMBOL; indent = njs_min(indent, 10); stringify->space.length = indent; stringify->space.start = stringify->space_buf; @@ -2028,6 +2052,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, if (njs_is_defined(&prop->getter)) { if (njs_is_defined(&prop->setter)) { val = njs_value_arg(&string_get_set); + } else { val = njs_value_arg(&string_get); } @@ -2050,7 +2075,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, njs_chb_append_literal(&chain, " "); } - if (njs_dump_is_object(val)) { + if (njs_dump_is_recursive(val)) { state = njs_json_push_stringify_state(vm, stringify, val); if (njs_slow_path(state == NULL)) { goto exception; @@ -2059,7 +2084,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, break; } - ret = njs_dump_value(stringify, &chain, val, console); + ret = njs_dump_terminal(stringify, &chain, val, console); if (njs_slow_path(ret != NJS_OK)) { if (ret == NJS_DECLINED) { goto exception; @@ -2072,6 +2097,17 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, case NJS_JSON_ARRAY: if (state->index == 0) { + ret = njs_object_string_tag(vm, &state->value, &tag); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (ret == NJS_OK) { + (void) njs_string_prop(&string, &tag); + njs_chb_append(&chain, string.start, string.size); + njs_chb_append_literal(&chain, " "); + } + njs_chb_append_literal(&chain, "["); njs_json_stringify_indent(stringify->depth + 1); } @@ -2095,7 +2131,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, val = &njs_array_start(&state->value)[state->index++]; - if (njs_dump_is_object(val)) { + if (njs_dump_is_recursive(val)) { state = njs_json_push_stringify_state(vm, stringify, val); if (njs_slow_path(state == NULL)) { goto exception; @@ -2106,7 +2142,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, state->written = 1; - ret = njs_dump_value(stringify, &chain, val, console); + ret = njs_dump_terminal(stringify, &chain, val, console); if (njs_slow_path(ret != NJS_OK)) { if (ret == NJS_DECLINED) { goto exception; diff --git a/src/njs_main.h b/src/njs_main.h index 6bc39614..51e8229d 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include diff --git a/src/njs_number.c b/src/njs_number.c index 8b4476c1..3d0f0022 100644 --- a/src/njs_number.c +++ b/src/njs_number.c @@ -20,19 +20,16 @@ static njs_int_t njs_number_to_string_radix(njs_vm_t *vm, njs_value_t *string, double number, uint32_t radix); -uint32_t +double njs_key_to_index(const njs_value_t *value) { - double num; njs_array_t *array; - num = NAN; - if (njs_fast_path(njs_is_numeric(value))) { - num = njs_number(value); + return njs_number(value); } else if (njs_is_string(value)) { - num = njs_string_to_index(value); + return njs_string_to_index(value); } else if (njs_is_array(value)) { @@ -52,11 +49,7 @@ njs_key_to_index(const njs_value_t *value) } } - if ((uint32_t) num == num) { - return (uint32_t) num; - } - - return NJS_ARRAY_INVALID_INDEX; + return NAN; } diff --git a/src/njs_number.h b/src/njs_number.h index f73bf7c0..b8348144 100644 --- a/src/njs_number.h +++ b/src/njs_number.h @@ -8,7 +8,7 @@ #define _NJS_NUMBER_H_INCLUDED_ -uint32_t njs_key_to_index(const njs_value_t *value); +double njs_key_to_index(const njs_value_t *value); double njs_number_dec_parse(const u_char **start, const u_char *end); uint64_t njs_number_oct_parse(const u_char **start, const u_char *end); uint64_t njs_number_bin_parse(const u_char **start, const u_char *end); @@ -29,6 +29,17 @@ njs_int_t njs_number_parse_float(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); +njs_inline njs_bool_t +njs_number_is_integer_index(double num, const njs_value_t *value) +{ + uint32_t u32; + + u32 = num; + + return (u32 == num && u32 != 0xffffffff) + && !(njs_is_string(value) && num == 0 && signbit(num)); +} + njs_inline int64_t njs_number_to_int64(double num) { diff --git a/src/njs_object.c b/src/njs_object.c index 41fa88f8..65a57e8e 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -20,6 +20,8 @@ static uint32_t njs_object_own_enumerate_object_length( njs_object_enum_type_t type, njs_bool_t all); static njs_int_t njs_object_enumerate_array(njs_vm_t *vm, const njs_array_t *array, njs_array_t *items, njs_object_enum_t kind); +static njs_int_t njs_object_enumerate_typed_array(njs_vm_t *vm, + const njs_typed_array_t *array, njs_array_t *items, njs_object_enum_t kind); static njs_int_t njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, njs_array_t *items, njs_object_enum_t kind); static njs_int_t njs_object_enumerate_object(njs_vm_t *vm, @@ -462,6 +464,10 @@ njs_object_own_enumerate_length(const njs_object_t *object, length += njs_object_enumerate_array_length(object); break; + case NJS_TYPED_ARRAY: + length += njs_typed_array_length((njs_typed_array_t *) object); + break; + case NJS_OBJECT_STRING: length += njs_object_enumerate_string_length(object); break; @@ -531,6 +537,12 @@ njs_object_own_enumerate_value(njs_vm_t *vm, const njs_object_t *object, kind); break; + case NJS_TYPED_ARRAY: + ret = njs_object_enumerate_typed_array(vm, + (njs_typed_array_t *) object, + items, kind); + break; + case NJS_OBJECT_STRING: obj_val = (njs_object_value_t *) object; @@ -797,6 +809,54 @@ njs_object_enumerate_array(njs_vm_t *vm, const njs_array_t *array, } +static njs_int_t +njs_object_enumerate_typed_array(njs_vm_t *vm, const njs_typed_array_t *array, + njs_array_t *items, njs_object_enum_t kind) +{ + uint32_t i, length; + njs_value_t *item; + njs_array_t *entry; + + item = items->start; + length = njs_typed_array_length(array); + + switch (kind) { + case NJS_ENUM_KEYS: + for (i = 0; i < length; i++) { + njs_uint32_to_string(item++, i); + } + + break; + + case NJS_ENUM_VALUES: + for (i = 0; i < length; i++) { + njs_set_number(item++, njs_typed_array_get(array, i)); + } + + break; + + case NJS_ENUM_BOTH: + for (i = 0; i < length; i++) { + entry = njs_array_alloc(vm, 2, 0); + if (njs_slow_path(entry == NULL)) { + return NJS_ERROR; + } + + njs_uint32_to_string(&entry->start[0], i); + njs_set_number(&entry->start[1], njs_typed_array_get(array, i)); + + njs_set_array(item++, entry); + } + + break; + } + + items->start = item; + + return NJS_OK; +} + + static njs_int_t njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, njs_array_t *items, njs_object_enum_t kind) @@ -2356,8 +2416,6 @@ static const njs_value_t njs_object_object_string = njs_long_string("[object Object]"); static const njs_value_t njs_object_array_string = njs_string("[object Array]"); -static const njs_value_t njs_object_array_buffer_string = - njs_long_string("[object ArrayBuffer]"); static const njs_value_t njs_object_function_string = njs_long_string("[object Function]"); static const njs_value_t njs_object_regexp_string = @@ -2411,7 +2469,8 @@ njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, &njs_object_date_string, &njs_object_object_string, &njs_object_object_string, - &njs_object_array_buffer_string, + &njs_object_object_string, + &njs_object_object_string, }; value = njs_argument(args, 0); diff --git a/src/njs_object_hash.h b/src/njs_object_hash.h index 1d845dfa..2392bed2 100644 --- a/src/njs_object_hash.h +++ b/src/njs_object_hash.h @@ -587,4 +587,143 @@ 'A'), 'r'), 'r'), 'a'), 'y'), 'B'), 'u'), 'f'), 'f'), 'e'), 'r') +#define NJS_UINT8ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'U'), 'i'), 'n'), 't'), '8'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_UINT16ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'U'), 'i'), 'n'), 't'), '1'), '6'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_UINT32ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'U'), 'i'), 'n'), 't'), '3'), '2'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_INT8ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'I'), 'n'), 't'), '8'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_INT16ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'I'), 'n'), 't'), '1'), '6'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_INT32ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'I'), 'n'), 't'), '3'), '2'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_FLOAT32ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'F'), 'l'), 'o'), 'a'), 't'), '3'), '2'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_FLOAT64ARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'F'), 'l'), 'o'), 'a'), 't'), '6'), '4'), 'A'), 'r'), 'r'), 'a'), 'y') + + +#define NJS_UINT8CLAMPEDARRAY_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'U'), 'i'), 'n'), 't'), '8'), 'C'), 'l'), 'a'), 'm'), 'p'), 'e'), \ + 'd'), 'A'), 'r'), 'r'), 'a'), 'y') + + #endif /* _NJS_OBJECT_HASH_H_INCLUDED_ */ diff --git a/src/njs_object_prop.c b/src/njs_object_prop.c index 0a8aa960..c486ece1 100644 --- a/src/njs_object_prop.c +++ b/src/njs_object_prop.c @@ -164,6 +164,16 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, return NJS_ERROR; } + if (njs_slow_path(njs_is_typed_array(object) + && njs_is_string(name))) + { + /* Integer-Indexed Exotic Objects [[DefineOwnProperty]]. */ + if (!isnan(njs_string_to_index(name))) { + njs_type_error(vm, "Invalid typed array index"); + return NJS_ERROR; + } + } + /* 6.2.5.6 CompletePropertyDescriptor */ if (njs_is_accessor_descriptor(prop)) { @@ -234,6 +244,26 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, return NJS_OK; + case NJS_PROPERTY_TYPED_ARRAY_REF: + if (njs_is_accessor_descriptor(prop)) { + goto exception; + } + + if (prop->configurable == NJS_ATTRIBUTE_TRUE || + prop->enumerable == NJS_ATTRIBUTE_FALSE || + prop->writable == NJS_ATTRIBUTE_FALSE) + { + goto exception; + } + + if (njs_is_valid(&prop->value)) { + return njs_typed_array_set_value(vm, njs_typed_array(&prev->value), + prev->value.data.magic32, + &prop->value); + } + + return NJS_OK; + default: njs_internal_error(vm, "unexpected property type \"%s\" " "while defining property", diff --git a/src/njs_string.c b/src/njs_string.c index 19eeda33..f234f882 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -4342,32 +4342,85 @@ njs_string_to_number(const njs_value_t *value, njs_bool_t parse_float) double njs_string_to_index(const njs_value_t *value) { + size_t size, len; double num; - size_t size; - const u_char *p, *end; + njs_bool_t minus; + const u_char *p, *start, *end; + u_char buf[128]; size = value->short_string.size; if (size != NJS_STRING_LONG) { - p = value->short_string.start; + start = value->short_string.start; } else { size = value->long_string.size; - p = value->long_string.data->start; + start = value->long_string.data->start; } - if (size == 0) { - return NAN; + p = start; + end = p + size; + minus = 0; + + if (size > 1) { + switch (p[0]) { + case '0': + if (size != 1) { + return NAN; + } + + /* Fall through. */ + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + + case '-': + if (size == 2 && p[1] == '0') { + return -0.0; + } + + if (size == njs_length("-Infinity") + && memcmp(&p[1], "Infinity", njs_length("Infinity")) == 0) + { + return -INFINITY; + } + + p++; + minus = 1; + + break; + + case 'I': + if (size == njs_length("Infinity") + && memcmp(p, "Infinity", njs_length("Infinity")) == 0) + { + return INFINITY; + } + + /* Fall through. */ + + default: + return NAN; + } } - if (*p == '0' && size > 1) { + num = njs_strtod(&p, end); + if (p != end) { return NAN; } - end = p + size; - num = njs_number_dec_parse(&p, end); + num = minus ? -num : num; - if (p != end) { + len = njs_dtoa(num, (char *) buf); + if (size != len || memcmp(start, buf, size) != 0) { return NAN; } diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c new file mode 100644 index 00000000..6c35ea11 --- /dev/null +++ b/src/njs_typed_array.c @@ -0,0 +1,1751 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + + +#include + + +static njs_int_t +njs_typed_array_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t magic) +{ + double num; + uint32_t i, length, element_size; + uint64_t size, offset; + njs_int_t ret; + njs_value_t *value, index, prop; + njs_array_t *src_array; + njs_object_type_t type; + njs_typed_array_t *array, *src_tarray; + njs_array_buffer_t *buffer; + + size = 0; + offset = 0; + + buffer = NULL; + src_array = NULL; + src_tarray = NULL; + + type = magic; + element_size = njs_typed_array_element_size(type); + + if (!vm->top_frame->ctor) { + njs_type_error(vm, "Constructor of TypedArray requires 'new'"); + return NJS_ERROR; + } + + value = njs_arg(args, nargs, 1); + + if (njs_is_array_buffer(value)) { + buffer = njs_array_buffer(value); + + ret = njs_value_to_index(vm, njs_arg(args, nargs, 2), &offset); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + if (njs_slow_path((offset % element_size) != 0)) { + njs_range_error(vm, "start offset must be multiple of %uD", + element_size); + return NJS_ERROR; + } + + if (!njs_is_undefined(njs_arg(args, nargs, 3))) { + ret = njs_value_to_index(vm, njs_argument(args, 3), &size); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + size *= element_size; + + if (njs_slow_path((offset + size) > buffer->size)) { + njs_range_error(vm, "Invalid typed array length: %uD", size); + return NJS_ERROR; + } + + } else { + if (njs_slow_path((buffer->size % element_size) != 0)) { + njs_range_error(vm, "byteLength of buffer must be " + "multiple of %uD", element_size); + return NJS_ERROR; + } + + if (offset > buffer->size) { + njs_range_error(vm, "byteOffset %uL is outside the bound of " + "the buffer", offset); + return NJS_ERROR; + } + + size = buffer->size - offset; + } + + } else if (njs_is_typed_array(value)) { + src_tarray = njs_typed_array(value); + size = njs_typed_array_length(src_tarray) * element_size; + + } else if (njs_is_object(value)) { + if (njs_is_array(value) && njs_object_hash_is_empty(value)) { + src_array = njs_array(value); + length = src_array->length; + + } else { + ret = njs_object_length(vm, value, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + size = (uint64_t) length * element_size; + + } else { + ret = njs_value_to_index(vm, value, &size); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + size *= element_size; + } + + if (buffer == NULL) { + buffer = njs_array_buffer_alloc(vm, size); + if (njs_slow_path(buffer == NULL)) { + return NJS_ERROR; + } + } + + array = njs_mp_zalloc(vm->mem_pool, sizeof(njs_typed_array_t)); + if (njs_slow_path(array == NULL)) { + goto memory_error; + } + + array->buffer = buffer; + array->offset = offset / element_size; + array->byte_length = size; + array->type = type; + + if (src_tarray != NULL) { + if (type != src_tarray->type) { + length = njs_typed_array_length(src_tarray); + for (i = 0; i < length; i++) { + njs_typed_array_set(array, i, + njs_typed_array_get(src_tarray, i)); + } + + } else { + memcpy(&buffer->u.u8[0], &src_tarray->buffer->u.u8[0], size); + } + + } else if (src_array != NULL) { + for (i = 0; i < length; i++) { + ret = njs_value_to_number(vm, &src_array->start[i], &num); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (ret == NJS_OK) { + njs_typed_array_set(array, i, num); + } + } + + } else if (!njs_is_array_buffer(value) && njs_is_object(value)) { + for (i = 0; i < length; i++) { + njs_uint32_to_string(&index, i); + + ret = njs_value_property(vm, value, &index, &prop); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + num = NAN; + + if (ret == NJS_OK) { + ret = njs_value_to_number(vm, &prop, &num); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + } + + njs_typed_array_set(array, i, num); + } + } + + njs_lvlhsh_init(&array->object.hash); + njs_lvlhsh_init(&array->object.shared_hash); + array->object.__proto__ = &vm->prototypes[type].object; + array->object.type = NJS_TYPED_ARRAY; + array->object.shared = 0; + array->object.extensible = 1; + + njs_set_typed_array(&vm->retval, array); + + return NJS_OK; + +memory_error: + + njs_memory_error(vm); + + return NJS_ERROR; +} + + +static njs_int_t +njs_typed_array_get_this(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + vm->retval = args[0]; + + return NJS_OK; +} + + +static const njs_value_t njs_typed_array_uint8_tag = njs_string("Uint8Array"); +static const njs_value_t njs_typed_array_uint8_clamped_tag = + njs_long_string("Uint8ClampedArray"); +static const njs_value_t njs_typed_array_int8_tag = njs_string("Int8Array"); +static const njs_value_t njs_typed_array_uint16_tag = + njs_string("Uint16Array"); +static const njs_value_t njs_typed_array_int16_tag = njs_string("Int16Array"); +static const njs_value_t njs_typed_array_uint32_tag = + njs_string("Uint32Array"); +static const njs_value_t njs_typed_array_int32_tag = njs_string("Int32Array"); +static const njs_value_t njs_typed_array_float32_tag = + njs_string("Float32Array"); +static const njs_value_t njs_typed_array_float64_tag = + njs_string("Float64Array"); + +static njs_int_t +njs_typed_array_get_string_tag(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_value_t *this; + + static const njs_value_t *tags[NJS_OBJ_TYPE_TYPED_ARRAY_SIZE] = { + &njs_typed_array_uint8_tag, + &njs_typed_array_uint8_clamped_tag, + &njs_typed_array_int8_tag, + &njs_typed_array_uint16_tag, + &njs_typed_array_int16_tag, + &njs_typed_array_uint32_tag, + &njs_typed_array_int32_tag, + &njs_typed_array_float32_tag, + &njs_typed_array_float64_tag, + }; + + this = njs_argument(args, 0); + + if (!njs_is_typed_array(this)) { + njs_set_undefined(&vm->retval); + return NJS_OK; + } + + vm->retval = *tags[njs_typed_array_index(njs_typed_array(this)->type)]; + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_length(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_value_t *this; + njs_typed_array_t *array; + + this = njs_argument(args, 0); + if (!njs_is_typed_array(this)) { + njs_type_error(vm, "Method TypedArray.prototype.length called " + "on incompatible receiver"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + + njs_set_number(&vm->retval, njs_typed_array_length(array)); + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_buffer(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_value_t *this; + njs_typed_array_t *array; + + this = njs_argument(args, 0); + if (!njs_is_typed_array(this)) { + njs_type_error(vm, "Method TypedArray.prototype.buffer called " + "on incompatible receiver"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + + njs_set_array_buffer(&vm->retval, njs_typed_array_buffer(array)); + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_byte_length(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_value_t *this; + njs_typed_array_t *array; + + this = njs_argument(args, 0); + if (!njs_is_typed_array(this)) { + njs_type_error(vm, "Method TypedArray.prototype.byteLength called " + "on incompatible receiver"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + + njs_set_number(&vm->retval, array->byte_length); + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_byte_offset(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_value_t *this; + njs_typed_array_t *array; + + this = njs_argument(args, 0); + if (!njs_is_typed_array(this)) { + njs_type_error(vm, "Method TypedArray.prototype.byteOffset called " + "on incompatible receiver"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + + njs_set_number(&vm->retval, array->offset + * njs_typed_array_element_size(array->type)); + + return NJS_OK; +} + + +njs_int_t +njs_typed_array_set_value(njs_vm_t *vm, njs_typed_array_t *array, + uint32_t index, njs_value_t *setval) +{ + double num; + njs_int_t ret; + + ret = njs_value_to_number(vm, setval, &num); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_typed_array_set(array, index, num); + + njs_set_number(setval, num); + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_set(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + double num; + uint32_t i, length, src_length; + int64_t offset; + njs_int_t ret; + njs_value_t *this, *src, *value, index, prop; + njs_array_t *array; + njs_typed_array_t *self, *src_tarray; + + this = njs_argument(args, 0); + if (njs_slow_path(!njs_is_typed_array(this))) { + njs_type_error(vm, "this is not a typed array"); + return NJS_ERROR; + } + + self = njs_typed_array(this); + src = njs_arg(args, nargs, 1); + value = njs_arg(args, nargs, 2); + + ret = njs_value_to_integer(vm, value, &offset); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + if (njs_slow_path(offset < 0)) { + njs_range_error(vm, "offset is out of bounds"); + return NJS_ERROR; + } + + length = njs_typed_array_length(self); + + if (njs_is_typed_array(src)) { + src_tarray = njs_typed_array(src); + src_length = njs_typed_array_length(src_tarray); + + if (njs_slow_path((src_length > length) + || (offset > length - src_length))) + { + njs_range_error(vm, "source is too large"); + return NJS_ERROR; + } + + length = njs_min(njs_typed_array_length(src_tarray), length - offset); + + for (i = 0; i < length; i++) { + njs_typed_array_set(self, offset + i, + njs_typed_array_get(src_tarray, i)); + } + + } else { + if (njs_is_array(src) && njs_object_hash_is_empty(src)) { + array = njs_array(src); + src_length = array->length; + + if (njs_slow_path((src_length > length) + || (offset > length - src_length))) + { + njs_range_error(vm, "source is too large"); + return NJS_ERROR; + } + + length = njs_min(array->length, length - offset); + + for (i = 0; i < length; i++) { + ret = njs_value_to_number(vm, &array->start[i], &num); + if (ret == NJS_OK) { + njs_typed_array_set(self, offset + i, num); + } + } + + goto done; + } + + ret = njs_value_to_object(vm, src); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_object_length(vm, src, &src_length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (njs_slow_path((src_length > length) + || (offset > length - src_length))) + { + njs_range_error(vm, "source is too large"); + return NJS_ERROR; + } + + length = njs_min(src_length, length - offset); + + for (i = 0; i < length; i++) { + njs_uint32_to_string(&index, i); + + ret = njs_value_property(vm, src, &index, &prop); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + num = NAN; + + if (ret == NJS_OK) { + ret = njs_value_to_number(vm, &prop, &num); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + } + + njs_typed_array_set(self, offset + i, num); + } + } + +done: + + njs_set_undefined(&vm->retval); + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + float f32; + int8_t i8; + double num; + int16_t i16; + int32_t i32; + uint8_t u8; + int64_t start, end; + uint32_t i, length; + njs_int_t ret; + njs_value_t *this, *setval, lvalue; + njs_typed_array_t *array; + njs_array_buffer_t *buffer; + + this = njs_argument(args, 0); + if (njs_slow_path(!njs_is_typed_array(this))) { + njs_type_error(vm, "this is not a typed array"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + length = njs_typed_array_length(array); + + setval = njs_lvalue_arg(&lvalue, args, nargs, 1); + ret = njs_value_to_number(vm, setval, &num); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &start); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length); + + if (njs_is_undefined(njs_arg(args, nargs, 3))) { + end = length; + + } else { + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 3), &end); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + end = (end < 0) ? njs_max(length + end, 0) : njs_min(end, length); + + njs_set_typed_array(&vm->retval, array); + + buffer = array->buffer; + + switch (array->type) { + case NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY: + if (num < 0) { + num = 0; + + } else if (num > 255) { + num = 255; + } + + u8 = lrint(num); + + for (i = start; i < end; i++) { + buffer->u.u8[i] = u8; + } + + break; + + case NJS_OBJ_TYPE_UINT8_ARRAY: + case NJS_OBJ_TYPE_INT8_ARRAY: + i8 = njs_number_to_int32(num); + + for (i = start; i < end; i++) { + buffer->u.u8[i] = i8; + } + + break; + + case NJS_OBJ_TYPE_UINT16_ARRAY: + case NJS_OBJ_TYPE_INT16_ARRAY: + i16 = njs_number_to_int32(num); + + for (i = start; i < end; i++) { + buffer->u.u16[i] = i16; + } + + break; + + case NJS_OBJ_TYPE_UINT32_ARRAY: + case NJS_OBJ_TYPE_INT32_ARRAY: + i32 = njs_number_to_int32(num); + + for (i = start; i < end; i++) { + buffer->u.u32[i] = i32; + } + + break; + + case NJS_OBJ_TYPE_FLOAT32_ARRAY: + f32 = num; + + for (i = start; i < end; i++) { + buffer->u.f32[i] = f32; + } + + break; + + default: + + /* NJS_OBJ_TYPE_FLOAT64_ARRAY. */ + + for (i = start; i < end; i++) { + buffer->u.f64[i] = num; + } + } + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_species_create(njs_vm_t *vm, njs_value_t *exemplar, + int64_t count, njs_value_t *retval) +{ + njs_int_t ret; + njs_value_t this, constructor, argument; + njs_object_t *object; + njs_typed_array_t *array; + + array = njs_typed_array(exemplar); + + njs_set_function(&constructor, &vm->constructors[array->type]); + + ret = njs_value_species_constructor(vm, exemplar, &constructor, + &constructor); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + object = njs_function_new_object(vm, &constructor); + if (njs_slow_path(object == NULL)) { + return NJS_ERROR; + } + + njs_set_object(&this, object); + njs_set_number(&argument, count); + + ret = njs_function_call2(vm, njs_function(&constructor), &this, + &argument, 1, retval, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + if (!njs_is_typed_array(retval)) { + njs_type_error(vm, "Derived TypedArray constructor " + "returned not a typed array"); + return NJS_ERROR; + } + + array = njs_typed_array(retval); + if (njs_typed_array_length(array) < count) { + njs_type_error(vm, "Derived TypedArray constructor too small array"); + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + int64_t start, end, count; + uint32_t i, element_size, length; + njs_int_t ret; + njs_value_t *this, *value; + njs_typed_array_t *array, *new_array; + njs_array_buffer_t *buffer, *new_buffer; + + this = njs_argument(args, 0); + if (njs_slow_path(!njs_is_typed_array(this))) { + njs_type_error(vm, "this is not a typed array"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + length = njs_typed_array_length(array); + buffer = njs_typed_array_buffer(array); + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start); + if (njs_slow_path(ret != NJS_OK)) { + njs_range_error(vm, "invalid start"); + return NJS_ERROR; + } + + start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length); + + value = njs_arg(args, nargs, 2); + + if (njs_is_undefined(value)) { + end = length; + + } else { + ret = njs_value_to_integer(vm, value, &end); + if (njs_slow_path(ret != NJS_OK)) { + njs_range_error(vm, "invalid end"); + return NJS_ERROR; + } + } + + end = (end < 0) ? njs_max(length + end, 0) : njs_min(end, length); + + count = njs_max(end - start, 0); + + ret = njs_typed_array_species_create(vm, this, count, &vm->retval); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + new_array = njs_typed_array(&vm->retval); + new_buffer = njs_typed_array_buffer(new_array); + element_size = njs_typed_array_element_size(array->type); + + if (njs_fast_path(array->type == new_array->type)) { + start = start * element_size; + count = count * element_size; + + memcpy(&new_buffer->u.u8[0], &buffer->u.u8[start], count); + + } else { + for (i = 0; i < count; i++) { + njs_typed_array_set(new_array, i, + njs_typed_array_get(array, i + start)); + } + } + + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_prototype_copy_within(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + int64_t length, to, from, final, count; + uint32_t element_size; + njs_int_t ret; + njs_value_t *this, *value; + njs_typed_array_t *array; + njs_array_buffer_t *buffer; + + this = njs_argument(args, 0); + if (njs_slow_path(!njs_is_typed_array(this))) { + njs_type_error(vm, "this is not a typed array"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + length = njs_typed_array_length(array); + + value = njs_arg(args, nargs, 1); + + ret = njs_value_to_integer(vm, value, &to); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + to = to < 0 ? njs_max(to + length, 0) : njs_min(to, length); + + value = njs_arg(args, nargs, 2); + + ret = njs_value_to_integer(vm, value, &from); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + from = from < 0 ? njs_max(from + length, 0) : njs_min(from, length); + + value = njs_arg(args, nargs, 3); + + if (njs_is_undefined(value)) { + final = length; + + } else { + ret = njs_value_to_integer(vm, value, &final); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + final = (final < 0) ? njs_max(final + length, 0) : njs_min(final, length); + + njs_set_typed_array(&vm->retval, array); + + count = njs_min(final - from, length - to); + + if (count <= 0) { + return NJS_OK; + } + + buffer = njs_typed_array_buffer(array); + element_size = njs_typed_array_element_size(array->type); + + to = to * element_size; + from = from * element_size; + count = count * element_size; + + memmove(&buffer->u.u8[to], &buffer->u.u8[from], count); + + return NJS_OK; +} + + +njs_int_t +njs_typed_array_to_chain(njs_vm_t *vm, njs_chb_t *chain, + njs_typed_array_t *array, njs_value_t *sep) +{ + size_t size, length, arr_length; + uint32_t i; + njs_string_prop_t separator; + + if (sep == NULL) { + sep = njs_value_arg(&njs_string_comma); + } + + (void) njs_string_prop(&separator, sep); + + arr_length = njs_typed_array_length(array); + + if (arr_length == 0) { + return 0; + } + + for (i = 0; i < arr_length; i++) { + njs_number_to_chain(vm, chain, njs_typed_array_get(array, i)); + njs_chb_append(chain, separator.start, separator.size); + } + + njs_chb_drop(chain, separator.size); + + size = njs_chb_size(chain); + + if (njs_utf8_length(separator.start, separator.size) >= 0) { + length = size - (separator.size - separator.length) * (arr_length - 1); + + } else { + length = 0; + } + + return length; +} + + +static njs_int_t +njs_typed_array_prototype_join(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + u_char *p; + size_t size, length, arr_length; + njs_int_t ret; + njs_chb_t chain; + njs_value_t *this, *separator; + njs_typed_array_t *array; + + this = njs_argument(args, 0); + if (njs_slow_path(!njs_is_typed_array(this))) { + njs_type_error(vm, "this is not a typed array"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + arr_length = njs_typed_array_length(array); + + separator = njs_arg(args, nargs, 1); + + if (njs_slow_path(!njs_is_string(separator))) { + if (njs_is_undefined(separator)) { + separator = njs_value_arg(&njs_string_comma); + + } else { + ret = njs_value_to_string(vm, separator, separator); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + } + + if (arr_length == 0) { + vm->retval = njs_string_empty; + return NJS_OK; + } + + njs_chb_init(&chain, vm->mem_pool); + + length = njs_typed_array_to_chain(vm, &chain, array, separator); + size = njs_chb_size(&chain); + + p = njs_string_alloc(vm, &vm->retval, size, length); + if (njs_slow_path(p == NULL)) { + return NJS_ERROR; + } + + njs_chb_join_to(&chain, p); + njs_chb_destroy(&chain); + + return NJS_OK; +} + + +static njs_int_t +njs_typed_array_constructor_intrinsic(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_type_error(vm, "Abstract class TypedArray not directly constructable"); + + return NJS_ERROR; +} + + +static const njs_object_prop_t njs_typed_array_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("TypedArray"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 0, 0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_SPECIES), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_typed_array_get_this, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_constructor_init = { + njs_typed_array_constructor_props, + njs_nitems(njs_typed_array_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_prototype_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_typed_array_get_string_tag, + 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("buffer"), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_typed_array_prototype_buffer, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("byteLength"), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_typed_array_prototype_byte_length, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("byteOffset"), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_typed_array_prototype_byte_offset, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_typed_array_prototype_length, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("set"), + .value = njs_native_function(njs_typed_array_prototype_set, 2), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("slice"), + .value = njs_native_function(njs_typed_array_prototype_slice, 2), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("copyWithin"), + .value = njs_native_function(njs_typed_array_prototype_copy_within, 2), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("fill"), + .value = njs_native_function(njs_typed_array_prototype_fill, 1), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("join"), + .value = njs_native_function(njs_typed_array_prototype_join, 1), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("toString"), + .value = njs_native_function(njs_array_prototype_to_string, 0), + .writable = 1, + .configurable = 1, + }, +}; + + +static const njs_object_init_t njs_typed_array_prototype_init = { + njs_typed_array_prototype_properties, + njs_nitems(njs_typed_array_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor_intrinsic, 0, 0), + .prototype_props = &njs_typed_array_prototype_init, + .constructor_props = &njs_typed_array_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_u8_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Uint8Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 1), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u8_constructor_init = { + njs_typed_array_u8_constructor_props, + njs_nitems(njs_typed_array_u8_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_u8_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 1), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u8_prototype_init = { + njs_typed_array_u8_prototype_properties, + njs_nitems(njs_typed_array_u8_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_u8_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_UINT8_ARRAY), + .prototype_props = &njs_typed_array_u8_prototype_init, + .constructor_props = &njs_typed_array_u8_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_u8c_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_long_string("Uint8ClampedArray"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 1), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u8c_constructor_init = { + njs_typed_array_u8c_constructor_props, + njs_nitems(njs_typed_array_u8c_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_u8c_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 1), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u8c_prototype_init = { + njs_typed_array_u8c_prototype_properties, + njs_nitems(njs_typed_array_u8c_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_u8clamped_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY), + .prototype_props = &njs_typed_array_u8c_prototype_init, + .constructor_props = &njs_typed_array_u8c_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_i8_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Int8Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 1), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_i8_constructor_init = { + njs_typed_array_i8_constructor_props, + njs_nitems(njs_typed_array_i8_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_i8_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 1), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_i8_prototype_init = { + njs_typed_array_i8_prototype_properties, + njs_nitems(njs_typed_array_i8_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_i8_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_INT8_ARRAY), + .prototype_props = &njs_typed_array_i8_prototype_init, + .constructor_props = &njs_typed_array_i8_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_u16_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Uint16Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 2), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u16_constructor_init = { + njs_typed_array_u16_constructor_props, + njs_nitems(njs_typed_array_u16_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_u16_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 2), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u16_prototype_init = { + njs_typed_array_u16_prototype_properties, + njs_nitems(njs_typed_array_u16_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_u16_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_UINT16_ARRAY), + .prototype_props = &njs_typed_array_u16_prototype_init, + .constructor_props = &njs_typed_array_u16_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_i16_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Int16Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 2), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_i16_constructor_init = { + njs_typed_array_i16_constructor_props, + njs_nitems(njs_typed_array_i16_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_i16_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 2), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_i16_prototype_init = { + njs_typed_array_i16_prototype_properties, + njs_nitems(njs_typed_array_i16_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_i16_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_INT16_ARRAY), + .prototype_props = &njs_typed_array_i16_prototype_init, + .constructor_props = &njs_typed_array_i16_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_u32_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Uint32Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 4), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u32_constructor_init = { + njs_typed_array_u32_constructor_props, + njs_nitems(njs_typed_array_u32_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_u32_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 4), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_u32_prototype_init = { + njs_typed_array_u32_prototype_properties, + njs_nitems(njs_typed_array_u32_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_u32_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_UINT32_ARRAY), + .prototype_props = &njs_typed_array_u32_prototype_init, + .constructor_props = &njs_typed_array_u32_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_i32_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Int32Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 4), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_i32_constructor_init = { + njs_typed_array_i32_constructor_props, + njs_nitems(njs_typed_array_i32_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_i32_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 4), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_i32_prototype_init = { + njs_typed_array_i32_prototype_properties, + njs_nitems(njs_typed_array_i32_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_i32_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_INT32_ARRAY), + .prototype_props = &njs_typed_array_i32_prototype_init, + .constructor_props = &njs_typed_array_i32_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_f32_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Float32Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 4), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_f32_constructor_init = { + njs_typed_array_f32_constructor_props, + njs_nitems(njs_typed_array_f32_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_f32_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 4), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_f32_prototype_init = { + njs_typed_array_f32_prototype_properties, + njs_nitems(njs_typed_array_f32_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_f32_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_FLOAT32_ARRAY), + .prototype_props = &njs_typed_array_f32_prototype_init, + .constructor_props = &njs_typed_array_f32_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static const njs_object_prop_t njs_typed_array_f64_constructor_props[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Float64Array"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 3.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 8), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_f64_constructor_init = { + njs_typed_array_f64_constructor_props, + njs_nitems(njs_typed_array_f64_constructor_props), +}; + + +static const njs_object_prop_t njs_typed_array_f64_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("BYTES_PER_ELEMENT"), + .value = njs_value(NJS_NUMBER, 1, 8), + .configurable = 0, + .enumerable = 0, + .writable = 0, + }, +}; + + +static const njs_object_init_t njs_typed_array_f64_prototype_init = { + njs_typed_array_f64_prototype_properties, + njs_nitems(njs_typed_array_f64_prototype_properties), +}; + + +const njs_object_type_init_t njs_typed_array_f64_type_init = { + .constructor = njs_native_ctor(njs_typed_array_constructor, 3, + NJS_OBJ_TYPE_FLOAT64_ARRAY), + .prototype_props = &njs_typed_array_f64_prototype_init, + .constructor_props = &njs_typed_array_f64_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; diff --git a/src/njs_typed_array.h b/src/njs_typed_array.h new file mode 100644 index 00000000..daed1684 --- /dev/null +++ b/src/njs_typed_array.h @@ -0,0 +1,163 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_TYPED_ARRAY_H_INCLUDED_ +#define _NJS_TYPED_ARRAY_H_INCLUDED_ + + +njs_int_t njs_typed_array_set_value(njs_vm_t *vm, njs_typed_array_t *array, + uint32_t index, njs_value_t *setval); +njs_int_t njs_typed_array_to_chain(njs_vm_t *vm, njs_chb_t *chain, + njs_typed_array_t *array, njs_value_t *sep); + + +njs_inline unsigned +njs_typed_array_element_size(njs_object_type_t type) +{ + switch (type) { + case NJS_OBJ_TYPE_UINT8_ARRAY: + case NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY: + case NJS_OBJ_TYPE_INT8_ARRAY: + return 1; + + case NJS_OBJ_TYPE_UINT16_ARRAY: + case NJS_OBJ_TYPE_INT16_ARRAY: + return 2; + + case NJS_OBJ_TYPE_UINT32_ARRAY: + case NJS_OBJ_TYPE_INT32_ARRAY: + case NJS_OBJ_TYPE_FLOAT32_ARRAY: + return 4; + + default: + + /* NJS_OBJ_TYPE_FLOAT64_ARRAY. */ + + return 8; + } +} + + +njs_inline uint32_t +njs_typed_array_length(const njs_typed_array_t *array) +{ + return array->byte_length / njs_typed_array_element_size(array->type); +} + + +njs_inline double +njs_typed_array_get(const njs_typed_array_t *array, uint32_t index) +{ + njs_array_buffer_t *buffer; + + index += array->offset; + + buffer = array->buffer; + + switch (array->type) { + case NJS_OBJ_TYPE_UINT8_ARRAY: + case NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY: + return buffer->u.u8[index]; + + case NJS_OBJ_TYPE_INT8_ARRAY: + return buffer->u.i8[index]; + + case NJS_OBJ_TYPE_UINT16_ARRAY: + return buffer->u.u16[index]; + + case NJS_OBJ_TYPE_INT16_ARRAY: + return buffer->u.i16[index]; + + case NJS_OBJ_TYPE_UINT32_ARRAY: + return buffer->u.u32[index]; + + case NJS_OBJ_TYPE_INT32_ARRAY: + return buffer->u.i32[index]; + + case NJS_OBJ_TYPE_FLOAT32_ARRAY: + return buffer->u.f32[index]; + + default: + + /* NJS_OBJ_TYPE_FLOAT64_ARRAY. */ + + return buffer->u.f64[index]; + } + + return NAN; +} + + +njs_inline void +njs_typed_array_set(njs_typed_array_t *array, uint32_t index, double v) +{ + int8_t i8; + int16_t i16; + int32_t i32; + njs_array_buffer_t *buffer; + + index += array->offset; + + buffer = array->buffer; + + switch (array->type) { + case NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY: + if (v < 0) { + v = 0; + } + + if (v > 255) { + v = 255; + } + + buffer->u.u8[index] = lrint(v); + + break; + + case NJS_OBJ_TYPE_UINT8_ARRAY: + case NJS_OBJ_TYPE_INT8_ARRAY: + i8 = njs_number_to_int32(v); + buffer->u.u8[index] = i8; + break; + + case NJS_OBJ_TYPE_UINT16_ARRAY: + case NJS_OBJ_TYPE_INT16_ARRAY: + i16 = njs_number_to_int32(v); + buffer->u.u16[index] = i16; + break; + + case NJS_OBJ_TYPE_UINT32_ARRAY: + case NJS_OBJ_TYPE_INT32_ARRAY: + i32 = njs_number_to_int32(v); + buffer->u.u32[index] = i32; + break; + + case NJS_OBJ_TYPE_FLOAT32_ARRAY: + buffer->u.f32[index] = v; + break; + + default: + + /* NJS_OBJ_TYPE_FLOAT64_ARRAY. */ + + buffer->u.f64[index] = v; + } +} + + +extern const njs_object_type_init_t njs_typed_array_type_init; +extern const njs_object_type_init_t njs_typed_array_u8_type_init; +extern const njs_object_type_init_t njs_typed_array_u8clamped_type_init; +extern const njs_object_type_init_t njs_typed_array_i8_type_init; +extern const njs_object_type_init_t njs_typed_array_u16_type_init; +extern const njs_object_type_init_t njs_typed_array_i16_type_init; +extern const njs_object_type_init_t njs_typed_array_u32_type_init; +extern const njs_object_type_init_t njs_typed_array_i32_type_init; +extern const njs_object_type_init_t njs_typed_array_f32_type_init; +extern const njs_object_type_init_t njs_typed_array_f64_type_init; + + +#endif /* _NJS_TYPED_ARRAY_H_INCLUDED_ */ diff --git a/src/njs_value.c b/src/njs_value.c index e015c213..aff4dd98 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -13,6 +13,8 @@ static njs_int_t njs_object_property_query(njs_vm_t *vm, const njs_value_t *key); static njs_int_t njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_array_t *array, uint32_t index); +static njs_int_t njs_typed_array_property_query(njs_vm_t *vm, + njs_property_query_t *pq, njs_typed_array_t *array, uint32_t index); static njs_int_t njs_string_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object, uint32_t index); static njs_int_t njs_external_property_query(njs_vm_t *vm, @@ -339,7 +341,10 @@ njs_type_string(njs_value_type_t type) return "array"; case NJS_ARRAY_BUFFER: - return "object arraybuffer"; + return "array buffer"; + + case NJS_TYPED_ARRAY: + return "typed array"; case NJS_OBJECT_BOOLEAN: return "object boolean"; @@ -500,7 +505,8 @@ njs_value_is_function(const njs_value_t *value) * in NJS_PROPERTY_QUERY_GET * prop->type is NJS_PROPERTY or NJS_PROPERTY_HANDLER. * in NJS_PROPERTY_QUERY_SET, NJS_PROPERTY_QUERY_DELETE - * prop->type is NJS_PROPERTY, NJS_PROPERTY_REF or + * prop->type is NJS_PROPERTY, NJS_PROPERTY_REF, + * NJS_PROPERTY_TYPED_ARRAY_REF or * NJS_PROPERTY_HANDLER. * NJS_DECLINED property was not found in object, * if pq->lhq.value != NULL it contains retval of type @@ -515,6 +521,7 @@ njs_int_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, njs_value_t *key) { + double num; uint32_t index; njs_int_t ret; njs_object_t *obj; @@ -541,10 +548,9 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_STRING: if (njs_fast_path(!njs_is_null_or_undefined_or_boolean(key))) { - index = njs_key_to_index(key); - - if (njs_fast_path(index < NJS_STRING_MAX_LENGTH)) { - return njs_string_property_query(vm, pq, value, index); + num = njs_key_to_index(key); + if (njs_fast_path(njs_number_is_integer_index(num, key))) { + return njs_string_property_query(vm, pq, value, num); } } @@ -554,6 +560,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_OBJECT: case NJS_ARRAY: case NJS_ARRAY_BUFFER: + case NJS_TYPED_ARRAY: case NJS_OBJECT_BOOLEAN: case NJS_OBJECT_NUMBER: case NJS_OBJECT_SYMBOL: @@ -624,12 +631,13 @@ static njs_int_t njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_object_t *object, const njs_value_t *key) { - uint32_t index; + double num; njs_int_t ret; njs_bool_t own; njs_array_t *array; njs_object_t *proto; njs_object_prop_t *prop; + njs_typed_array_t *tarray; njs_object_value_t *ov; pq->lhq.proto = &njs_object_hash_proto; @@ -645,19 +653,32 @@ njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, if (!njs_is_null_or_undefined_or_boolean(key)) { switch (proto->type) { case NJS_ARRAY: - index = njs_key_to_index(key); - if (njs_fast_path(index < NJS_ARRAY_MAX_INDEX)) { + num = njs_key_to_index(key); + if (njs_fast_path(njs_number_is_integer_index(num, key))) { array = (njs_array_t *) proto; - return njs_array_property_query(vm, pq, array, index); + return njs_array_property_query(vm, pq, array, num); + } + + break; + + case NJS_TYPED_ARRAY: + num = njs_key_to_index(key); + if (njs_fast_path(njs_number_is_integer_index(num, key))) { + tarray = (njs_typed_array_t *) proto; + return njs_typed_array_property_query(vm, pq, tarray, num); + } + + if (!isnan(num)) { + return NJS_DECLINED; } break; case NJS_OBJECT_STRING: - index = njs_key_to_index(key); - if (njs_fast_path(index < NJS_STRING_MAX_LENGTH)) { + num = njs_key_to_index(key); + if (njs_fast_path(njs_number_is_integer_index(num, key))) { ov = (njs_object_value_t *) proto; - ret = njs_string_property_query(vm, pq, &ov->value, index); + ret = njs_string_property_query(vm, pq, &ov->value, num); if (njs_fast_path(ret != NJS_DECLINED)) { return ret; @@ -774,6 +795,38 @@ njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, } +static njs_int_t +njs_typed_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_typed_array_t *array, uint32_t index) +{ + njs_object_prop_t *prop; + + if (index >= njs_typed_array_length(array)) { + return NJS_DECLINED; + } + + prop = &pq->scratch; + + if (pq->query == NJS_PROPERTY_QUERY_GET) { + njs_set_number(&prop->value, njs_typed_array_get(array, index)); + prop->type = NJS_PROPERTY; + + } else { + prop->value.data.u.typed_array = array; + prop->value.data.magic32 = index; + prop->type = NJS_PROPERTY_TYPED_ARRAY_REF; + } + + prop->writable = 1; + prop->enumerable = 1; + prop->configurable = 0; + + pq->lhq.value = prop; + + return NJS_OK; +} + + static njs_int_t njs_string_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object, uint32_t index) @@ -1098,6 +1151,12 @@ njs_value_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, *prop->value.data.u.value = *setval; return NJS_OK; + case NJS_PROPERTY_TYPED_ARRAY_REF: + return njs_typed_array_set_value(vm, + njs_typed_array(&prop->value), + prop->value.data.magic32, + setval); + default: njs_internal_error(vm, "unexpected property type \"%s\" " "while setting", @@ -1124,6 +1183,15 @@ njs_value_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, goto found; } + if (njs_slow_path(pq.own && njs_is_typed_array(value) + && njs_is_string(key))) + { + /* Integer-Indexed Exotic Objects [[DefineOwnProperty]]. */ + if (!isnan(njs_string_to_index(key))) { + return NJS_OK; + } + } + break; case NJS_ERROR: diff --git a/src/njs_value.h b/src/njs_value.h index 28ed7640..daf08534 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -14,17 +14,17 @@ */ typedef enum { - NJS_NULL = 0x00, - NJS_UNDEFINED = 0x01, + NJS_NULL, + NJS_UNDEFINED, /* The order of the above type is used in njs_is_null_or_undefined(). */ - NJS_BOOLEAN = 0x02, + NJS_BOOLEAN, /* * The order of the above type is used in * njs_is_null_or_undefined_or_boolean(). */ - NJS_NUMBER = 0x03, + NJS_NUMBER, /* * The order of the above type is used in njs_is_numeric(). * Booleans, null and void values can be used in mathematical operations: @@ -32,16 +32,16 @@ typedef enum { * a numeric value of the null and false values is zero, * a numeric value of the void value is NaN. */ - NJS_SYMBOL = 0x04, + NJS_SYMBOL, - NJS_STRING = 0x05, + NJS_STRING, /* The order of the above type is used in njs_is_primitive(). */ - NJS_DATA = 0x06, + NJS_DATA, /* The type is external code. */ - NJS_EXTERNAL = 0x07, + NJS_EXTERNAL, /* * The invalid value type is used: @@ -49,7 +49,7 @@ typedef enum { * to detect non-declared explicitly or implicitly variables, * for native property getters. */ - NJS_INVALID = 0x08, + NJS_INVALID, /* * The object types are >= NJS_OBJECT, this is used in njs_is_object(). @@ -59,17 +59,22 @@ typedef enum { * is used in vm->prototypes and vm->constructors arrays. */ NJS_OBJECT = 0x10, - NJS_ARRAY = 0x11, - NJS_OBJECT_BOOLEAN = 0x12, - NJS_OBJECT_NUMBER = 0x13, - NJS_OBJECT_SYMBOL = 0x14, - NJS_OBJECT_STRING = 0x15, - NJS_FUNCTION = 0x16, - NJS_REGEXP = 0x17, - NJS_DATE = 0x18, - NJS_PROMISE = 0x19, - NJS_OBJECT_VALUE = 0x1A, - NJS_ARRAY_BUFFER = 0x1B, + NJS_ARRAY, +#define NJS_OBJECT_WRAPPER_MIN (NJS_OBJECT_BOOLEAN) + NJS_OBJECT_BOOLEAN, + NJS_OBJECT_NUMBER, + NJS_OBJECT_SYMBOL, + NJS_OBJECT_STRING, +#define NJS_OBJECT_WRAPPER_MAX (NJS_OBJECT_STRING + 1) +#define NJS_OBJECT_SPECIAL_MIN (NJS_FUNCTION) + NJS_FUNCTION, + NJS_REGEXP, + NJS_DATE, + NJS_TYPED_ARRAY, +#define NJS_OBJECT_SPECIAL_MAX (NJS_TYPED_ARRAY + 1) + NJS_PROMISE, + NJS_OBJECT_VALUE, + NJS_ARRAY_BUFFER, NJS_VALUE_TYPE_MAX } njs_value_type_t; @@ -82,6 +87,7 @@ typedef struct njs_function_lambda_s njs_function_lambda_t; typedef struct njs_regexp_pattern_s njs_regexp_pattern_t; typedef struct njs_array_s njs_array_t; typedef struct njs_array_buffer_s njs_array_buffer_t; +typedef struct njs_typed_array_s njs_typed_array_t; typedef struct njs_regexp_s njs_regexp_t; typedef struct njs_date_s njs_date_t; typedef struct njs_object_value_s njs_promise_t; @@ -143,6 +149,7 @@ union njs_value_s { njs_object_t *object; njs_array_t *array; njs_array_buffer_t *array_buffer; + njs_typed_array_t *typed_array; njs_object_value_t *object_value; njs_function_t *function; njs_function_lambda_t *lambda; @@ -249,6 +256,15 @@ struct njs_array_buffer_s { }; +struct njs_typed_array_s { + njs_object_t object; + njs_array_buffer_t *buffer; + size_t offset; // byte_offset / element_size + size_t byte_length; + uint8_t type; +}; + + typedef struct { union { uint32_t count; @@ -345,6 +361,7 @@ typedef enum { typedef enum { NJS_PROPERTY = 0, NJS_PROPERTY_REF, + NJS_PROPERTY_TYPED_ARRAY_REF, NJS_PROPERTY_HANDLER, NJS_WHITEOUT, } njs_object_prop_type_t; @@ -634,6 +651,10 @@ typedef struct { ((value)->type == NJS_ARRAY_BUFFER) +#define njs_is_typed_array(value) \ + ((value)->type == NJS_TYPED_ARRAY) + + #define njs_is_function(value) \ ((value)->type == NJS_FUNCTION) @@ -710,6 +731,14 @@ typedef struct { ((value)->data.u.array_buffer) +#define njs_typed_array(value) \ + ((value)->data.u.typed_array) + + +#define njs_typed_array_buffer(value) \ + ((value)->buffer) + + #define njs_array_start(value) \ ((value)->data.u.array->start) @@ -870,6 +899,15 @@ njs_set_array_buffer(njs_value_t *value, njs_array_buffer_t *array) } +njs_inline void +njs_set_typed_array(njs_value_t *value, njs_typed_array_t *array) +{ + value->data.u.typed_array = array; + value->type = NJS_TYPED_ARRAY; + value->data.truth = 1; +} + + njs_inline void njs_set_function(njs_value_t *value, njs_function_t *function) { diff --git a/src/njs_value_conversion.h b/src/njs_value_conversion.h index 581a29c6..d6845781 100644 --- a/src/njs_value_conversion.h +++ b/src/njs_value_conversion.h @@ -97,7 +97,7 @@ njs_value_to_length(njs_vm_t *vm, njs_value_t *value, uint32_t *dst) njs_inline njs_int_t -njs_value_to_index(njs_vm_t *vm, njs_value_t *value, uint32_t *dst) +njs_value_to_index(njs_vm_t *vm, njs_value_t *value, uint64_t *dst) { int64_t integer_index; njs_int_t ret; @@ -116,7 +116,7 @@ njs_value_to_index(njs_vm_t *vm, njs_value_t *value, uint32_t *dst) return NJS_ERROR; } - *dst = (uint32_t) integer_index; + *dst = integer_index; } return NJS_OK; diff --git a/src/njs_vm.h b/src/njs_vm.h index 5167ff80..623a2176 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -82,7 +82,6 @@ typedef enum { typedef enum { NJS_OBJ_TYPE_OBJECT = 0, NJS_OBJ_TYPE_ARRAY, - NJS_OBJ_TYPE_ARRAY_BUFFER, NJS_OBJ_TYPE_BOOLEAN, NJS_OBJ_TYPE_NUMBER, NJS_OBJ_TYPE_SYMBOL, @@ -91,10 +90,29 @@ typedef enum { NJS_OBJ_TYPE_REGEXP, NJS_OBJ_TYPE_DATE, NJS_OBJ_TYPE_PROMISE, + NJS_OBJ_TYPE_ARRAY_BUFFER, + NJS_OBJ_TYPE_CRYPTO_HASH, #define NJS_OBJ_TYPE_HIDDEN_MIN (NJS_OBJ_TYPE_CRYPTO_HASH) NJS_OBJ_TYPE_CRYPTO_HMAC, -#define NJS_OBJ_TYPE_HIDDEN_MAX (NJS_OBJ_TYPE_CRYPTO_HMAC + 1) + NJS_OBJ_TYPE_TYPED_ARRAY, +#define NJS_OBJ_TYPE_HIDDEN_MAX (NJS_OBJ_TYPE_TYPED_ARRAY + 1) +#define NJS_OBJ_TYPE_NORMAL_MAX (NJS_OBJ_TYPE_HIDDEN_MAX) + +#define NJS_OBJ_TYPE_TYPED_ARRAY_MIN (NJS_OBJ_TYPE_UINT8_ARRAY) +#define njs_typed_array_index(type) (type - NJS_OBJ_TYPE_TYPED_ARRAY_MIN) + NJS_OBJ_TYPE_UINT8_ARRAY, + NJS_OBJ_TYPE_UINT8_CLAMPED_ARRAY, + NJS_OBJ_TYPE_INT8_ARRAY, + NJS_OBJ_TYPE_UINT16_ARRAY, + NJS_OBJ_TYPE_INT16_ARRAY, + NJS_OBJ_TYPE_UINT32_ARRAY, + NJS_OBJ_TYPE_INT32_ARRAY, + NJS_OBJ_TYPE_FLOAT32_ARRAY, + NJS_OBJ_TYPE_FLOAT64_ARRAY, +#define NJS_OBJ_TYPE_TYPED_ARRAY_MAX (NJS_OBJ_TYPE_FLOAT64_ARRAY + 1) +#define NJS_OBJ_TYPE_TYPED_ARRAY_SIZE (NJS_OBJ_TYPE_TYPED_ARRAY_MAX \ + - NJS_OBJ_TYPE_TYPED_ARRAY_MIN) NJS_OBJ_TYPE_ERROR, NJS_OBJ_TYPE_EVAL_ERROR, NJS_OBJ_TYPE_INTERNAL_ERROR, @@ -104,7 +122,7 @@ typedef enum { NJS_OBJ_TYPE_TYPE_ERROR, NJS_OBJ_TYPE_URI_ERROR, NJS_OBJ_TYPE_MEMORY_ERROR, -#define NJS_OBJ_TYPE_MAX (NJS_OBJ_TYPE_MEMORY_ERROR + 1) + NJS_OBJ_TYPE_MAX, } njs_object_type_t; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index b5de9966..ff0e8a27 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -1141,22 +1141,24 @@ static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, njs_value_t *init) { + double num; uint32_t index, size; + njs_int_t ret; njs_array_t *array; njs_value_t *val, name; - njs_jump_off_t ret; njs_object_prop_t *prop; njs_lvlhsh_query_t lhq; switch (value->type) { case NJS_ARRAY: - index = njs_key_to_index(key); - if (njs_slow_path(index == NJS_ARRAY_INVALID_INDEX)) { + num = njs_key_to_index(key); + if (njs_slow_path(!njs_number_is_integer_index(num, key))) { njs_internal_error(vm, "invalid index while property initialization"); return NJS_ERROR; } + index = (uint32_t) num; array = value->data.u.array; if (index >= array->length) { @@ -1420,6 +1422,8 @@ njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld) &njs_string_object, &njs_string_object, &njs_string_object, + &njs_string_object, + &njs_string_object, }; vm->retval = *types[value->type]; diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c index b85f66f6..d04fa989 100644 --- a/src/test/njs_benchmark.c +++ b/src/test/njs_benchmark.c @@ -149,9 +149,20 @@ main(int argc, char **argv) static njs_str_t while_loop = njs_str( "var i = 0; while (i < 100000000) { i++ }; i"); + static njs_str_t typed_array_10M = njs_str( + "var arr = new Uint8Array(10000000); var count = 0, length = arr.length;" + "arr.fill(2);" + "for (var i = 0; i < length; i++) { count += arr[i]; } count"); + + static njs_str_t array_10M = njs_str( + "var arr = new Array(10000000); var count = 0, length = arr.length;" + "arr.fill(2);" + "for (var i = 0; i < length; i++) { count += arr[i]; } count"); + static njs_str_t fibo_result = njs_str("3524578"); static njs_str_t json_result = njs_str("123"); static njs_str_t loop_result = njs_str("100000000"); + static njs_str_t sum_result = njs_str("20000000"); if (argc > 1) { @@ -188,9 +199,18 @@ main(int argc, char **argv) case 'u': return njs_unit_test_benchmark(&fibo_utf8, &fibo_result, "fibobench utf8 strings", 1); + + case 't': + return njs_unit_test_benchmark(&typed_array_10M, &sum_result, + "typed_array_10M", 1); + + case 'A': + return njs_unit_test_benchmark(&array_10M, &sum_result, + "array_10M", 1); } } njs_printf("unknown agrument\n"); + return EXIT_FAILURE; } diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 80a92a4e..14264d20 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -348,7 +348,7 @@ static njs_unit_test_t njs_test[] = "(1).X()"), njs_str("123") }, - /* Indexes. */ + /* Indices. */ { njs_str("var a = []; a[-1] = 2; a[-1] == a['-1']"), njs_str("true") }, @@ -359,6 +359,19 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = []; a[NaN] = 2; a[NaN] == a['NaN']"), njs_str("true") }, +#define NJS_NOT_CANONICAL_INDICES "['+0', '-0', '1.', '0.', '0.0', '4294967295', " \ + " '4294967296', '-1', '1.1', '9223372036854775808']" + + { njs_str("var a = [1,2]; " + NJS_NOT_CANONICAL_INDICES + ".every(v=>(a[v] === undefined))"), + njs_str("true") }, + + { njs_str("var a = [1,2]; " + NJS_NOT_CANONICAL_INDICES + ".every(v=>{a[v] = 'a'; return a[v] === 'a'})"), + njs_str("true") }, + /* Number.toString(radix) method. */ { njs_str("0..toString(2)"), @@ -4825,6 +4838,512 @@ static njs_unit_test_t njs_test[] = ".map(pr=>buffer.slice(pr[0], pr[1]).byteLength)"), njs_str("8,0,0,15,0,0") }, + { njs_str("[false,NaN,1]" + ".map(v=>(new Uint8Array(v)).length)"), + njs_str("0,0,1") }, + +#define NJS_TYPED_ARRAY_LIST "[Uint8Array,Uint8ClampedArray,Int8Array," \ + " Uint16Array,Int16Array,Uint32Array," \ + " Int32Array, Float32Array,Float64Array]" + +#define NJS_INT_TYPED_ARRAY_LIST "[Uint8Array,Uint8ClampedArray,Int8Array," \ + " Uint16Array,Int16Array,Uint32Array," \ + " Int32Array]" + +#define NJS_FLOAT_TYPED_ARRAY_LIST "[Float32Array,Float64Array]" + + { njs_str("var TypedArray = Object.getPrototypeOf(Uint8Array);" + "[TypedArray.name, TypedArray.length]"), + njs_str("TypedArray,0") }, + + { njs_str("Object.getPrototypeOf(Uint8Array)()"), + njs_str("TypeError: Abstract class TypedArray not directly constructable") }, + + { njs_str("var TypedArray = Object.getPrototypeOf(Uint8Array);" + NJS_TYPED_ARRAY_LIST + ".every(v=>Object.getPrototypeOf(v) === TypedArray)"), + njs_str("true") }, + + { njs_str("var TypedArray = Object.getPrototypeOf(Uint8Array);" + NJS_TYPED_ARRAY_LIST + ".every(v=>Object.getPrototypeOf(v.prototype) === TypedArray.prototype)"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v.prototype.constructor(0); " + " return njs.dump(a) === `${v.name} []`})"), + njs_str("true") }, + + { njs_str("var global = this;" + NJS_TYPED_ARRAY_LIST + ".every(v=>ArrayBuffer.isView(new global[v.name]))"), + njs_str("true") }, + + { njs_str("var global = this;" + NJS_TYPED_ARRAY_LIST + ".every(v=>global[v.name][Symbol.species].name === v.name)"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v({length:2, '0':1, '1':2}); " + " return a[0] == 1 && a[1] == 2 && a.length == 2})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{try{ new v({length:Math.pow(2,53)}) } " + " catch(e) {return e.name == 'InternalError' || e.name == 'RangeError'}})"), + njs_str("true") }, + + { njs_str(NJS_FLOAT_TYPED_ARRAY_LIST + ".every(v=>{var a = new v({length:5, 0: null, 2:42, 3:'7', 4:NaN, 5:Symbol('1')}); " + " return njs.dump(a) === `${v.name} [0,NaN,42,7,NaN]`})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var o = {length:2, '0':1}; Object.defineProperty(o, '1', {get(){throw 'Oops'}});" + " try {new v(o)} catch (e) { return e == 'Oops'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(3); Object.defineProperty(a, '1', {value:1});" + " return njs.dump(a) === `${v.name} [0,1,0]`})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,1,1]); Object.defineProperty(a, '1', {});" + " return njs.dump(a) === `${v.name} [1,1,1]`})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{try {var a = new v([1,1]); Object.defineProperty(a, '1', {configurable:true})} " + " catch (e) { return e.message == 'Cannot redefine property: \"1\"'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{try {var a = new v([1,1]); Object.defineProperty(a, '1', {enumerable:false})} " + " catch (e) { return e.message == 'Cannot redefine property: \"1\"'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{try {var a = new v([1,1]); Object.defineProperty(a, '1', {writable:false})} " + " catch (e) { return e.message == 'Cannot redefine property: \"1\"'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{try {var a = new v([1,1]); Object.defineProperty(a, '1', {get(){return 22}})} " + " catch (e) { return e.message == 'Cannot redefine property: \"1\"'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(3);" + " return [a.hasOwnProperty('1'), a.hasOwnProperty('4')].toString() === 'true,false'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var descs = Object.getOwnPropertyNames(v).sort().toString(); " + " return descs === 'BYTES_PER_ELEMENT,length,name,prototype'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".map(v=>{var a = new ArrayBuffer(8); return (new v(a).length)})"), + njs_str("8,8,8,4,4,2,2,2,1") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".map(v=>{var a = new v(1); a[0]--; return a[0]})"), + njs_str("255,0,-1,65535,-1,4294967295,-1,-1,-1") }, + + { njs_str(NJS_NOT_CANONICAL_INDICES + ".every(v=>{var a = new Uint8Array([1,2]); return a[v] === undefined})"), + njs_str("true") }, + + { njs_str(NJS_NOT_CANONICAL_INDICES + ".map(v=>{var a = new Uint8Array([1,2]); a[v] = 'a'; return a[v]})"), + njs_str("a,,a,a,a,,,,,a") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{v.prototype[10] = 'foo'; return (new v(16))[10] === 0})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{v.prototype[20] = 'foo'; return (new v(16))[20] === undefined})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{v.prototype.foo = 'bar'; return (new v(16)).foo === 'bar'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{v.prototype[-1] = 'foo'; return (new v(8))[-1] === undefined})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".map(v=>v.BYTES_PER_ELEMENT)"), + njs_str("1,1,1,2,2,4,4,4,8") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>v.length === 3)"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(1); return --a[0] == -1})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(4); a.fill(42); return (a[0] === 42 && a.length == 4)})"), + njs_str("true") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(1); a.fill({}); return a[0] === 0})"), + njs_str("true") }, + + { njs_str(NJS_FLOAT_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(1); a.fill({}); return isNaN(a[0])})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); a.fill({valueOf(){return 12}}, 1,2); " + " return (a[0] === 1 && a[1] === 12 && a[2] === 3 && a.length == 3)})"), + njs_str("true") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); a.fill('qq', 1, 2); " + " return (a[0] === 1 && a[1] === 0 && a[2] === 3 && a.length == 3)})"), + njs_str("true") }, + + { njs_str(NJS_FLOAT_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); a.fill('qq', 1, 2); " + " return (a[0] === 1 && isNaN(a[1]) && a[2] === 3 && a.length == 3)})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); var d = Object.getOwnPropertyDescriptors(a)[1];" + " return (d.value === 2 && d.writable && d.enumerable && !d.configurable)})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); return Object.keys(a).toString() === '0,1,2'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); return Object.values(a).toString() === '1,2,3'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); return Object.entries(a).toString() === '0,1,1,2,2,3'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{try {delete (new v(1))[0]} catch (e) { return e.name == 'TypeError'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{return delete (new v(1))[-1]; })"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(8), b = new v(a.buffer); a[0] = 42; return b[0] === 42})"), + njs_str("true") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint8Array([1,2,3]);" + " try { return new v(init.buffer, 0, 2)} catch (e) {return e.name}})"), + njs_str("1,2,1,2,1,2,RangeError,RangeError,RangeError,RangeError") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint8Array([1,2,3]);" + " try { return new v(init.buffer, 1)} catch (e) {return e.name}})"), + njs_str("2,3,2,3,2,3,RangeError,RangeError,RangeError,RangeError") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint32Array([0xaabbccdd]);" + " try { return new v(init.buffer, 0, 2)} catch (e) {return e.name}})"), + njs_str("221,204,221,204,-35,-52,52445,43707,-13091,-21829,RangeError,RangeError") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint32Array([0xaabbccdd]);" + " try { return new v(init.buffer, 1, 2)} catch (e) {return e.name}})"), + njs_str("204,187,204,187,-52,-69,RangeError,RangeError,RangeError,RangeError") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint32Array([0xaabbccdd,0xdeadbeef]);" + " try { return new v(init.buffer, 0, 2)} catch (e) {return e.name}})"), + njs_str("221,204,221,204,-35,-52,52445,43707,-13091,-21829,2864434397,3735928559,-1430532899,-559038737") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var buffer1 = new ArrayBuffer(8 * v.BYTES_PER_ELEMENT);" + " var ta1 = new v(buffer1);" + " var ta2 = new v(ta1.buffer, 4 * v.BYTES_PER_ELEMENT); " + " ta1[5] = 100; ta1[7] = 101;" + " return ta2.toString() === '0,100,0,101'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([42,11]); return a.toString() === '42,11'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(3); a[1] = 42; return a.toString() === '0,42,0'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v(0); return a.toString() === ''})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([42,11]); return a.join('|') === '42|11'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([42,11]); return a.join('α').length === 5})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{return njs.dump(new v()) === `${v.name} []`})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{return njs.dump(new v([42,11])) === `${v.name} [42,11]`})"), + njs_str("true") }, + + { njs_str("var a = new Uint8Array(8); var b = new Uint32Array(a.buffer);" + "a[0] = 0xff; a[1] = 0xff; a[2] = 0xff; a[3] = 0xff; b[0];"), + njs_str("4294967295") }, + + { njs_str("[1,300,-100]" + ".map(v=>{var a = new Uint8Array(1); a[0] = v; return a[0];})"), + njs_str("1,44,156") }, + + { njs_str("[1,300,-100]" + ".map(v=>{var a = new Uint8ClampedArray(1); a[0] = v; return a[0];})"), + njs_str("1,255,0") }, + + { njs_str("[1,300,-100]" + ".map(v=>{var a = new Uint8ClampedArray(1); a.set([v], 0); return a[0];})"), + njs_str("1,255,0") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2,3,4]; var a = new v(4);" + " a.set(init); return a.toString() === '1,2,3,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:4, 0:1,1:2,2:3,3:4}; var a = new v(4);" + " a.set(init); return a.toString() === '1,2,3,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2,3,4]); var a = new v(init);" + " return a.toString() === '1,2,3,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2,3,4]); var a = new v(4);" + " a.set(init); return a.toString() === '1,2,3,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:2, 0:1,1:2}; var a = new v(4);" + " a.set(init,2); return a.toString() === '0,0,1,2'})"), + njs_str("true") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:4, 0:1,1:2,3:4}; var a = new v(4);" + " a.set(init); return a.toString() === '1,2,0,4'})"), + njs_str("true") }, + + { njs_str(NJS_FLOAT_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:4, 0:1,1:2,3:4}; var a = new v(4);" + " a.set(init); return a.toString() === '1,2,NaN,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2,3]; var a = new v(4);" + " a.set(init); return a.toString() === '1,2,3,0'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2,3]); var a = new v(4);" + " a.set(init); return a.toString() === '1,2,3,0'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2,3,4]; var a = new v(4);" + " a.set(init, 0); return a.toString() === '1,2,3,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2]; var a = new v(4);" + " a.set(init, 2); return a.toString() === '0,0,1,2'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2]); var a = new v(4);" + " a.set(init,2); return a.toString() === '0,0,1,2'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2,3,4]; var a = new v(4);" + " try {a.set(init,2)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2,3,4]; var a = new v(4);" + " try {a.set(init,Infinity)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = [1,2,3,4,5]; var a = new v(4);" + " try {a.set(init)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:5}; var a = new v(4);" + " try {a.set(init)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:3}; var a = new v(4);" + " try {a.set(init,2)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = {length:3}; var a = new v(4);" + " try {a.set(init,Infinity)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2,3,4,5]); var a = new v(4);" + " try {a.set(init)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2,3]); var a = new v(4);" + " try {a.set(init,2)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var init = new v([1,2,3]); var a = new v(4);" + " try {a.set(init,Infinity)} catch (e) {return e.name == 'RangeError'};})"), + njs_str("true") }, + + { njs_str("[-1,-1.00001,-Infinity]" + ".every(v=>{ try {(new Uint8Array(10)).set([], v)} catch (ee) {return ee.name === 'RangeError'}})"), + njs_str("true") }, + + { njs_str("[0.1,2.5,{},{'1': '10'},[1000]]" + ".map(v=>{var a = new Uint8Array(1); a.set([v], 0); return a[0]})"), + njs_str("0,2,0,0,232") }, + + { njs_str("[1.0, -1234.0]" + ".map(v=>{var a = new Float32Array(1); a[0] = v; var b = new Uint8Array(a.buffer);" + " return (b[0] << 24 | b[1] << 16| b[2] <<8 | b[3]).toString(16).padStart(8, '0');})"), + njs_str("0000803f,00409ac4") }, + + { njs_str("var a = new ArrayBuffer(0); a.slice(0, 0).byteLength"), + njs_str("0") }, + + { njs_str("var a = new ArrayBuffer(10); a.slice(1).byteLength"), + njs_str("9") }, + + { njs_str("var a = new ArrayBuffer(10); a.slice(1,2).byteLength"), + njs_str("1") }, + + { njs_str("var a = new ArrayBuffer(10); a.slice(0,-1).byteLength"), + njs_str("9") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".map(v=>{var buffer = new ArrayBuffer(8); var view = new v(buffer);" + " view[0] = 511; return new Uint8Array(buffer.slice(0,4))})"), + njs_str("255,0,0,0,255,0,0,0,255,0,0,0,255,1,0,0,255,1,0,0,255,1,0,0,255,1,0,0,0,128,255,67,0,0,0,0") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".map(v=>{var buffer = new ArrayBuffer(8); var view = new v(buffer);" + " view[view.length - 1] = 511; return new Uint8Array(buffer.slice(4))})"), + njs_str("0,0,0,255,0,0,0,255,0,0,0,255,0,0,255,1,0,0,255,1,255,1,0,0,255,1,0,0,0,128,255,67,0,240,127,64") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".map(v=>{var buffer = new ArrayBuffer(8); var view = new v(buffer);" + " view[0] = 511; return new Uint8Array(buffer.slice(0,-4))})"), + njs_str("255,0,0,0,255,0,0,0,255,0,0,0,255,1,0,0,255,1,0,0,255,1,0,0,255,1,0,0,0,128,255,67,0,0,0,0") }, + + { njs_str("var a = new Uint8Array(10); var b = a.slice(1); b.length"), + njs_str("9") }, + + { njs_str("var a = new Uint8Array(10); var b = a.slice(0,9); b.length"), + njs_str("9") }, + + { njs_str("var a = new Uint8Array(10); var b = a.slice(9,10); b.length"), + njs_str("1") }, + + { njs_str("var a = new Uint8Array(10); var b = a.slice(); b.length"), + njs_str("10") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([42]); " + " var r = a.slice();" + " return njs.dump(r) === `${v.name} [42]`;})"), + njs_str("true") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint8Array([1,2,3,4,5,6,7,8]); var view = new v(init.buffer);" + " return view.slice(0,2)})"), + njs_str("1,2,1,2,1,2,513,1027,513,1027,67305985,134678021,67305985,134678021") }, + + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{var init = new Uint8Array([1,2,3,4,5,6,7,8]); var view = new v(init.buffer);" + " return view.slice(0,-2)})"), + njs_str("1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,513,1027,513,1027,,") }, + + { njs_str("var other = new Uint8Array([0xff,0xff,0xff,0xff]);" + NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([42]); " + " a.constructor = {[Symbol.species]: function () {return other;}}; " + " var r = a.slice(0,0);" + " return r == other && r.length == 4 && r[0] == 0xff;})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); " + " a.constructor = {[Symbol.species]: function () {return new v([0xff,0xee]);}}; " + " try {a.slice(0)} catch(e) {return e.name == 'TypeError'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3]); " + " a.constructor = {[Symbol.species]: function () {return new Date();}}; " + " try {a.slice(0)} catch(e) {return e.name == 'TypeError'}})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3,4]); a.copyWithin(2); " + " return a.toString() === '1,2,1,2'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3,4]); a.copyWithin(2,1); " + " return a.toString() === '1,2,2,3'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3,4]); a.copyWithin(2,1,2); " + " return a.toString() === '1,2,2,4'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3,4]); a.copyWithin(-1,1,2); " + " return a.toString() === '1,2,3,2'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3,4]); a.copyWithin(-1,-4,2); " + " return a.toString() === '1,2,3,1'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([1,2,3,4]); a.copyWithin(-1,-2); " + " return a.toString() === '1,2,3,3'})"), + njs_str("true") }, + #if NJS_HAVE_LARGE_STACK { njs_str("var o = Object({length: 3});" "Object.defineProperty(o, '0', {set: function(v){this[0] = 2 * v}});" @@ -17079,6 +17598,88 @@ done: } +static njs_int_t +njs_string_to_index_test(njs_vm_t *vm, njs_opts_t *opts, njs_stat_t *stat) +{ + njs_str_t s, string; + njs_int_t ret; + njs_bool_t success, is_integer_index; + njs_uint_t i; + + static const struct { + njs_value_t value; + njs_str_t expected; + njs_bool_t is_integer_index; + } tests[] = { + { njs_string(" 1"), njs_str("NaN"), 0 }, + { njs_string(""), njs_str("NaN"), 0 }, + { njs_string("+0"), njs_str("NaN"), 0 }, + { njs_string("-"), njs_str("NaN"), 0 }, + + { njs_string("-0"), njs_str("-0"), 0 }, + { njs_value(NJS_NUMBER, 0, -0.0), njs_str("-0"), 1 }, + + { njs_string("-1"), njs_str("-1"), 0 }, + { njs_string("0"), njs_str("0"), 1 }, + { njs_string("0."), njs_str("NaN"), 0 }, + { njs_string("0.0"), njs_str("NaN"), 0 }, + { njs_string("0x1"), njs_str("NaN"), 0 }, + { njs_string("1 "), njs_str("NaN"), 0 }, + { njs_string("1"), njs_str("1"), 1 }, + { njs_string("1."), njs_str("NaN"), 0 }, + { njs_string("1.1"), njs_str("1.1"), 0 }, + { njs_string("100"), njs_str("100"), 1 }, + { njs_string("1a"), njs_str("NaN"), 0 }, + { njs_string("1e+19"), njs_str("NaN"), 0 }, + { njs_string("1e+22"), njs_str("1e+22"), 0 }, + { njs_string("1e22"), njs_str("NaN"), 0 }, + { njs_string("4294967296"), njs_str("4294967296"), 0 }, + }; + + for (i = 0; i < njs_nitems(tests); i++) { + if (njs_is_string(&tests[i].value)) { + njs_set_number(&vm->retval, njs_string_to_index(&tests[i].value)); + + ret = njs_vm_retval_dump(vm, &s, 0); + if (ret != NJS_OK) { + njs_printf("njs_string_to_index_test: " + "njs_vm_retval_dump() failed\n"); + return NJS_ERROR; + } + + success = njs_strstr_eq(&tests[i].expected, &s); + + if (!success) { + njs_string_get(&tests[i].value, &string); + njs_printf("njs_string_to_index_test(\"%V\"):\n" + "expected: \"%V\"\n got: \"%V\"\n", + &string, &tests[i].expected, &s); + + stat->failed++; + continue; + } + } + + is_integer_index = njs_number_is_integer_index(njs_number(&vm->retval), + &tests[i].value); + + if (tests[i].is_integer_index != is_integer_index) { + njs_string_get(&tests[i].value, &string); + njs_printf("njs_string_to_index_test2(\"%V\"):\n" + "expected: %b\n got: %b\n", + &string, tests[i].is_integer_index, is_integer_index); + + stat->failed++; + continue; + } + + stat->passed++; + } + + return NJS_OK; +} + + static njs_int_t njs_api_test(njs_opts_t *opts, njs_stat_t *stat) { @@ -17100,6 +17701,8 @@ njs_api_test(njs_opts_t *opts, njs_stat_t *stat) njs_str("njs_file_dirname_test") }, { njs_chb_test, njs_str("njs_chb_test") }, + { njs_string_to_index_test, + njs_str("njs_string_to_index_test") }, }; vm = NULL; -- 2.47.3