* Add a h_assert_refcount field to duk_heaphdr when assertions are enabled.
* When doing mark-and-sweep, clear h_assert_refcount, perform mark-and-sweep
processing normally, and assert for correct refcounts for objects that
remain in heap_allocated after sweeping. (Refcounts for objects prior to
sweeping won't match those computed via reachability roots.)
* Improve FASTREFS asserts for refcounting and mark-and-sweep.
The refcount field must be chosen so that there's never any wrapping,
otherwise memory unsafe behavior will happen.
For the default configuration a size_t is used for refcount; it's
guaranteed not to wrap because there can't be enough references to
wrap it. For low memory targets it's possible to configure 16-bit
refcounts. This is fine as long as memory pools are small enough
to never contain more than 2^16-1 references.
* Replace the two alternative algorithms with a single one which works for
both desktop and low memory cases.
* Basic algorithm is a hash table with size 2^N, hash mask is simply
(size - 1), e.g. if size is 0x100, mask is 0xFF. duk_hstring has a 'next'
pointer (single linked list) for chaining strings mapping to the same
slot.
* Change plain buffers to inherit from Uint8Array. This affects a lot of
small things like Object.prototype.toString() output, enumeration of plain
buffers, etc. It also changes JSON serialization for plain buffers because
the index properties are enumerable as with Uint8Array instances.
* Disable JSON stringify fastpath for plain buffers for now so that the
virtual index properties get serialized correctly.
* Remove ArrayBuffer non-standard virtual properties.
* Remove DataView non-standard virtual properties.
* Move .byteLength, .byteOffset, .BYTES_PER_ELEMENT, and .buffer into
inherited getters as required in ES6. However, the .length property
remains a virtual own property for now (it too is an inherited getter
in ES6).
* Move ArrayBuffer.allocPlain() and ArrayBuffer.plainOf() to
Uint8Array.allocPlain() and Uint8Array.plainOf() to match the
semantics change for plain buffers.
* Fix Node.js buffer .slice() behavior, the returned Node.js buffer
would have ArrayBuffer.isView == 0 which doesn't match the revised
Node.js behavior (Buffers being Uint8Array instances)
* Reject ArrayBuffers with a view offset/length in Node.js Buffer .slice()
rather than accept such ArrayBuffers without actually respecting the
view offset/length.
* Allow a plain buffer or a lightfunc as a constructor "replacement object"
return value.
Also provide explicit fast / slow (small) variants for fastint downgrade
check: it doesn't make sense to inline the very large check except in the
hot paths of executor and call handling. Elsewhere it's better to save
footprint and thus code cache.
* Rename fastint macros so that e.g. DUK_SET_FASTINT_U32() becomes simply
DUK_SET_U32(). This makes more sense because the macros are also enabled
without fastint support. Right now the macros will just cast e.g. a uint32
to a double and set it to the duk_tval, but that operation can be optimized
even when fastints are not present.
Improve readability by doing the following renames:
* duk_hcompiledfunction -> duk_hcompfunc
* duk_hnativefunction -> duk_hnatfunc
* duk_hbufferobject -> duk_hbufobj
Corresponding renames for all caps defines.
For example, add DUK_TVAL_SET_FASTINT_U32() which, when fastints are disabled,
simply coerces its argument to a double. This reduces the number of ifdefs
required in calling code when the argument would need to be cast anyway.
Reorder tags to accommodate a separate 'unused' tag so that 'undefined' can
become a single tag write (instead of tag + value like booleans). This is
good because 'undefined' values are involved in e.g. value stack resizes and
are performance relevant.
Also reorder tags so that "is heap allocated" check can be a single bit test
instead of a comparison when using non-packed duk_tval. This makes every
DECREF potentially faster because an "is heap allocated" test appears in
every DECREF.
Because "unused" is not intended to appear anywhere in actual use (e.g. as
a value stack value, as a property value, etc), "unused" values will fall
into the default clause of DUK_TAG_xxx switch case statements. Add an assert
to every such default clause that the value is not intended to be "unused".
Remove duk_push_unused() as it should no longer be used. It was only used
by the debugger protocol; refuse an inbound "unused" value in the debugger.
This is not breaking compatibility because there was no legitimate usage for
the debug client sending requests with "unused" values.
These replace the much repeated idiom:
duk_tval tv_tmp;
DUK_TVAL_SET_TVAL(&tv_tmp, tv_dst);
DUK_TVAL_SET_TVAL(tv_dst, tv_src);
DUK_TVAL_INCREF(thr, tv_src);
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
with (e.g.):
DUK_TVAL_SET_TVAL_UPDREF(thr, tv_dst, tv_src);
This reduces line count, and also allows the set-and-update-refcount
sequence to be optimized in detail.
Also remove the NULL check for refcounts: call sites where a NULL target value
(duk_tval * or duk_heaphdr *) may occur must use a NULL-tolerant variant
explicitly. This is for performance and to reduce call site size when the
refcount operations are inlined.
Using inline macros is basically the same as a forced inline when using a
good compiler and the single source file model. Macros make this more
portable, and will allow refcount updates to happen even directly from
Duktape API macros later.
Also remove mostly unused old debug code.
Debug code doesn't have access to 'heap' so it cannot decode pointers.
Cause an #error for now if both debug prints and pointer compression
are enabled at the same time.
Remove duk_debug_hobject.c from make and dist. It was out of date and
not used in practice anymore.
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups