Browse Source

Add prototype dense array fast paths for push/pop

Add minimal fast paths for Array.prototype operations like push() and pop(),
triggered when the 'this' binding is an Array which has its array part still
present.  This is the most common case and user code will expect for example
Array.prototype.push() to be relatively fast.

The main purpose of this commit is to figure out the necessary internal
helpers for fast paths.  More fast paths will be added separately.
pull/584/head
Sami Vaarala 9 years ago
parent
commit
0bead8a285
  1. 4
      src-input/duk_api_internal.h
  2. 201
      src-input/duk_bi_array.c
  3. 3
      src-input/duk_error.h
  4. 7
      src-input/duk_harray.h
  5. 8
      src-input/duk_tval.h

4
src-input/duk_api_internal.h

@ -206,4 +206,8 @@ DUK_INTERNAL_DECL void duk_unpack(duk_context *ctx);
#define DUK_GET_HOBJECT_POSIDX(ctx,idx) \
(DUK_ASSERT_VALID_POSIDX((ctx),(idx)), DUK_TVAL_GET_OBJECT(((duk_hthread *) (ctx))->valstack_bottom + (idx)))
#define DUK_GET_THIS_TVAL_PTR(thr) \
(DUK_ASSERT_EXPR((thr)->valstack_bottom > (thr)->valstack), \
(thr)->valstack_bottom - 1)
#endif /* DUK_API_INTERNAL_H_INCLUDED */

201
src-input/duk_bi_array.c

@ -1,10 +1,13 @@
/*
* Array built-ins
*
* Note that most Array built-ins are intentionally generic and work even
* when the 'this' binding is not an Array instance. To ensure this,
* Array algorithms do not assume "magical" Array behavior for the "length"
* property, for instance.
* Most Array built-ins are intentionally generic in Ecmascript, and are
* intended to work even when the 'this' binding is not an Array instance.
* This Ecmascript feature is also used by much real world code. For this
* reason the implementations here don't assume exotic Array behavior or
* e.g. presence of a .length property. However, some algorithms have a
* fast path for duk_harray backed actual Array instances, enabled when
* footprint is not a concern.
*
* XXX: the "Throw" flag should be set for (almost?) all [[Put]] and
* [[Delete]] operations, but it's currently false throughout. Go through
@ -17,7 +20,6 @@
* the unsigned 32-bit range (E5.1 Section 15.4.5.1 throws a RangeError if so)
* some intermediate values may be above 0xffffffff and this may not be always
* correctly handled now (duk_uint32_t is not enough for all algorithms).
*
* For instance, push() can legitimately write entries beyond length 0xffffffff
* and cause a RangeError only at the end. To do this properly, the current
* push() implementation tracks the array index using a 'double' instead of a
@ -34,11 +36,6 @@
* Both "put" and "define" are used in the E5.1 specification; as a rule,
* "put" is used when modifying an existing array (or a non-array 'this'
* binding) and "define" for setting values into a fresh result array.
*
* Also note that Array instance 'length' should be writable, but not
* enumerable and definitely not configurable: even Duktape code internally
* assumes that an Array instance will always have a 'length' property.
* Preventing deletion of the property is critical.
*/
#include "duk_internal.h"
@ -48,12 +45,19 @@
*/
#define DUK__ARRAY_MID_JOIN_LIMIT 4096
/* Shared entry code for many Array built-ins. Note that length is left
* on stack (it could be popped, but that's not necessary).
/*
* Shared helpers.
*/
/* Shared entry code for many Array built-ins: the 'this' binding is pushed
* on the value stack and object coerced, and the current .length is returned.
* Note that length is left on stack (it could be popped, but that's not
* usually necessary because call handling will clean it up automatically).
*/
DUK_LOCAL duk_uint32_t duk__push_this_obj_len_u32(duk_context *ctx) {
duk_uint32_t len;
/* XXX: push more directly? */
(void) duk_push_this_coercible_to_object(ctx);
DUK_ASSERT_HOBJECT_VALID(duk_get_hobject(ctx, -1));
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
@ -75,6 +79,64 @@ DUK_LOCAL duk_uint32_t duk__push_this_obj_len_u32_limited(duk_context *ctx) {
return ret;
}
#if defined(DUK_USE_ARRAY_FASTPATH)
/* Check if 'this' binding is an Array instance (duk_harray) which satisfies
* a few other guarantees for fast path operation. The fast path doesn't
* need to handle all operations, even for duk_harrays, but must handle a
* significant fraction to improve performance. Return a non-NULL duk_harray
* pointer when all fast path criteria are met, NULL otherwise.
*/
DUK_LOCAL duk_harray *duk__arraypart_fastpath_this(duk_context *ctx) {
duk_hthread *thr;
duk_tval *tv;
duk_hobject *h;
duk_uint_t flags_mask, flags_bits, flags_value;
thr = (duk_hthread *) ctx;
DUK_ASSERT(thr->valstack_bottom > thr->valstack); /* because call in progress */
tv = DUK_GET_THIS_TVAL_PTR(thr);
/* Fast path requires that 'this' is a duk_harray. Read only arrays
* (ROM backed) are also rejected for simplicity.
*/
if (!DUK_TVAL_IS_OBJECT(tv)) {
DUK_DD(DUK_DDPRINT("reject array fast path: not an object"));
return NULL;
}
h = DUK_TVAL_GET_OBJECT(tv);
DUK_ASSERT(h != NULL);
flags_mask = DUK_HOBJECT_FLAG_ARRAY_PART | \
DUK_HOBJECT_FLAG_EXOTIC_ARRAY | \
DUK_HEAPHDR_FLAG_READONLY;
flags_bits = DUK_HOBJECT_FLAG_ARRAY_PART | \
DUK_HOBJECT_FLAG_EXOTIC_ARRAY;
flags_value = DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) h);
if ((flags_value & flags_mask) != flags_bits) {
DUK_DD(DUK_DDPRINT("reject array fast path: object flag check failed"));
return NULL;
}
/* In some cases a duk_harray's 'length' may be larger than the
* current array part allocation. Avoid the fast path in these
* cases, so that all fast path code can safely assume that all
* items in the range [0,length[ are backed by the current array
* part allocation.
*/
if (((duk_harray *) h)->length > DUK_HOBJECT_GET_ASIZE(h)) {
DUK_DD(DUK_DDPRINT("reject array fast path: length > array part size"));
return NULL;
}
/* Guarantees for fast path. */
DUK_ASSERT(h != NULL);
DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(h) == 0 || DUK_HOBJECT_A_GET_BASE(thr->heap, h) != NULL);
DUK_ASSERT(((duk_harray *) h)->length <= DUK_HOBJECT_GET_ASIZE(h));
DUK_DD(DUK_DDPRINT("array fast path allowed for: %!O", (duk_heaphdr *) h));
return (duk_harray *) h;
}
#endif /* DUK_USE_ARRAY_FASTPATH */
/*
* Constructor
*/
@ -339,11 +401,66 @@ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_join_shared(duk_context *ctx) {
* pop(), push()
*/
#if defined(DUK_USE_ARRAY_FASTPATH)
DUK_LOCAL duk_ret_t duk__array_pop_fastpath(duk_context *ctx, duk_harray *h_arr) {
duk_hthread *thr;
duk_tval *tv_arraypart;
duk_tval *tv_val;
duk_uint32_t len;
thr = (duk_hthread *) ctx;
tv_arraypart = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) h_arr);
len = h_arr->length;
if (len <= 0) {
/* nop, return undefined */
return 0;
}
len--;
h_arr->length = len;
/* Fast path doesn't check for an index property inherited from
* Array.prototype. This is quite often acceptable; if not,
* disable fast path.
*/
DUK_ASSERT_VS_SPACE(thr);
tv_val = tv_arraypart + len;
if (DUK_TVAL_IS_UNUSED(tv_val)) {
/* No net refcount change. Value stack already has
* 'undefined' based on value stack init policy.
*/
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top));
DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv_val));
} else {
/* No net refcount change. */
DUK_TVAL_SET_TVAL(thr->valstack_top, tv_val);
DUK_TVAL_SET_UNUSED(tv_val);
}
thr->valstack_top++;
/* XXX: there's no shrink check in the fast path now */
return 1;
}
#endif /* DUK_USE_ARRAY_FASTPATH */
DUK_INTERNAL duk_ret_t duk_bi_array_prototype_pop(duk_context *ctx) {
duk_uint32_t len;
duk_uint32_t idx;
duk_harray *h_arr;
DUK_ASSERT_TOP(ctx, 0);
#if defined(DUK_USE_ARRAY_FASTPATH)
h_arr = duk__arraypart_fastpath_this(ctx);
if (h_arr) {
return duk__array_pop_fastpath(ctx, h_arr);
}
#endif
/* XXX: Merge fastpath check into a related call (push this, coerce length, etc)? */
len = duk__push_this_obj_len_u32(ctx);
if (len == 0) {
duk_push_int(ctx, 0);
@ -359,6 +476,53 @@ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_pop(duk_context *ctx) {
return 1;
}
#if defined(DUK_USE_ARRAY_FASTPATH)
DUK_LOCAL duk_bool_t duk__array_push_fastpath(duk_context *ctx, duk_harray *h_arr) {
duk_hthread *thr;
duk_tval *tv_arraypart;
duk_tval *tv_src;
duk_tval *tv_dst;
duk_uint32_t len;
duk_idx_t i, n;
thr = (duk_hthread *) ctx;
len = h_arr->length;
tv_arraypart = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) h_arr);
n = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
if (DUK_UNLIKELY(len + n < len)) {
DUK_D(DUK_DPRINT("Array.prototype.push() would go beyond 32-bit length, throw"));
return DUK_RET_RANGE_ERROR;
}
if (len + n > DUK_HOBJECT_GET_ASIZE((duk_hobject *) h_arr)) {
/* Array part would need to be extended. Rely on slow path
* for now.
*
* XXX: Rework hobject code a bit and add extend support.
*/
return 0;
}
tv_src = thr->valstack_bottom;
tv_dst = tv_arraypart + len;
for (i = 0; i < n; i++) {
/* No net refcount change; reset value stack values to
* undefined to satisfy value stack init policy.
*/
DUK_TVAL_SET_TVAL(tv_dst, tv_src);
DUK_TVAL_SET_UNDEFINED(tv_src);
tv_src++;
tv_dst++;
}
thr->valstack_top = thr->valstack_bottom;
len += n;
h_arr->length = len;
return 1;
}
#endif /* DUK_USE_ARRAY_FASTPATH */
DUK_INTERNAL duk_ret_t duk_bi_array_prototype_push(duk_context *ctx) {
/* Note: 'this' is not necessarily an Array object. The push()
* algorithm is supposed to work for other kinds of objects too,
@ -368,6 +532,19 @@ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_push(duk_context *ctx) {
duk_uint32_t len;
duk_idx_t i, n;
duk_harray *h_arr;
#if defined(DUK_USE_ARRAY_FASTPATH)
h_arr = duk__arraypart_fastpath_this(ctx);
if (h_arr) {
duk_ret_t rc;
rc = duk__array_push_fastpath(ctx, h_arr);
if (rc != 0) {
return rc;
}
DUK_DD(DUK_DDPRINT("array push() fast path exited, resize case"));
}
#endif
n = duk_get_top(ctx);
len = duk__push_this_obj_len_u32(ctx);

3
src-input/duk_error.h

@ -325,6 +325,9 @@
#define DUK_ASSERT_DOUBLE_IS_NORMALIZED(dval) /* nop */
#endif
#define DUK_ASSERT_VS_SPACE(thr) \
DUK_ASSERT(thr->valstack_top < thr->valstack_end)
/*
* Helper for valstack space
*

7
src-input/duk_harray.h

@ -24,7 +24,12 @@ struct duk_harray {
/* Shared object part. */
duk_hobject obj;
/* Array .length. */
/* Array .length.
*
* At present Array .length may be smaller, equal, or even larger
* than the allocated underlying array part. Fast path code must
* always take this into account carefully.
*/
duk_uint32_t length;
/* Array .length property attributes. The property is always

8
src-input/duk_tval.h

@ -90,14 +90,14 @@ typedef struct {
#if defined(DUK_USE_DOUBLE_ME)
#define DUK__TVAL_SET_LIGHTFUNC(tv,fp,flags) do { \
(tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_LIGHTFUNC) << 16) | \
((duk_uint64_t) (flags)) | \
(((duk_uint64_t) (duk_uint32_t) (fp)) << 32); \
((duk_uint64_t) (flags)) | \
(((duk_uint64_t) (duk_uint32_t) (fp)) << 32); \
} while (0)
#else
#define DUK__TVAL_SET_LIGHTFUNC(tv,fp,flags) do { \
(tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_LIGHTFUNC) << 48) | \
(((duk_uint64_t) (flags)) << 32) | \
((duk_uint64_t) (duk_uint32_t) (fp)); \
(((duk_uint64_t) (flags)) << 32) | \
((duk_uint64_t) (duk_uint32_t) (fp)); \
} while (0)
#endif
#else /* DUK_USE_64BIT_OPS */

Loading…
Cancel
Save