diff --git a/RELEASES.rst b/RELEASES.rst index c9d4068e..4582bece 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -2982,7 +2982,8 @@ Planned NOINLINE helpers (GH-1510); duk_harray fast path for internal array unpack for bound functions, .call, and .apply (GH-1525); unsafe internal value stack pops where safe (GH-1583, GH-1584); initial object property - table size estimate for NEWOBJ opcode (object literals) (GH-1596) + table size estimates for NEWOBJ and NEWARR opcodes (object and array + literals) (GH-1596, GH-1597) 3.0.0 (XXXX-XX-XX) ------------------ diff --git a/debugger/duk_opcodes.yaml b/debugger/duk_opcodes.yaml index ca0999ef..8148daa7 100644 --- a/debugger/duk_opcodes.yaml +++ b/debugger/duk_opcodes.yaml @@ -930,6 +930,7 @@ opcodes: - BC_R - name: NEWARR args: + - A_I # array item count init size - BC_R - name: MPUTOBJ args: diff --git a/src-input/duk_hobject.h b/src-input/duk_hobject.h index 780fde97..feab775c 100644 --- a/src-input/duk_hobject.h +++ b/src-input/duk_hobject.h @@ -868,9 +868,14 @@ DUK_INTERNAL_DECL void duk_hobject_realloc_props(duk_hthread *thr, duk_uint32_t new_a_size, duk_uint32_t new_h_size, duk_bool_t abandon_array); -DUK_INTERNAL_DECL void duk_hobject_resize_props(duk_hthread *thr, - duk_hobject *obj, - duk_uint32_t new_e_size); +DUK_INTERNAL_DECL void duk_hobject_resize_entrypart(duk_hthread *thr, + duk_hobject *obj, + duk_uint32_t new_e_size); +#if 0 /*unused*/ +DUK_INTERNAL_DECL void duk_hobject_resize_arraypart(duk_hthread *thr, + duk_hobject *obj, + duk_uint32_t new_a_size); +#endif /* low-level property functions */ DUK_INTERNAL_DECL void duk_hobject_find_existing_entry(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_int_t *e_idx, duk_int_t *h_idx); diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index a111bb3a..8650c18d 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -842,7 +842,6 @@ DUK_INTERNAL void duk_hobject_realloc_props(duk_hthread *thr, DUK_ASSERT(new_h != NULL); /* fill new_h with u32 0xff = UNUSED */ - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL); DUK_ASSERT(new_h_size > 0); DUK_MEMSET(new_h, 0xff, sizeof(duk_uint32_t) * new_h_size); @@ -960,9 +959,9 @@ DUK_INTERNAL void duk_hobject_realloc_props(duk_hthread *thr, * Helpers to resize properties allocation on specific needs. */ -DUK_INTERNAL void duk_hobject_resize_props(duk_hthread *thr, - duk_hobject *obj, - duk_uint32_t new_e_size) { +DUK_INTERNAL void duk_hobject_resize_entrypart(duk_hthread *thr, + duk_hobject *obj, + duk_uint32_t new_e_size) { duk_uint32_t old_e_size; duk_uint32_t new_a_size; duk_uint32_t new_h_size; @@ -984,6 +983,31 @@ DUK_INTERNAL void duk_hobject_resize_props(duk_hthread *thr, duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); } +#if 0 /*unused */ +DUK_INTERNAL void duk_hobject_resize_arraypart(duk_hthread *thr, + duk_hobject *obj, + duk_uint32_t new_a_size) { + duk_uint32_t old_a_size; + duk_uint32_t new_e_size; + duk_uint32_t new_h_size; + + DUK_ASSERT(thr != NULL); + DUK_ASSERT(obj != NULL); + + if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) { + return; + } + old_a_size = DUK_HOBJECT_GET_ASIZE(obj); + if (old_a_size > new_a_size) { + new_a_size = old_a_size; + } + new_e_size = DUK_HOBJECT_GET_ESIZE(obj); + new_h_size = DUK_HOBJECT_GET_HSIZE(obj); + + duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); +} +#endif + /* Grow entry part allocation for one additional entry. */ DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) { duk_uint32_t old_e_used; /* actually used, non-NULL entries */ diff --git a/src-input/duk_js_compiler.c b/src-input/duk_js_compiler.c index bf2482f4..7469507a 100644 --- a/src-input/duk_js_compiler.c +++ b/src-input/duk_js_compiler.c @@ -2818,6 +2818,10 @@ DUK_LOCAL void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *re duk_uarridx_t start_idx; /* start array index of current MPUTARR set */ duk_uarridx_t init_idx; /* last array index explicitly initialized, +1 */ duk_bool_t require_comma; /* next loop requires a comma */ +#if !defined(DUK_USE_PREFER_SIZE) + duk_int_t pc_newarr; + duk_compiler_instr *instr; +#endif /* DUK_TOK_LBRACKET already eaten, current token is right after that */ DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LBRACKET); @@ -2825,6 +2829,9 @@ DUK_LOCAL void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *re max_init_values = DUK__MAX_ARRAY_INIT_VALUES; /* XXX: depend on available temps? */ reg_obj = DUK__ALLOCTEMP(comp_ctx); +#if !defined(DUK_USE_PREFER_SIZE) + pc_newarr = duk__get_current_pc(comp_ctx); +#endif duk__emit_bc(comp_ctx, DUK_OP_NEWARR, reg_obj); /* XXX: patch initial size hint afterwards? */ temp_start = DUK__GETTEMP(comp_ctx); @@ -2924,6 +2931,14 @@ DUK_LOCAL void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *re } } + /* Update initil size for NEWARR, doesn't need to be exact and is + * capped at A field limit. + */ +#if !defined(DUK_USE_PREFER_SIZE) + instr = duk__get_instr_ptr(comp_ctx, pc_newarr); + instr->ins |= DUK_ENC_OP_A(0, curr_idx > DUK_BC_A_MAX ? DUK_BC_A_MAX : curr_idx); +#endif + DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RBRACKET); duk__advance(comp_ctx); @@ -3008,8 +3023,8 @@ DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *r duk_small_uint_t max_init_pairs; /* max # of key-value pairs initialized in one MPUTOBJ set */ duk_bool_t first; /* first value: comma must not precede the value */ duk_bool_t is_set, is_get; /* temps */ - duk_int_t pc_newobj; #if !defined(DUK_USE_PREFER_SIZE) + duk_int_t pc_newobj; duk_compiler_instr *instr; #endif @@ -3022,7 +3037,9 @@ DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *r st.num_pairs = 0; /* number of key/value pairs emitted for current MPUTOBJ set */ st.num_total_pairs = 0; /* number of key/value pairs emitted overall */ +#if !defined(DUK_USE_PREFER_SIZE) pc_newobj = duk__get_current_pc(comp_ctx); +#endif duk__emit_bc(comp_ctx, DUK_OP_NEWOBJ, st.reg_obj); /* @@ -3219,7 +3236,7 @@ DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *r */ #if !defined(DUK_USE_PREFER_SIZE) instr = duk__get_instr_ptr(comp_ctx, pc_newobj); - instr->ins |= DUK_ENC_OP_A(0, st.num_total_pairs > 255 ? 255 : st.num_total_pairs); + instr->ins |= DUK_ENC_OP_A(0, st.num_total_pairs > DUK_BC_A_MAX ? DUK_BC_A_MAX : st.num_total_pairs); #endif DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY); diff --git a/src-input/duk_js_executor.c b/src-input/duk_js_executor.c index 435dd416..e829cfed 100644 --- a/src-input/duk_js_executor.c +++ b/src-input/duk_js_executor.c @@ -4741,8 +4741,19 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread * case DUK_OP_NEWOBJ: { duk_context *ctx = (duk_context *) thr; duk_push_object(ctx); +#if defined(DUK_USE_ASSERTIONS) + { + duk_hobject *h; + h = duk_require_hobject(ctx, -1); + DUK_ASSERT(DUK_HOBJECT_GET_ESIZE(h) == 0); + DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(h) == 0); + DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(h) == 0); + DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(h) == 0); + } +#endif #if !defined(DUK_USE_PREFER_SIZE) - duk_hobject_resize_props(thr, duk_known_hobject(ctx, -1), DUK_DEC_A(ins)); + /* XXX: could do a direct props realloc, but need hash size */ + duk_hobject_resize_entrypart(thr, duk_known_hobject(ctx, -1), DUK_DEC_A(ins)); #endif DUK__REPLACE_TOP_BC_BREAK(); } @@ -4750,6 +4761,28 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread * case DUK_OP_NEWARR: { duk_context *ctx = (duk_context *) thr; duk_push_array(ctx); +#if defined(DUK_USE_ASSERTIONS) + { + duk_hobject *h; + h = duk_require_hobject(ctx, -1); + DUK_ASSERT(DUK_HOBJECT_GET_ESIZE(h) == 0); + DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(h) == 0); + DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(h) == 0); + DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(h) == 0); + DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(h)); + } +#endif +#if !defined(DUK_USE_PREFER_SIZE) + duk_hobject_realloc_props(thr, + duk_known_hobject(ctx, -1), + 0 /*new_e_size*/, + DUK_DEC_A(ins) /*new_a_size*/, + 0 /*new_h_size*/, + 0 /*abandon_array*/); +#if 0 + duk_hobject_resize_arraypart(thr, duk_known_hobject(ctx, -1), DUK_DEC_A(ins)); +#endif +#endif DUK__REPLACE_TOP_BC_BREAK(); } diff --git a/tests/ecmascript/test-dev-array-literal-sizes.js b/tests/ecmascript/test-dev-array-literal-sizes.js new file mode 100644 index 00000000..7fb33a6f --- /dev/null +++ b/tests/ecmascript/test-dev-array-literal-sizes.js @@ -0,0 +1,56 @@ +/* + * Array literals of different sizes + */ + +/*=== +0 +100 +200 +300 +400 +500 +600 +700 +800 +900 +done +===*/ + +function makeTestFunction(count) { + var src = []; + var i; + + src.push('(function () {'); + src.push(' var arr = ['); + for (i = 0; i < count; i++) { + src.push(' "val' + i + '",'); + } + src.push(' ];'); + src.push(' return arr;'); + src.push('})'); + return eval(src.join('\n') + '\n'); +} + +function test() { + var i, j, arr, fn; + + for (i = 0; i < 1000; i++) { + if ((i % 100) == 0) { + print(i); + } + fn = makeTestFunction(i); + for (j = 0; j < 100; j++) { + arr = fn(); + } + if (Object.keys(arr).length !== i) { + throw new Error('failed for i: ' + i); + } + } + print('done'); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/ecmascript/test-dev-object-literal-sizes.js b/tests/ecmascript/test-dev-object-literal-sizes.js new file mode 100644 index 00000000..5622e958 --- /dev/null +++ b/tests/ecmascript/test-dev-object-literal-sizes.js @@ -0,0 +1,56 @@ +/* + * Object literals of different sizes + */ + +/*=== +0 +100 +200 +300 +400 +500 +600 +700 +800 +900 +done +===*/ + +function makeTestFunction(count) { + var src = []; + var i; + + src.push('(function () {'); + src.push(' var obj = {'); + for (i = 0; i < count; i++) { + src.push(' key' + i + ': "val' + i + '",'); + } + src.push(' };'); + src.push(' return obj;'); + src.push('})'); + return eval(src.join('\n') + '\n'); +} + +function test() { + var i, j, obj, fn; + + for (i = 0; i < 1000; i++) { + if ((i % 100) == 0) { + print(i); + } + fn = makeTestFunction(i); + for (j = 0; j < 100; j++) { + obj = fn(); + } + if (Object.keys(obj).length !== i) { + throw new Error('failed for i: ' + i); + } + } + print('done'); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/perf/test-array-literal-100.js b/tests/perf/test-array-literal-100.js new file mode 100644 index 00000000..3ebdd44f --- /dev/null +++ b/tests/perf/test-array-literal-100.js @@ -0,0 +1,40 @@ +/* + * Create Array using a literal + */ + +if (typeof print !== 'function') { print = console.log; } + +function test() { + var arr; + var i; + + for (i = 0; i < 1e6; i++) { + arr = [ 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5', + 'foo', 'bar', 'quux', 'baz', 'quuux', + '1', '2', '3', '4', '5' ]; + } +} + +try { + test(); +} catch (e) { + print(e.stack || e); + throw e; +} diff --git a/tests/perf/test-array-literal.js b/tests/perf/test-array-literal-20.js similarity index 100% rename from tests/perf/test-array-literal.js rename to tests/perf/test-array-literal-20.js diff --git a/tests/perf/test-array-literal-3.js b/tests/perf/test-array-literal-3.js new file mode 100644 index 00000000..83220d24 --- /dev/null +++ b/tests/perf/test-array-literal-3.js @@ -0,0 +1,21 @@ +/* + * Create Array using a literal + */ + +if (typeof print !== 'function') { print = console.log; } + +function test() { + var arr; + var i; + + for (i = 0; i < 1e6; i++) { + arr = [ 'foo', 'bar', 'quux' ]; + } +} + +try { + test(); +} catch (e) { + print(e.stack || e); + throw e; +}