Browse Source

Merge pull request #389 from svaarala/perf-valstack-init-policy

Change value stack init policy (garbage after current top)
pull/397/head
Sami Vaarala 9 years ago
parent
commit
d7ee8df5f0
  1. 4
      RELEASES.rst
  2. 2
      config/config-options/DUK_USE_PREFER_SIZE.yaml
  3. 5
      config/examples/low_memory.yaml
  4. 1
      config/examples/performance_sensitive.yaml
  5. 5
      config/header-snippets/types.h.in
  6. 5
      config/header-snippets/types2.h.in
  7. 2
      config/header-snippets/unsorted_flags.h.in
  8. 1
      config/other-defines/c_types.yaml
  9. 323
      src/duk_api_stack.c
  10. 4
      src/duk_bi_global.c
  11. 2
      src/duk_heap_markandsweep.c
  12. 2
      src/duk_heap_refcount.c
  13. 37
      src/duk_hthread.h
  14. 5
      src/duk_hthread_alloc.c
  15. 5
      src/duk_js_call.c
  16. 9
      src/duk_js_executor.c
  17. 1
      src/duk_tval.h
  18. 0
      tests/perf/test-call-basic-1.js
  19. 0
      tests/perf/test-call-basic-1.lua
  20. 0
      tests/perf/test-call-basic-1.pl
  21. 0
      tests/perf/test-call-basic-1.py
  22. 0
      tests/perf/test-call-basic-1.rb
  23. 34
      tests/perf/test-call-basic-2.js
  24. 40
      tests/perf/test-call-basic-3.js
  25. 47
      tests/perf/test-call-basic-4.js

4
RELEASES.rst

@ -1167,6 +1167,10 @@ Planned
outer function, with the outer function containing a setjmp/longjmp catch
point and the inner function free of setjmp/longjmp (GH-369, GH-370)
* Internal performance improvement: change value stack initialization policy
so that values above current value stack top are set to "undefined" instead
of "unused" to improve call performance (GH-389)
2.0.0 (XXXX-XX-XX)
------------------

2
config/config-options/DUK_USE_PREFER_SIZE.yaml

@ -1,6 +1,6 @@
define: DUK_USE_PREFER_SIZE
introduced: 1.2.0
default: true
default: false
tags:
- lowmemory
description: >

5
config/examples/low_memory.yaml

@ -9,6 +9,7 @@
# need target specific support code
#
DUK_USE_PREFER_SIZE: true
DUK_USE_AUGMENT_ERROR_CREATE: false
DUK_USE_AUGMENT_ERROR_THROW: false
DUK_USE_TRACEBACKS: false
@ -40,3 +41,7 @@ DUK_USE_STRTAB_CHAIN_SIZE: 128
# Consider to reduce code footprint at the expense of more erratic RAM usage
#DUK_USE_REFERENCE_COUNTING: false
#DUK_USE_DOUBLE_LINKED_HEAP: false
# Consider to reduce code footprint at the expense of less safeguards against
# bugs in calling C code
#DUK_USE_VALSTACK_UNSAFE: true

1
config/examples/performance_sensitive.yaml

@ -3,6 +3,7 @@
# You should choose the fastest setjmp/longjmp for your platform.
DUK_USE_PREFER_SIZE: false
DUK_USE_PACKED_TVAL: false # packed duk_tval slower in most cases
DUK_USE_FASTINT: true
DUK_USE_VALSTACK_UNSAFE: true

5
config/header-snippets/types.h.in

@ -475,6 +475,11 @@ typedef duk_int_t duk_idx_t;
#define DUK_IDX_MIN DUK_INT_MIN
#define DUK_IDX_MAX DUK_INT_MAX
/* Unsigned index variant. */
typedef duk_uint_t duk_uidx_t;
#define DUK_UIDX_MIN DUK_UINT_MIN
#define DUK_UIDX_MAX DUK_UINT_MAX
/* Array index values, could be exact 32 bits.
* Currently no need for signed duk_arridx_t.
*/

5
config/header-snippets/types2.h.in

@ -63,6 +63,11 @@ typedef duk_int_t duk_idx_t;
#define DUK_IDX_MIN DUK_INT_MIN
#define DUK_IDX_MAX DUK_INT_MAX
/* Unsigned index variant. */
typedef duk_uint_t duk_uidx_t;
#define DUK_UIDX_MIN DUK_UINT_MIN
#define DUK_UIDX_MAX DUK_UINT_MAX
/* Array index values, could be exact 32 bits.
* Currently no need for signed duk_arridx_t.
*/

2
config/header-snippets/unsorted_flags.h.in

@ -19,7 +19,7 @@
* where a speed-size tradeoff exists (e.g. lookup tables). When it really
* matters, specific use flags may be appropriate.
*/
#define DUK_USE_PREFER_SIZE
#undef DUK_USE_PREFER_SIZE
/* Use a sliding window for lexer; slightly larger footprint, slightly faster. */
#define DUK_USE_LEXER_SLIDING_WINDOW

1
config/other-defines/c_types.yaml

@ -128,6 +128,7 @@
- typedef: duk_small_uint_fast_t
- typedef: duk_bool_t
- typedef: duk_idx_t
- typedef: duk_uidx_t
- typedef: duk_uarridx_t
- typedef: duk_ret_t
- typedef: duk_errcode_t

323
src/duk_api_stack.c

@ -179,7 +179,8 @@ DUK_LOCAL duk_uint_t duk__api_coerce_d2ui(duk_context *ctx, duk_idx_t index, duk
DUK_EXTERNAL duk_idx_t duk_normalize_index(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_idx_t vs_size;
duk_uidx_t vs_size;
duk_uidx_t uindex;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(DUK_INVALID_INDEX < 0);
@ -191,117 +192,110 @@ DUK_EXTERNAL duk_idx_t duk_normalize_index(duk_context *ctx, duk_idx_t index) {
*/
/* Assume value stack sizes (in elements) fits into duk_idx_t. */
vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT(vs_size >= 0);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */
if (index < 0) {
index = vs_size + index;
if (DUK_UNLIKELY(index < 0)) {
/* Also catches index == DUK_INVALID_INDEX: vs_size >= 0
* so that vs_size + DUK_INVALID_INDEX cannot underflow
* and will always be negative.
*/
return DUK_INVALID_INDEX;
}
uindex = vs_size + (duk_uidx_t) index;
} else {
/* since index non-negative */
DUK_ASSERT(index != DUK_INVALID_INDEX);
if (DUK_UNLIKELY(index >= vs_size)) {
return DUK_INVALID_INDEX;
}
uindex = (duk_uidx_t) index;
}
DUK_ASSERT(index >= 0);
DUK_ASSERT(index < vs_size);
return index;
/* DUK_INVALID_INDEX won't be accepted as a valid index. */
DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size);
if (DUK_LIKELY(uindex < vs_size)) {
return (duk_idx_t) uindex;
}
return DUK_INVALID_INDEX;
}
DUK_EXTERNAL duk_idx_t duk_require_normalize_index(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_idx_t vs_size;
duk_uidx_t vs_size;
duk_uidx_t uindex;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(DUK_INVALID_INDEX < 0);
vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT(vs_size >= 0);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */
if (index < 0) {
index = vs_size + index;
if (DUK_UNLIKELY(index < 0)) {
goto invalid_index;
}
uindex = vs_size + (duk_uidx_t) index;
} else {
DUK_ASSERT(index != DUK_INVALID_INDEX);
if (DUK_UNLIKELY(index >= vs_size)) {
goto invalid_index;
}
uindex = (duk_uidx_t) index;
}
DUK_ASSERT(index >= 0);
DUK_ASSERT(index < vs_size);
return index;
/* DUK_INVALID_INDEX won't be accepted as a valid index. */
DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size);
invalid_index:
if (DUK_LIKELY(uindex < vs_size)) {
return (duk_idx_t) uindex;
}
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
return 0; /* unreachable */
}
DUK_INTERNAL duk_tval *duk_get_tval(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_idx_t vs_size;
duk_uidx_t vs_size;
duk_uidx_t uindex;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(DUK_INVALID_INDEX < 0);
vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT(vs_size >= 0);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */
if (index < 0) {
index = vs_size + index;
if (DUK_UNLIKELY(index < 0)) {
return NULL;
}
uindex = vs_size + (duk_uidx_t) index;
} else {
DUK_ASSERT(index != DUK_INVALID_INDEX);
if (DUK_UNLIKELY(index >= vs_size)) {
return NULL;
}
uindex = (duk_uidx_t) index;
}
DUK_ASSERT(index >= 0);
DUK_ASSERT(index < vs_size);
return thr->valstack_bottom + index;
/* DUK_INVALID_INDEX won't be accepted as a valid index. */
DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size);
if (DUK_LIKELY(uindex < vs_size)) {
return thr->valstack_bottom + uindex;
}
return NULL;
}
DUK_INTERNAL duk_tval *duk_require_tval(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_idx_t vs_size;
duk_uidx_t vs_size;
duk_uidx_t uindex;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(DUK_INVALID_INDEX < 0);
vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT(vs_size >= 0);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom);
DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */
/* Use unsigned arithmetic to optimize comparison. */
if (index < 0) {
index = vs_size + index;
if (DUK_UNLIKELY(index < 0)) {
goto invalid_index;
}
uindex = vs_size + (duk_uidx_t) index;
} else {
DUK_ASSERT(index != DUK_INVALID_INDEX);
if (DUK_UNLIKELY(index >= vs_size)) {
goto invalid_index;
}
uindex = (duk_uidx_t) index;
}
DUK_ASSERT(index >= 0);
DUK_ASSERT(index < vs_size);
return thr->valstack_bottom + index;
/* DUK_INVALID_INDEX won't be accepted as a valid index. */
DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size);
invalid_index:
if (DUK_LIKELY(uindex < vs_size)) {
return thr->valstack_bottom + uindex;
}
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
return NULL;
}
@ -338,86 +332,98 @@ DUK_EXTERNAL duk_idx_t duk_get_top(duk_context *ctx) {
return (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
}
/* set stack top within currently allocated range, but don't reallocate */
/* Set stack top within currently allocated range, but don't reallocate.
* This is performance critical especially for call handling, so whenever
* changing, profile and look at generated code.
*/
DUK_EXTERNAL void duk_set_top(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_idx_t vs_size;
duk_idx_t vs_limit;
duk_idx_t count;
duk_uidx_t vs_size;
duk_uidx_t vs_limit;
duk_uidx_t uindex;
duk_tval *tv;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(DUK_INVALID_INDEX < 0);
vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
vs_limit = (duk_idx_t) (thr->valstack_end - thr->valstack_bottom);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
DUK_ASSERT(thr->valstack_end >= thr->valstack_bottom);
vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom);
vs_limit = (duk_uidx_t) (thr->valstack_end - thr->valstack_bottom);
if (index < 0) {
/* Negative indices are always within allocated stack but
* must not go below zero index.
*/
index = vs_size + index;
if (index < 0) {
/* Also catches index == DUK_INVALID_INDEX. */
goto invalid_index;
}
uindex = vs_size + (duk_uidx_t) index;
} else {
/* Positive index can be higher than valstack top but must
* not go above allocated stack (equality is OK).
*/
if (index > vs_limit) {
goto invalid_index;
}
uindex = (duk_uidx_t) index;
}
DUK_ASSERT(index >= 0);
DUK_ASSERT(index <= vs_limit);
if (index >= vs_size) {
/* Stack size increases or stays the same. Fill the new
* entries (if any) with undefined. No pointer stability
* issues here so we can use a running pointer.
*/
/* DUK_INVALID_INDEX won't be accepted as a valid index. */
DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size);
DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_limit);
tv = thr->valstack_top;
count = index - vs_size;
DUK_ASSERT(count >= 0);
while (count > 0) {
/* no need to decref previous or new value */
#if defined(DUK_USE_VALSTACK_UNSAFE)
DUK_ASSERT(uindex <= vs_limit);
#else
if (DUK_UNLIKELY(uindex > vs_limit)) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
return;
}
#endif
DUK_ASSERT(uindex <= vs_limit);
/* Handle change in value stack top. Respect value stack
* initialization policy: 'undefined' above top. Note that
* DECREF may cause a side effect that reallocates valstack,
* so must relookup after DECREF.
*/
if (uindex >= vs_size) {
/* Stack size increases or stays the same. */
#if defined(DUK_USE_ASSERTIONS)
duk_uidx_t count;
count = uindex - vs_size;
while (count != 0) {
count--;
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);
tv++;
tv = thr->valstack_top + count;
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_ACTUAL(tv));
}
thr->valstack_top = tv;
#endif
thr->valstack_top = thr->valstack_bottom + uindex;
} else {
/* Stack size decreases, DECREF entries which are above the
* new top. Each DECREF potentially invalidates valstack
* pointers, so don't hold on to pointers. The valstack top
* must also be updated on every loop in case a GC is triggered.
*/
/* XXX: Here it would be useful to have a DECREF macro which
* doesn't need a NULL check, and does refzero queueing without
* running the refzero algorithm. There would be no pointer
* instability in this case, and code could be inlined. After
* the loop, one call to refzero would be needed.
*/
/* Stack size decreases. */
#if defined(DUK_USE_REFERENCE_COUNTING)
duk_uidx_t count;
count = vs_size - index;
count = vs_size - uindex;
DUK_ASSERT(count > 0);
while (count > 0) {
count--;
tv = --thr->valstack_top; /* tv -> value just before prev top value */
tv = --thr->valstack_top; /* tv -> value just before prev top value; must relookup */
DUK_ASSERT(tv >= thr->valstack_bottom);
DUK_TVAL_SET_UNDEFINED_UNUSED_UPDREF(thr, tv); /* side effects */
/* XXX: fast primitive to set a bunch of values to UNDEFINED_UNUSED */
DUK_TVAL_SET_UNDEFINED_ACTUAL_UPDREF(thr, tv); /* side effects */
}
}
return;
#else /* DUK_USE_REFERENCE_COUNTING */
duk_uidx_t count;
duk_tval *tv_end;
invalid_index:
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
count = vs_size - uindex;
tv = thr->valstack_top;
tv_end = tv - count;
DUK_ASSERT(tv > tv_end);
do {
tv--;
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);
} while (tv != tv_end);
thr->valstack_top = tv_end;
#endif /* DUK_USE_REFERENCE_COUNTING */
}
}
DUK_EXTERNAL duk_idx_t duk_get_top_index(duk_context *ctx) {
@ -483,8 +489,8 @@ DUK_LOCAL duk_bool_t duk__resize_valstack(duk_context *ctx, duk_size_t new_size)
duk_tval *old_valstack_post;
#endif
duk_tval *new_valstack;
duk_tval *p;
duk_size_t new_alloc_size;
duk_tval *p;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(thr != NULL);
@ -549,6 +555,9 @@ DUK_LOCAL duk_bool_t duk__resize_valstack(duk_context *ctx, duk_size_t new_size)
#endif
thr->valstack = new_valstack;
thr->valstack_end = new_valstack + new_size;
#if !defined(DUK_USE_PREFER_SIZE)
thr->valstack_size = new_size;
#endif
thr->valstack_bottom = (duk_tval *) (void *) ((duk_uint8_t *) new_valstack + old_bottom_offset);
thr->valstack_top = (duk_tval *) (void *) ((duk_uint8_t *) new_valstack + old_top_offset);
@ -579,23 +588,23 @@ DUK_LOCAL duk_bool_t duk__resize_valstack(duk_context *ctx, duk_size_t new_size)
(void *) thr->valstack, (void *) thr->valstack_end,
(void *) thr->valstack_bottom, (void *) thr->valstack_top));
/* init newly allocated slots (only) */
/* Init newly allocated slots (only). */
p = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + old_end_offset_post);
while (p < thr->valstack_end) {
/* never executed if new size is smaller */
DUK_TVAL_SET_UNDEFINED_UNUSED(p);
/* Never executed if new size is smaller. */
DUK_TVAL_SET_UNDEFINED_ACTUAL(p);
p++;
}
/* assertion check: we maintain elements above top in known state */
#ifdef DUK_USE_ASSERTIONS
/* Assert for value stack initialization policy. */
#if defined(DUK_USE_ASSERTIONS)
p = thr->valstack_top;
while (p < thr->valstack_end) {
/* everything above old valstack top should be preinitialized now */
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(p));
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_ACTUAL(p));
p++;
}
#endif
return 1;
}
@ -625,7 +634,12 @@ duk_bool_t duk_valstack_resize_raw(duk_context *ctx,
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
#if defined(DUK_USE_PREFER_SIZE)
old_size = (duk_size_t) (thr->valstack_end - thr->valstack);
#else
DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack) == thr->valstack_size);
old_size = thr->valstack_size;
#endif
if (min_new_size <= old_size) {
is_shrink = 1;
@ -901,7 +915,7 @@ DUK_EXTERNAL void duk_replace(duk_context *ctx, duk_idx_t to_index) {
*/
DUK_TVAL_SET_TVAL(&tv_tmp, tv2);
DUK_TVAL_SET_TVAL(tv2, tv1);
DUK_TVAL_SET_UNDEFINED_UNUSED(tv1);
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
thr->valstack_top--;
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
}
@ -955,7 +969,7 @@ DUK_EXTERNAL void duk_remove(duk_context *ctx, duk_idx_t index) {
nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p)); /* Note: 'q' is top-1 */
DUK_MEMMOVE(p, p + 1, nbytes); /* zero size not an issue: pointers are valid */
DUK_TVAL_SET_UNDEFINED_UNUSED(q);
DUK_TVAL_SET_UNDEFINED_ACTUAL(q);
thr->valstack_top--;
#ifdef DUK_USE_REFERENCE_COUNTING
@ -1016,24 +1030,22 @@ DUK_EXTERNAL void duk_xcopymove_raw(duk_context *to_ctx, duk_context *from_ctx,
to_thr->valstack_top = (duk_tval *) (void *) (((duk_uint8_t *) p) + nbytes);
if (is_copy) {
/* incref copies, keep originals */
/* Incref copies, keep originals. */
q = to_thr->valstack_top;
while (p < q) {
DUK_TVAL_INCREF(to_thr, p); /* no side effects */
p++;
}
} else {
/* no net refcount change */
/* No net refcount change. */
p = from_thr->valstack_top;
q = (duk_tval *) (void *) (((duk_uint8_t *) p) - nbytes);
from_thr->valstack_top = q;
/* elements above stack top are kept UNUSED */
while (p > q) {
p--;
DUK_TVAL_SET_UNDEFINED_UNUSED(p);
/* XXX: fast primitive to set a bunch of values to UNDEFINED_UNUSED */
DUK_TVAL_SET_UNDEFINED_ACTUAL(p);
/* XXX: fast primitive to set a bunch of values to UNDEFINED */
}
}
}
@ -2886,13 +2898,16 @@ DUK_INTERNAL void duk_push_unused(duk_context *ctx) {
DUK_EXTERNAL void duk_push_undefined(duk_context *ctx) {
duk_hthread *thr;
duk_tval *tv_slot;
DUK_ASSERT_CTX_VALID(ctx);
thr = (duk_hthread *) ctx;
DUK__CHECK_SPACE();
tv_slot = thr->valstack_top++;
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv_slot);
/* Because value stack init policy is 'undefined above top',
* we don't need to write, just assert.
*/
thr->valstack_top++;
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_ACTUAL(thr->valstack_top - 1));
}
DUK_EXTERNAL void duk_push_null(duk_context *ctx) {
@ -4129,16 +4144,17 @@ DUK_INTERNAL void duk_push_hobject_bidx(duk_context *ctx, duk_small_int_t builti
DUK_EXTERNAL void duk_pop_n(duk_context *ctx, duk_idx_t count) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_tval *tv;
DUK_ASSERT_CTX_VALID(ctx);
if (count < 0) {
if (DUK_UNLIKELY(count < 0)) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_COUNT);
return;
}
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
if ((duk_size_t) (thr->valstack_top - thr->valstack_bottom) < (duk_size_t) count) {
if (DUK_UNLIKELY((duk_size_t) (thr->valstack_top - thr->valstack_bottom) < (duk_size_t) count)) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_POP_TOO_MANY);
}
@ -4152,33 +4168,58 @@ DUK_EXTERNAL void duk_pop_n(duk_context *ctx, duk_idx_t count) {
* instability), inline code.
*/
#ifdef DUK_USE_REFERENCE_COUNTING
while (count > 0) {
duk_tval *tv;
/* XXX: optimize loops */
#if defined(DUK_USE_REFERENCE_COUNTING)
while (count > 0) {
count--;
tv = --thr->valstack_top; /* tv points to element just below prev top */
DUK_ASSERT(tv >= thr->valstack_bottom);
DUK_TVAL_SET_UNDEFINED_UNUSED_UPDREF(thr, tv); /* side effects */
count--;
DUK_TVAL_SET_UNDEFINED_ACTUAL_UPDREF(thr, tv); /* side effects */
}
#else
tv = thr->valstack_top;
while (count > 0) {
duk_tval *tv;
tv = --thr->valstack_top;
DUK_ASSERT(tv >= thr->valstack_bottom);
DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
count--;
tv--;
DUK_ASSERT(tv >= thr->valstack_bottom);
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);
}
thr->valstack_top = tv;
#endif
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
}
/* Popping one element is called so often that when footprint is not an issue,
* compile a specialized function for it.
*/
#if defined(DUK_USE_PREFER_SIZE)
DUK_EXTERNAL void duk_pop(duk_context *ctx) {
DUK_ASSERT_CTX_VALID(ctx);
duk_pop_n(ctx, 1);
}
#else
DUK_EXTERNAL void duk_pop(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_tval *tv;
DUK_ASSERT_CTX_VALID(ctx);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
if (DUK_UNLIKELY(thr->valstack_top == thr->valstack_bottom)) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_POP_TOO_MANY);
}
tv = --thr->valstack_top; /* tv points to element just below prev top */
DUK_ASSERT(tv >= thr->valstack_bottom);
#ifdef DUK_USE_REFERENCE_COUNTING
DUK_TVAL_SET_UNDEFINED_ACTUAL_UPDREF(thr, tv); /* side effects */
#else
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);
#endif
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
}
#endif /* !DUK_USE_PREFER_SIZE */
DUK_EXTERNAL void duk_pop_2(duk_context *ctx) {
DUK_ASSERT_CTX_VALID(ctx);

4
src/duk_bi_global.c

@ -727,6 +727,8 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_print_helper(duk_context *ctx) {
duk_file *f_out;
#endif
DUK_UNREF(thr);
magic = duk_get_current_magic(ctx);
DUK_UNREF(magic);
@ -781,7 +783,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_print_helper(duk_context *ctx) {
p += sz_str;
*p++ = (duk_uint8_t) (i == nargs - 1 ? DUK_ASC_LF : DUK_ASC_SPACE);
}
DUK_ASSERT((const duk_uint8_t *) p == buf + sz_total);
DUK_ASSERT((const duk_uint8_t *) p == buf + sz_buf);
#endif /* DUK_USE_PREFER_SIZE */
} else {
buf = (const duk_uint8_t *) &nl;

2
src/duk_heap_markandsweep.c

@ -105,7 +105,7 @@ DUK_LOCAL void duk__mark_hobject(duk_heap *heap, duk_hobject *h) {
duk_tval *tv;
tv = t->valstack;
while (tv < t->valstack_end) {
while (tv < t->valstack_top) {
duk__mark_tval(heap, tv);
tv++;
}

2
src/duk_heap_refcount.c

@ -117,7 +117,7 @@ DUK_LOCAL void duk__refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h)
duk_tval *tv;
tv = t->valstack;
while (tv < t->valstack_end) {
while (tv < t->valstack_top) {
duk_tval_decref(thr, tv);
tv++;
}

37
src/duk_hthread.h

@ -155,6 +155,13 @@
* diagnose behavior so it's worth checking even when the check is not 100%.
*/
#if defined(DUK_USE_PREFER_SIZE)
#define DUK_ASSERT_CTX_VSSIZE(ctx) /*nop*/
#else
#define DUK_ASSERT_CTX_VSSIZE(ctx) \
DUK_ASSERT((duk_size_t) (((duk_hthread *) (ctx))->valstack_end - ((duk_hthread *) (ctx))->valstack) == \
((duk_hthread *) (ctx))->valstack_size)
#endif
#define DUK_ASSERT_CTX_VALID(ctx) do { \
DUK_ASSERT((ctx) != NULL); \
DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) (ctx)) == DUK_HTYPE_OBJECT); \
@ -165,6 +172,8 @@
DUK_ASSERT(((duk_hthread *) (ctx))->valstack_end >= ((duk_hthread *) (ctx))->valstack); \
DUK_ASSERT(((duk_hthread *) (ctx))->valstack_top >= ((duk_hthread *) (ctx))->valstack); \
DUK_ASSERT(((duk_hthread *) (ctx))->valstack_top >= ((duk_hthread *) (ctx))->valstack_bottom); \
DUK_ASSERT(((duk_hthread *) (ctx))->valstack_end >= ((duk_hthread *) (ctx))->valstack_top); \
DUK_ASSERT_CTX_VSSIZE((ctx)); \
} while (0)
/*
@ -248,46 +257,54 @@ struct duk_hthread {
*/
duk_instr_t **ptr_curr_pc;
/* backpointers */
/* Backpointers. */
duk_heap *heap;
/* current strictness flag: affects API calls */
/* Current strictness flag: affects API calls. */
duk_uint8_t strict;
/* Thread state. */
duk_uint8_t state;
duk_uint8_t unused1;
duk_uint8_t unused2;
/* sanity limits */
/* Sanity limits for stack sizes. */
duk_size_t valstack_max;
duk_size_t callstack_max;
duk_size_t catchstack_max;
/* XXX: valstack, callstack, and catchstack are currently assumed
/* XXX: Valstack, callstack, and catchstack are currently assumed
* to have non-NULL pointers. Relaxing this would not lead to big
* benefits (except perhaps for terminated threads).
*/
/* value stack: these are expressed as pointers for faster stack manipulation */
/* Value stack: these are expressed as pointers for faster stack manipulation.
* [valstack,valstack_top[ is GC-reachable, [valstack_top,valstack_end[ is
* not GC-reachable but kept initialized as 'undefined'.
*/
duk_tval *valstack; /* start of valstack allocation */
duk_tval *valstack_end; /* end of valstack allocation (exclusive) */
duk_tval *valstack_bottom; /* bottom of current frame */
duk_tval *valstack_top; /* top of current frame (exclusive) */
#if !defined(DUK_USE_PREFER_SIZE)
duk_size_t valstack_size; /* cached: valstack_end - valstack (in entries, not bytes) */
#endif
/* call stack */
/* Call stack. [0,callstack_top[ is GC reachable. */
duk_activation *callstack;
duk_size_t callstack_size; /* allocation size */
duk_size_t callstack_top; /* next to use, highest used is top - 1 */
duk_size_t callstack_preventcount; /* number of activation records in callstack preventing a yield */
/* catch stack */
/* Catch stack. [0,catchstack_top[ is GC reachable. */
duk_catcher *catchstack;
duk_size_t catchstack_size; /* allocation size */
duk_size_t catchstack_top; /* next to use, highest used is top - 1 */
/* yield/resume book-keeping */
/* Yield/resume book-keeping. */
duk_hthread *resumer; /* who resumed us (if any) */
/* current compiler state (if any), used for augmenting SyntaxErrors */
/* Current compiler state (if any), used for augmenting SyntaxErrors. */
duk_compiler_ctx *compile_ctx;
#if defined(DUK_USE_INTERRUPT_COUNTER)
@ -311,7 +328,7 @@ struct duk_hthread {
*/
duk_hobject *builtins[DUK_NUM_BUILTINS];
/* convenience copies from heap/vm for faster access */
/* Convenience copies from heap/vm for faster access. */
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t *strs16;
#else

5
src/duk_hthread_alloc.c

@ -31,11 +31,14 @@ DUK_INTERNAL duk_bool_t duk_hthread_init_stacks(duk_heap *heap, duk_hthread *thr
}
DUK_MEMZERO(thr->valstack, alloc_size);
thr->valstack_end = thr->valstack + DUK_VALSTACK_INITIAL_SIZE;
#if !defined(DUK_USE_PREFER_SIZE)
thr->valstack_size = DUK_VALSTACK_INITIAL_SIZE;
#endif
thr->valstack_bottom = thr->valstack;
thr->valstack_top = thr->valstack;
for (i = 0; i < DUK_VALSTACK_INITIAL_SIZE; i++) {
DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->valstack[i]);
DUK_TVAL_SET_UNDEFINED_ACTUAL(&thr->valstack[i]);
}
/* callstack */

5
src/duk_js_call.c

@ -867,7 +867,12 @@ duk_int_t duk_handle_call(duk_hthread *thr,
*/
entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack);
#if defined(DUK_USE_PREFER_SIZE)
entry_valstack_end = (duk_size_t) (thr->valstack_end - thr->valstack);
#else
DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack) == thr->valstack_size);
entry_valstack_end = thr->valstack_size;
#endif
entry_callstack_top = thr->callstack_top;
entry_catchstack_top = thr->catchstack_top;
entry_call_recursion_depth = thr->heap->call_recursion_depth;

9
src/duk_js_executor.c

@ -2261,16 +2261,15 @@ DUK_LOCAL DUK_NOINLINE void duk__js_execute_bytecode_inner(duk_hthread *entry_th
#endif
#if defined(DUK_USE_ASSERTIONS)
/* Quite heavy assert: check that valstack is in correctly
* initialized state. Improper shuffle instructions can
* write beyond valstack_end so this check catches them in
* the act.
/* Quite heavy assert: check valstack policy. Improper
* shuffle instructions can write beyond valstack_top/end
* so this check catches them in the act.
*/
{
duk_tval *tv;
tv = thr->valstack_top;
while (tv != thr->valstack_end) {
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_ACTUAL(tv));
tv++;
}
}

1
src/duk_tval.h

@ -459,6 +459,7 @@ struct duk_tval_struct {
#define DUK_TVAL_IS_OBJECT(tv) ((tv)->t == DUK_TAG_OBJECT)
#define DUK_TVAL_IS_BUFFER(tv) ((tv)->t == DUK_TAG_BUFFER)
/* XXX: rework to make this into a bit test? Matters to refcount code. */
#define DUK_TVAL_IS_HEAP_ALLOCATED(tv) ((tv)->t >= DUK_TAG_STRING)
#if defined(DUK_USE_FASTINT)

0
tests/perf/test-call-basic.js → tests/perf/test-call-basic-1.js

0
tests/perf/test-call-basic.lua → tests/perf/test-call-basic-1.lua

0
tests/perf/test-call-basic.pl → tests/perf/test-call-basic-1.pl

0
tests/perf/test-call-basic.py → tests/perf/test-call-basic-1.py

0
tests/perf/test-call-basic.rb → tests/perf/test-call-basic-1.rb

34
tests/perf/test-call-basic-2.js

@ -0,0 +1,34 @@
/*
* Basic function call performance.
*/
if (typeof print !== 'function') { print = console.log; }
function func() {
return;
}
function test() {
var i;
var f = func; // eliminate slow path lookup from results
for (i = 0; i < 1e7; i++) {
f();
f();
f();
f();
f();
f();
f();
f();
f();
f();
}
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}

40
tests/perf/test-call-basic-3.js

@ -0,0 +1,40 @@
/*
* Basic function call performance.
*/
if (typeof print !== 'function') { print = console.log; }
function func() {
// allocate regs so that meaningful value stack resizes happen
var x0, x1, x2, x3, x4, x5, x6, x7, x8, x9;
var y0, y1, y2, y3, y4, y5, y6, y7, y8, y9;
var z0, z1, z2, z3, z4, z5, z6, z7, z8, z9;
var z9 = 1;
return;
}
function test() {
var i;
var f = func; // eliminate slow path lookup from results
for (i = 0; i < 1e7; i++) {
f();
f();
f();
f();
f();
f();
f();
f();
f();
f();
}
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}

47
tests/perf/test-call-basic-4.js

@ -0,0 +1,47 @@
/*
* Basic function call performance.
*/
if (typeof print !== 'function') { print = console.log; }
function func(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
// allocate regs so that meaningful value stack resizes happen
var x00, x01, x02, x03, x04, x05, x06, x07, x08, x09;
var x10, x11, x12, x13, x14, x15, x16, x17, x18, x19;
var x20, x21, x22, x23, x24, x25, x26, x27, x28, x29;
var x30, x31, x32, x33, x34, x35, x36, x37, x38, x39;
var x40, x41, x42, x43, x44, x45, x46, x47, x48, x49;
var x50, x51, x52, x53, x54, x55, x56, x57, x58, x59;
var x60, x61, x62, x63, x64, x65, x66, x67, x68, x69;
var x70, x71, x72, x73, x74, x75, x76, x77, x78, x79;
var x80, x81, x82, x83, x84, x85, x86, x87, x88, x89;
var x90, x91, x92, x93, x94, x95, x96, x97, x98, x99;
var x99 = 1;
return;
}
function test() {
var i;
var f = func; // eliminate slow path lookup from results
for (i = 0; i < 1e7; i++) {
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}
Loading…
Cancel
Save