* Strings with 0xFF byte prefix are considered special symbols: they have
typeof "symbol" but still mostly behave as strings (e.g. allow ToString)
so that existing code dealing with internal keys, especially inside
Duktape, can work with fewer changes.
* Strings with 0x80 byte prefix are global symbols, e.g. Symbol.for('foo')
creates the byte representatio: 0x80 "foo"
* Strings with 0x81 byte prefix are unique symbols; the 0x81 byte is followed
by the Symbol description, and an internal string component ensuring
uniqueness is separated by a 0xFF byte (which can never appear anywhere in
an extended UTF-8 string). The unique suffix is up to Duktape internals,
currently two 32-bit counters are used. For example:
0x81 "mySymbol" 0xFF "0-17".
* Well-known symbols use the 0x81 prefix but lack a unique suffix, so their
format is 0x81 <description> 0xFF.
* ES6 distinguishes between an undefined symbol description and an empty
string symbol description. This distinction is not currently visible via
Ecmascript bindings but may be visible in the future. Append an extra
0xFF to the unique suffix when the description is undefined, i.e.
0x81 0xFF <unique suffix> 0xFF.
Change dispatch to use an 8-bit main opcode instead of a 6-bit one.
This removes the need for "EXTRA" opcodes and a secondary switch
clause in the executor dispatch loop.
The new opcode layout uses four 8-bit fields: opcode, A, B, C. The
previous reg/const concept which used 9-bit B and C fields, with the
top bit reserved to denote reg vs const, is now implemented by using
four consecutive opcode slots and moving the B and C reg/const flags
into the opcode. For example:
ADD_RR reg(A) <- reg(B) + reg(C)
ADD_CR reg(A) <- const(B) + reg(C)
ADD_RC reg(A) <- reg(B) + const(C)
ADD_CC reg(A) <- const(B) + const(C)
From a footprint standpoint this allows the executor to remain roughly
the same size: four dispatched opcodes (each a function pointer in a
compiled jump table) point to the same case clause handler, which does
the reg/const decision based on an instruction bit test as before.
However, when performance is more important than footprint, each reg/const
case can be handled separately in the executor so that there's no longer
a reg/const check when the opcode executes.
Note that not all opcodes require a reg/const qualifier, so that opcode
space is effectively increased even if reg/const opcodes consume multiple
entries from the opcode table.
Other minor changes:
* Optimize behavior of several opcodes to e.g. avoid unnecessary support
for shuffling/indirection when wider register arguments are now
available.
* Plain buffers still inherit from ArrayBuffer.prototype.
* Plain buffers won't object coerce, so Object(plainBuffer) fails.
* All buffer object related methods throw an error; their function bodies
are essentially empty. Note that this includes bindings such as
String.fromBuffer(), ArrayBuffer.allocPlain(), ArrayBuffer.plainOf(),
and so on. In essence, you can index plain buffers in Ecmascript but
the buffer values must be created via the C API.
* Duktape custom bindings like Duktape.dec('hex', 'deadbeef') still work
and produce plain buffers.
* Remove CSPROP(I) opcode. CSPROP usually leads to three opcodes, e.g.
LDREG + LDCONST + CSPROP. Direct loads for 'base[key]' and 'base' has
the same effect with one opcode shorter bytecode and no need for a
separate CSPROP opcode.
* Remove CSREGI (indirect) opcode, simplify CSREG opcode a bit. Extend
CSREG base register argument to be wider (BC) so that shuffling is not
needed in practice.
* Remove CSVARI (indirect) opcode and handle its equivalent by using explicit
shuffling in the compiler. This removes one opcode, and indirect target
check from CSVAR.
* Remove NEWI and extend base register argument of NEW to be wider (BC) so
that shuffling is not needed in practice.
* Remove CALLI, and split CALL into CALL, TAILCALL, and EVALCALL. This
eliminates the flags field (A) and allows the base register to be wider
(BC), eliminating the need for shuffling in practice.
* Maximum argument count to constructor and ordinary calls drops from 511
to 255 with this change.
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.
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.
An external buffer is similar to a dynamic buffer but the external data
pointer is managed by user code rather than Duktape, and may point to an
arbitrary address. For example, user can build an external buffer which
points to a framebuffer and operate on that framebuffer from Ecmascript
code via the buffer.
Remove support for 'full tval' init. It was never enabled, and there was no
'full tval' support for non-packed duk_tval anyway.
Add fastint marker to Duktape.env.
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
* Fix outstanding FIXME issues for lightfunc semantics.
* Improve API and Ecmascript testcases to match.
* Clarify lightfunc limitations, e.g. finalizer limitations.
* Guide and API documentation changes for lightfuncs.
* Fix compile warning: duk_str_not_object unused.
A lot of changes to add preliminary lightfunc support:
* Add LIGHTFUNC tagged type to duk_tval.h and API.
* Internal changes for preliminary to support lightfuncs in call handling
and other operations (FIXMEs left in obvious places where support is
still missing after this commit)
* Preliminary Ecmascript and API testcases for lightfuncs
Detailed notes:
* Because magic is signed, reading it back involves sign extension which is
quite verbose to do in C. Use macros for reading the magic value and other
bit fields encoded in the flags.
* Function.prototype.bind(): the 'length' property of a bound function now
comes out wrong. We could simply look up the virtual 'length' property
even if h_target is NULL: no extra code and binding is relatively rare in
hot paths. Rewrite more cleanly in any case.
* The use flag DUK_USE_LIGHTFUNC_BUILTINS controls the forced lightfunc
conversion of built-ins. This results in non-compliant built-ins but
significant memory savings in very memory poor environments.
* Reject eval(), Thread.yield/resume as lightfuncs. These functions have
current assertions that they must be called as fully fledged functions.
* Lightfuncs are serialized like ordinary functions for JSON, JX, and JC
by this diff.
* Add 'magic' to activation for lightfuncs. It will be needed for lightweight
functions: we don't have the duk_tval related to the lightfunc, so we must
copy the magic value to the activation when a call is made.
* When lightfuncs are used as property lookup base values, continue property
lookup from the Function.prototype object. This is necessary to allow e.g.
``func.call()`` and ``func.apply()`` to be used.
* Call handling had to be reworked for lightfuncs, especially how bound
function chains are handled. This is a relatively large change but is
necessary to support lightweight functions properly in bound function
resolution.
The current solution is not ideal. The bytecode executor will first try an
ecma-to-ecma call setup which resolves the bound function chain first. If
the final, unbound function is not viable (a native function) the call setup
returns with an error code. The caller will then perform a normal call.
Although bound function resolution has already been done, the normal call
handling code will re-do it (and detect there is nothing to do).
This situation could be avoided by decoupling bound function handling and
effective this binding computation from the actual call setup. The caller
could then to do this prestep first, and only then decide whether to use an
ecma-to-ecma call or an ordinary heavyweight call.
Remove duk__find_nonbound_function as unused.
* Use indirect magic to allow LIGHTFUNCs for Date. Most of the built-in
functions not directly eligible as lightfuncs are the Date built-in methods,
whose magic values contain too much information to fit into the 8-bit magic
of a LIGHTFUNC value.
To work around this, add an array (duk__date_magics[]) containing the
actual control flags needed by the built-ins, and make the Date built-in
magic value an index into this table. With this change Date built-ins are
successfully converted to lightfuncs.
Testcase fixes:
- Whitespace fixes
- Print error for indirect eval error to make diagnosis easier
- Fix error string to match errmsg updated in this branch
These were revealed after fixing DUK_SINGLE_FILE, gcc will complain about
these internal functions being declared, defined, but not used. There were
also a few declared functions that did not exist. Compiled binary size was
reduced by a few kilobytes!
Unused functions must be commented out carefully: some functions might be
used with certain feature options but not by the defaults. These removals
were verified with grep.
There are no call sites for DUK_DEBUG_DUMP_HEAP() so all the functions
in duk_debug_heap.c are unused, even with a debug build.
Out of the checked allocation macros, only DUK_REALLOC_INDIRECT_CHECKED() is
actually in use at the moment. This commit just comments the unused stuff
out, but it might be better to just remove the unused stuff entirely.
e_used is actually used to track the number of entry part entries currently
GC reachable. Some of the entries may be NULL, indicating deleted keys.
The e_used value is also used as the index where the next insertion of a new
property key happens, so e_next is probably a more appropriate name and
doesn't incorrectly imply that the e_used count indicates actually used,
non-NULL key entries.
Lots of minor string format cleanups (casts mainly).
Add a special check to detect NULL pointers with %s/%p formats in debug
logging to avoid calling the platform sprintf() with a NULL value. This
is especially important for %s + NULL, which is not portable and may crash
on some platforms. Debug logging will now format these specially as "NULL"
and avoid calling the platform for formatting, so debug log call sites don't
need to be careful with NULL pointers any more.