|
|
|
/*
|
|
|
|
* Ecmascript specification algorithm and conversion helpers.
|
|
|
|
*
|
|
|
|
* These helpers encapsulate the primitive Ecmascript operation semantics,
|
|
|
|
* and are used by the bytecode executor and the API (among other places).
|
|
|
|
* Some primitives are only implemented as part of the API and have no
|
|
|
|
* "internal" helper. This is the case when an internal helper would not
|
|
|
|
* really be useful; e.g. the operation is rare, uses value stack heavily,
|
|
|
|
* etc.
|
|
|
|
*
|
|
|
|
* The operation arguments depend on what is required to implement
|
|
|
|
* the operation:
|
|
|
|
*
|
|
|
|
* - If an operation is simple and stateless, and has no side
|
|
|
|
* effects, it won't take an duk_hthread argument and its
|
|
|
|
* arguments may be duk_tval pointers (which are safe as long
|
|
|
|
* as no side effects take place).
|
|
|
|
*
|
|
|
|
* - If complex coercions are required (e.g. a "ToNumber" coercion)
|
|
|
|
* or errors may be thrown, the operation takes an duk_hthread
|
|
|
|
* argument. This also implies that the operation may have
|
|
|
|
* arbitrary side effects, invalidating any duk_tval pointers.
|
|
|
|
*
|
|
|
|
* - For operations with potential side effects, arguments can be
|
|
|
|
* taken in several ways:
|
|
|
|
*
|
|
|
|
* a) as duk_tval pointers, which makes sense if the "common case"
|
|
|
|
* can be resolved without side effects (e.g. coercion); the
|
|
|
|
* arguments are pushed to the valstack for coercion if
|
|
|
|
* necessary
|
|
|
|
*
|
|
|
|
* b) as duk_tval values
|
|
|
|
*
|
|
|
|
* c) implicitly on value stack top
|
|
|
|
*
|
|
|
|
* d) as indices to the value stack
|
|
|
|
*
|
|
|
|
* Future work:
|
|
|
|
*
|
|
|
|
* - Argument styles may not be the most sensible in every case now.
|
|
|
|
*
|
|
|
|
* - In-place coercions might be useful for several operations, if
|
|
|
|
* in-place coercion is OK for the bytecode executor and the API.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "duk_internal.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ToPrimitive() (E5 Section 9.1)
|
|
|
|
*
|
|
|
|
* ==> implemented in the API.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ToBoolean() (E5 Section 9.2)
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_toboolean(duk_tval *tv) {
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv)) {
|
|
|
|
case DUK_TAG_UNDEFINED:
|
|
|
|
case DUK_TAG_NULL:
|
|
|
|
return 0;
|
|
|
|
case DUK_TAG_BOOLEAN:
|
|
|
|
DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv) == 0 || DUK_TVAL_GET_BOOLEAN(tv) == 1);
|
|
|
|
return DUK_TVAL_GET_BOOLEAN(tv);
|
|
|
|
case DUK_TAG_STRING: {
|
|
|
|
/* Symbols ToBoolean() coerce to true, regardless of their
|
|
|
|
* description. This happens with no explicit check because
|
|
|
|
* of the symbol representation byte prefix.
|
|
|
|
*/
|
|
|
|
duk_hstring *h = DUK_TVAL_GET_STRING(tv);
|
|
|
|
DUK_ASSERT(h != NULL);
|
16-bit fields and heap pointer compression work
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
10 years ago
|
|
|
return (DUK_HSTRING_GET_BYTELEN(h) > 0 ? 1 : 0);
|
|
|
|
}
|
|
|
|
case DUK_TAG_OBJECT: {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
case DUK_TAG_BUFFER: {
|
|
|
|
/* Mimic Uint8Array semantics: objects coerce true, regardless
|
|
|
|
* of buffer length (zero or not) or context.
|
|
|
|
*/
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
case DUK_TAG_POINTER: {
|
|
|
|
void *p = DUK_TVAL_GET_POINTER(tv);
|
|
|
|
return (p != NULL ? 1 : 0);
|
|
|
|
}
|
First round of lightfunc changes
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
11 years ago
|
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
case DUK_TAG_FASTINT:
|
|
|
|
if (DUK_TVAL_GET_FASTINT(tv) != 0) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
default: {
|
|
|
|
/* number */
|
|
|
|
duk_double_t d;
|
|
|
|
#if defined(DUK_USE_PREFER_SIZE)
|
|
|
|
int c;
|
|
|
|
#endif
|
|
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv));
|
|
|
|
d = DUK_TVAL_GET_DOUBLE(tv);
|
|
|
|
#if defined(DUK_USE_PREFER_SIZE)
|
|
|
|
c = DUK_FPCLASSIFY((double) d);
|
|
|
|
if (c == DUK_FP_ZERO || c == DUK_FP_NAN) {
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
DUK_ASSERT(duk_double_is_nan_or_zero(d) == 0 || duk_double_is_nan_or_zero(d) == 1);
|
|
|
|
return duk_double_is_nan_or_zero(d) ^ 1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DUK_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ToNumber() (E5 Section 9.3)
|
|
|
|
*
|
|
|
|
* Value to convert must be on stack top, and is popped before exit.
|
|
|
|
*
|
|
|
|
* See: http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
|
|
|
|
* http://www.cs.indiana.edu/~burger/fp/index.html
|
|
|
|
*
|
|
|
|
* Notes on the conversion:
|
|
|
|
*
|
|
|
|
* - There are specific requirements on the accuracy of the conversion
|
|
|
|
* through a "Mathematical Value" (MV), so this conversion is not
|
|
|
|
* trivial.
|
|
|
|
*
|
|
|
|
* - Quick rejects (e.g. based on first char) are difficult because
|
|
|
|
* the grammar allows leading and trailing white space.
|
|
|
|
*
|
|
|
|
* - Quick reject based on string length is difficult even after
|
|
|
|
* accounting for white space; there may be arbitrarily many
|
|
|
|
* decimal digits.
|
|
|
|
*
|
|
|
|
* - Standard grammar allows decimal values ("123"), hex values
|
|
|
|
* ("0x123") and infinities
|
|
|
|
*
|
|
|
|
* - Unlike source code literals, ToNumber() coerces empty strings
|
|
|
|
* and strings with only whitespace to zero (not NaN).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* E5 Section 9.3.1 */
|
|
|
|
DUK_LOCAL duk_double_t duk__tonumber_string_raw(duk_hthread *thr) {
|
|
|
|
duk_small_uint_t s2n_flags;
|
|
|
|
duk_double_t d;
|
|
|
|
|
|
|
|
DUK_ASSERT(duk_is_string(thr, -1));
|
|
|
|
|
|
|
|
/* Quite lenient, e.g. allow empty as zero, but don't allow trailing
|
|
|
|
* garbage.
|
|
|
|
*/
|
|
|
|
s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
|
|
|
|
DUK_S2N_FLAG_ALLOW_EXP |
|
|
|
|
DUK_S2N_FLAG_ALLOW_PLUS |
|
|
|
|
DUK_S2N_FLAG_ALLOW_MINUS |
|
|
|
|
DUK_S2N_FLAG_ALLOW_INF |
|
|
|
|
DUK_S2N_FLAG_ALLOW_FRAC |
|
|
|
|
DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
|
|
|
|
DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
|
|
|
|
DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO |
|
|
|
|
DUK_S2N_FLAG_ALLOW_LEADING_ZERO |
|
|
|
|
DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT |
|
|
|
|
DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT |
|
|
|
|
DUK_S2N_FLAG_ALLOW_AUTO_BIN_INT;
|
|
|
|
|
|
|
|
duk_numconv_parse(thr, 10 /*radix*/, s2n_flags);
|
|
|
|
|
|
|
|
#if defined(DUK_USE_PREFER_SIZE)
|
|
|
|
d = duk_get_number(thr, -1);
|
|
|
|
duk_pop_unsafe(thr);
|
|
|
|
#else
|
|
|
|
thr->valstack_top--;
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(thr->valstack_top));
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_DOUBLE(thr->valstack_top)); /* no fastint conversion in numconv now */
|
|
|
|
DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(thr->valstack_top));
|
|
|
|
d = DUK_TVAL_GET_DOUBLE(thr->valstack_top); /* assumes not a fastint */
|
|
|
|
DUK_TVAL_SET_UNDEFINED(thr->valstack_top);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv) {
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
DUK_ASSERT(tv != NULL);
|
|
|
|
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv)) {
|
|
|
|
case DUK_TAG_UNDEFINED: {
|
|
|
|
/* return a specific NaN (although not strictly necessary) */
|
|
|
|
duk_double_union du;
|
|
|
|
DUK_DBLUNION_SET_NAN(&du);
|
|
|
|
DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
|
|
|
|
return du.d;
|
|
|
|
}
|
|
|
|
case DUK_TAG_NULL: {
|
|
|
|
/* +0.0 */
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
case DUK_TAG_BOOLEAN: {
|
|
|
|
if (DUK_TVAL_IS_BOOLEAN_TRUE(tv)) {
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
case DUK_TAG_STRING: {
|
|
|
|
/* For Symbols ToNumber() is always a TypeError. */
|
|
|
|
duk_hstring *h = DUK_TVAL_GET_STRING(tv);
|
|
|
|
if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
|
|
|
|
DUK_ERROR_TYPE(thr, DUK_STR_CANNOT_NUMBER_COERCE_SYMBOL);
|
|
|
|
}
|
|
|
|
duk_push_hstring(thr, h);
|
|
|
|
return duk__tonumber_string_raw(thr);
|
|
|
|
}
|
|
|
|
case DUK_TAG_BUFFER: /* plain buffer treated like object */
|
|
|
|
case DUK_TAG_OBJECT: {
|
|
|
|
duk_double_t d;
|
|
|
|
duk_push_tval(thr, tv);
|
|
|
|
duk_to_primitive(thr, -1, DUK_HINT_NUMBER); /* 'tv' becomes invalid */
|
|
|
|
|
|
|
|
/* recursive call for a primitive value (guaranteed not to cause second
|
|
|
|
* recursion).
|
|
|
|
*/
|
|
|
|
DUK_ASSERT(duk_get_tval(thr, -1) != NULL);
|
|
|
|
d = duk_js_tonumber(thr, duk_get_tval(thr, -1));
|
|
|
|
|
|
|
|
duk_pop_unsafe(thr);
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
case DUK_TAG_POINTER: {
|
|
|
|
/* Coerce like boolean */
|
|
|
|
void *p = DUK_TVAL_GET_POINTER(tv);
|
|
|
|
return (p != NULL ? 1.0 : 0.0);
|
|
|
|
}
|
First round of lightfunc changes
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
11 years ago
|
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
|
|
/* +(function(){}) -> NaN */
|
|
|
|
return DUK_DOUBLE_NAN;
|
|
|
|
}
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
case DUK_TAG_FASTINT:
|
|
|
|
return (duk_double_t) DUK_TVAL_GET_FASTINT(tv);
|
|
|
|
#endif
|
|
|
|
default: {
|
|
|
|
/* number */
|
|
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv));
|
|
|
|
return DUK_TVAL_GET_DOUBLE(tv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ToInteger() (E5 Section 9.4)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* exposed, used by e.g. duk_bi_date.c */
|
|
|
|
DUK_INTERNAL duk_double_t duk_js_tointeger_number(duk_double_t x) {
|
|
|
|
#if defined(DUK_USE_PREFER_SIZE)
|
|
|
|
duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
|
|
|
|
|
|
if (DUK_UNLIKELY(c == DUK_FP_NAN)) {
|
|
|
|
return 0.0;
|
|
|
|
} else if (DUK_UNLIKELY(c == DUK_FP_INFINITE)) {
|
|
|
|
return x;
|
|
|
|
} else {
|
|
|
|
/* Finite, including neg/pos zero. Neg zero sign must be
|
|
|
|
* preserved.
|
|
|
|
*/
|
|
|
|
return duk_double_trunc_towards_zero(x);
|
|
|
|
}
|
|
|
|
#else /* DUK_USE_PREFER_SIZE */
|
|
|
|
/* NaN and Infinity have the same exponent so it's a cheap
|
|
|
|
* initial check for the rare path.
|
|
|
|
*/
|
|
|
|
if (DUK_UNLIKELY(duk_double_is_nan_or_inf(x))) {
|
|
|
|
if (duk_double_is_nan(x)) {
|
|
|
|
return 0.0;
|
|
|
|
} else {
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return duk_double_trunc_towards_zero(x);
|
|
|
|
}
|
|
|
|
#endif /* DUK_USE_PREFER_SIZE */
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_double_t duk_js_tointeger(duk_hthread *thr, duk_tval *tv) {
|
|
|
|
/* XXX: fastint */
|
|
|
|
duk_double_t d = duk_js_tonumber(thr, tv); /* invalidates tv */
|
|
|
|
return duk_js_tointeger_number(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ToInt32(), ToUint32(), ToUint16() (E5 Sections 9.5, 9.6, 9.7)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* combined algorithm matching E5 Sections 9.5 and 9.6 */
|
|
|
|
DUK_LOCAL duk_double_t duk__toint32_touint32_helper(duk_double_t x, duk_bool_t is_toint32) {
|
|
|
|
#if defined (DUK_USE_PREFER_SIZE)
|
|
|
|
duk_small_int_t c;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined (DUK_USE_PREFER_SIZE)
|
|
|
|
c = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
|
|
if (c == DUK_FP_NAN || c == DUK_FP_ZERO || c == DUK_FP_INFINITE) {
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if (duk_double_is_nan_zero_inf(x)) {
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* x = sign(x) * floor(abs(x)), i.e. truncate towards zero, keep sign */
|
|
|
|
x = duk_double_trunc_towards_zero(x);
|
|
|
|
|
|
|
|
/* NOTE: fmod(x) result sign is same as sign of x, which
|
|
|
|
* differs from what Javascript wants (see Section 9.6).
|
|
|
|
*/
|
|
|
|
|
|
|
|
x = DUK_FMOD(x, DUK_DOUBLE_2TO32); /* -> x in ]-2**32, 2**32[ */
|
|
|
|
|
|
|
|
if (x < 0.0) {
|
|
|
|
x += DUK_DOUBLE_2TO32;
|
|
|
|
}
|
|
|
|
DUK_ASSERT(x >= 0 && x < DUK_DOUBLE_2TO32); /* -> x in [0, 2**32[ */
|
|
|
|
|
|
|
|
if (is_toint32) {
|
|
|
|
if (x >= DUK_DOUBLE_2TO31) {
|
|
|
|
/* x in [2**31, 2**32[ */
|
|
|
|
|
|
|
|
x -= DUK_DOUBLE_2TO32; /* -> x in [-2**31,2**31[ */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv) {
|
|
|
|
duk_double_t d;
|
|
|
|
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
if (DUK_TVAL_IS_FASTINT(tv)) {
|
|
|
|
return DUK_TVAL_GET_FASTINT_I32(tv);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
d = duk_js_tonumber(thr, tv); /* invalidates tv */
|
|
|
|
d = duk__toint32_touint32_helper(d, 1);
|
|
|
|
DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL);
|
|
|
|
DUK_ASSERT(d >= -2147483648.0 && d <= 2147483647.0); /* [-0x80000000,0x7fffffff] */
|
|
|
|
DUK_ASSERT(d == ((duk_double_t) ((duk_int32_t) d))); /* whole, won't clip */
|
|
|
|
return (duk_int32_t) d;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv) {
|
|
|
|
duk_double_t d;
|
|
|
|
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
if (DUK_TVAL_IS_FASTINT(tv)) {
|
|
|
|
return DUK_TVAL_GET_FASTINT_U32(tv);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
d = duk_js_tonumber(thr, tv); /* invalidates tv */
|
|
|
|
d = duk__toint32_touint32_helper(d, 0);
|
|
|
|
DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL);
|
|
|
|
DUK_ASSERT(d >= 0.0 && d <= 4294967295.0); /* [0x00000000, 0xffffffff] */
|
|
|
|
DUK_ASSERT(d == ((duk_double_t) ((duk_uint32_t) d))); /* whole, won't clip */
|
|
|
|
return (duk_uint32_t) d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv) {
|
|
|
|
/* should be a safe way to compute this */
|
|
|
|
return (duk_uint16_t) (duk_js_touint32(thr, tv) & 0x0000ffffU);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ToString() (E5 Section 9.8)
|
|
|
|
* ToObject() (E5 Section 9.9)
|
|
|
|
* CheckObjectCoercible() (E5 Section 9.10)
|
|
|
|
* IsCallable() (E5 Section 9.11)
|
|
|
|
*
|
|
|
|
* ==> implemented in the API.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loose equality, strict equality, and SameValue (E5 Sections 11.9.1, 11.9.4,
|
|
|
|
* 9.12). These have much in common so they can share some helpers.
|
|
|
|
*
|
|
|
|
* Future work notes:
|
|
|
|
*
|
|
|
|
* - Current implementation (and spec definition) has recursion; this should
|
|
|
|
* be fixed if possible.
|
|
|
|
*
|
|
|
|
* - String-to-number coercion should be possible without going through the
|
|
|
|
* value stack (and be more compact) if a shared helper is invoked.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Note that this is the same operation for strict and loose equality:
|
|
|
|
* - E5 Section 11.9.3, step 1.c (loose)
|
|
|
|
* - E5 Section 11.9.6, step 4 (strict)
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_LOCAL duk_bool_t duk__js_equals_number(duk_double_t x, duk_double_t y) {
|
|
|
|
#if defined(DUK_USE_PARANOID_MATH)
|
|
|
|
/* Straightforward algorithm, makes fewer compiler assumptions. */
|
|
|
|
duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
|
|
duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
|
|
|
|
if (cx == DUK_FP_NAN || cy == DUK_FP_NAN) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (x == y) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
#else /* DUK_USE_PARANOID_MATH */
|
|
|
|
/* Better equivalent algorithm. If the compiler is compliant, C and
|
|
|
|
* Ecmascript semantics are identical for this particular comparison.
|
|
|
|
* In particular, NaNs must never compare equal and zeroes must compare
|
|
|
|
* equal regardless of sign. Could also use a macro, but this inlines
|
|
|
|
* already nicely (no difference on gcc, for instance).
|
|
|
|
*/
|
|
|
|
if (x == y) {
|
|
|
|
/* IEEE requires that NaNs compare false */
|
|
|
|
DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN);
|
|
|
|
DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN);
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
/* IEEE requires that zeros compare the same regardless
|
|
|
|
* of their signed, so if both x and y are zeroes, they
|
|
|
|
* are caught above.
|
|
|
|
*/
|
|
|
|
DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* DUK_USE_PARANOID_MATH */
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_LOCAL duk_bool_t duk__js_samevalue_number(duk_double_t x, duk_double_t y) {
|
|
|
|
#if defined(DUK_USE_PARANOID_MATH)
|
|
|
|
duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
|
|
duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
|
|
|
|
|
|
|
|
if (cx == DUK_FP_NAN && cy == DUK_FP_NAN) {
|
|
|
|
/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) {
|
|
|
|
/* Note: cannot assume that a non-zero return value of signbit() would
|
|
|
|
* always be the same -- hence cannot (portably) use something like:
|
|
|
|
*
|
|
|
|
* signbit(x) == signbit(y)
|
|
|
|
*/
|
|
|
|
duk_small_int_t sx = DUK_SIGNBIT(x) ? 1 : 0;
|
|
|
|
duk_small_int_t sy = DUK_SIGNBIT(y) ? 1 : 0;
|
|
|
|
return (sx == sy);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* normal comparison; known:
|
|
|
|
* - both x and y are not NaNs (but one of them can be)
|
|
|
|
* - both x and y are not zero (but one of them can be)
|
|
|
|
* - x and y may be denormal or infinite
|
|
|
|
*/
|
|
|
|
|
|
|
|
return (x == y);
|
|
|
|
#else /* DUK_USE_PARANOID_MATH */
|
|
|
|
duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
|
|
duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
|
|
|
|
|
|
|
|
if (x == y) {
|
|
|
|
/* IEEE requires that NaNs compare false */
|
|
|
|
DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN);
|
|
|
|
DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN);
|
|
|
|
|
|
|
|
/* Using classification has smaller footprint than direct comparison. */
|
|
|
|
if (DUK_UNLIKELY(cx == DUK_FP_ZERO && cy == DUK_FP_ZERO)) {
|
|
|
|
/* Note: cannot assume that a non-zero return value of signbit() would
|
|
|
|
* always be the same -- hence cannot (portably) use something like:
|
|
|
|
*
|
|
|
|
* signbit(x) == signbit(y)
|
|
|
|
*/
|
|
|
|
return duk_double_same_sign(x, y);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
/* IEEE requires that zeros compare the same regardless
|
|
|
|
* of their sign, so if both x and y are zeroes, they
|
|
|
|
* are caught above.
|
|
|
|
*/
|
|
|
|
DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO));
|
|
|
|
|
|
|
|
/* Difference to non-strict/strict comparison is that NaNs compare
|
|
|
|
* equal and signed zero signs matter.
|
|
|
|
*/
|
|
|
|
if (DUK_UNLIKELY(cx == DUK_FP_NAN && cy == DUK_FP_NAN)) {
|
|
|
|
/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* DUK_USE_PARANOID_MATH */
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) {
|
|
|
|
duk_uint_t type_mask_x;
|
|
|
|
duk_uint_t type_mask_y;
|
|
|
|
|
|
|
|
/* If flags != 0 (strict or SameValue), thr can be NULL. For loose
|
|
|
|
* equals comparison it must be != NULL.
|
|
|
|
*/
|
|
|
|
DUK_ASSERT(flags != 0 || thr != NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Same type?
|
|
|
|
*
|
|
|
|
* Note: since number values have no explicit tag in the 8-byte
|
|
|
|
* representation, need the awkward if + switch.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) {
|
|
|
|
if (DUK_TVAL_GET_FASTINT(tv_x) == DUK_TVAL_GET_FASTINT(tv_y)) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
|
|
|
|
duk_double_t d1, d2;
|
|
|
|
|
|
|
|
/* Catches both doubles and cases where only one argument is
|
|
|
|
* a fastint so can't assume a double.
|
|
|
|
*/
|
|
|
|
d1 = DUK_TVAL_GET_NUMBER(tv_x);
|
|
|
|
d2 = DUK_TVAL_GET_NUMBER(tv_y);
|
|
|
|
if (DUK_UNLIKELY((flags & DUK_EQUALS_FLAG_SAMEVALUE) != 0)) {
|
|
|
|
/* SameValue */
|
|
|
|
return duk__js_samevalue_number(d1, d2);
|
|
|
|
} else {
|
|
|
|
/* equals and strict equals */
|
|
|
|
return duk__js_equals_number(d1, d2);
|
|
|
|
}
|
|
|
|
} else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) {
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv_x)) {
|
|
|
|
case DUK_TAG_UNDEFINED:
|
|
|
|
case DUK_TAG_NULL: {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
case DUK_TAG_BOOLEAN: {
|
|
|
|
return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y);
|
|
|
|
}
|
|
|
|
case DUK_TAG_POINTER: {
|
|
|
|
return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y);
|
|
|
|
}
|
|
|
|
case DUK_TAG_STRING:
|
|
|
|
case DUK_TAG_OBJECT: {
|
|
|
|
/* Heap pointer comparison suffices for strings and objects.
|
|
|
|
* Symbols compare equal if they have the same internal
|
|
|
|
* representation; again heap pointer comparison suffices.
|
|
|
|
*/
|
|
|
|
return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
|
|
|
|
}
|
|
|
|
case DUK_TAG_BUFFER: {
|
|
|
|
/* In Duktape 2.x plain buffers mimic Uint8Array objects
|
|
|
|
* so always compare by heap pointer. In Duktape 1.x
|
|
|
|
* strict comparison would compare heap pointers and
|
|
|
|
* non-strict would compare contents.
|
|
|
|
*/
|
|
|
|
return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
|
|
|
|
}
|
First round of lightfunc changes
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
11 years ago
|
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
|
|
/* At least 'magic' has a significant impact on function
|
|
|
|
* identity.
|
|
|
|
*/
|
|
|
|
duk_small_uint_t lf_flags_x;
|
|
|
|
duk_small_uint_t lf_flags_y;
|
|
|
|
duk_c_function func_x;
|
|
|
|
duk_c_function func_y;
|
|
|
|
|
|
|
|
DUK_TVAL_GET_LIGHTFUNC(tv_x, func_x, lf_flags_x);
|
|
|
|
DUK_TVAL_GET_LIGHTFUNC(tv_y, func_y, lf_flags_y);
|
|
|
|
return ((func_x == func_y) && (lf_flags_x == lf_flags_y)) ? 1 : 0;
|
|
|
|
}
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
case DUK_TAG_FASTINT:
|
|
|
|
#endif
|
|
|
|
default: {
|
|
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_x));
|
|
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_y));
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y));
|
|
|
|
DUK_UNREACHABLE();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_ASSERT(flags == 0); /* non-strict equality from here on */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Types are different; various cases for non-strict comparison
|
|
|
|
*
|
|
|
|
* Since comparison is symmetric, we use a "swap trick" to reduce
|
|
|
|
* code size.
|
|
|
|
*/
|
|
|
|
|
|
|
|
type_mask_x = duk_get_type_mask_tval(tv_x);
|
|
|
|
type_mask_y = duk_get_type_mask_tval(tv_y);
|
|
|
|
|
|
|
|
/* Undefined/null are considered equal (e.g. "null == undefined" -> true). */
|
|
|
|
if ((type_mask_x & (DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_NULL)) &&
|
|
|
|
(type_mask_y & (DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_UNDEFINED))) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Number/string -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */
|
|
|
|
if ((type_mask_x & DUK_TYPE_MASK_NUMBER) && (type_mask_y & DUK_TYPE_MASK_STRING)) {
|
|
|
|
if (!DUK_TVAL_STRING_IS_SYMBOL(tv_y)) {
|
|
|
|
duk_double_t d1, d2;
|
|
|
|
d1 = DUK_TVAL_GET_NUMBER(tv_x);
|
|
|
|
d2 = duk_to_number_tval(thr, tv_y);
|
|
|
|
return duk__js_equals_number(d1, d2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((type_mask_x & DUK_TYPE_MASK_STRING) && (type_mask_y & DUK_TYPE_MASK_NUMBER)) {
|
|
|
|
if (!DUK_TVAL_STRING_IS_SYMBOL(tv_x)) {
|
|
|
|
duk_double_t d1, d2;
|
|
|
|
d1 = DUK_TVAL_GET_NUMBER(tv_y);
|
|
|
|
d2 = duk_to_number_tval(thr, tv_x);
|
|
|
|
return duk__js_equals_number(d1, d2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Boolean/any -> coerce boolean to number and try again. If boolean is
|
|
|
|
* compared to a pointer, the final comparison after coercion now always
|
|
|
|
* yields false (as pointer vs. number compares to false), but this is
|
|
|
|
* not special cased.
|
|
|
|
*
|
|
|
|
* ToNumber(bool) is +1.0 or 0.0. Tagged boolean value is always 0 or 1.
|
|
|
|
*/
|
|
|
|
if (type_mask_x & DUK_TYPE_MASK_BOOLEAN) {
|
|
|
|
DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_x) == 0 || DUK_TVAL_GET_BOOLEAN(tv_x) == 1);
|
|
|
|
duk_push_int(thr, DUK_TVAL_GET_BOOLEAN(tv_x));
|
|
|
|
duk_push_tval(thr, tv_y);
|
|
|
|
goto recursive_call;
|
|
|
|
}
|
|
|
|
if (type_mask_y & DUK_TYPE_MASK_BOOLEAN) {
|
|
|
|
DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_y) == 0 || DUK_TVAL_GET_BOOLEAN(tv_y) == 1);
|
|
|
|
duk_push_tval(thr, tv_x);
|
|
|
|
duk_push_int(thr, DUK_TVAL_GET_BOOLEAN(tv_y));
|
|
|
|
goto recursive_call;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* String-number-symbol/object -> coerce object to primitive (apparently without hint), then try again. */
|
|
|
|
if ((type_mask_x & (DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_NUMBER)) &&
|
|
|
|
(type_mask_y & DUK_TYPE_MASK_OBJECT)) {
|
|
|
|
/* No symbol check needed because symbols and strings are accepted. */
|
|
|
|
duk_push_tval(thr, tv_x);
|
|
|
|
duk_push_tval(thr, tv_y);
|
|
|
|
duk_to_primitive(thr, -1, DUK_HINT_NONE); /* apparently no hint? */
|
|
|
|
goto recursive_call;
|
|
|
|
}
|
|
|
|
if ((type_mask_x & DUK_TYPE_MASK_OBJECT) &&
|
|
|
|
(type_mask_y & (DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_NUMBER))) {
|
|
|
|
/* No symbol check needed because symbols and strings are accepted. */
|
|
|
|
duk_push_tval(thr, tv_x);
|
|
|
|
duk_push_tval(thr, tv_y);
|
|
|
|
duk_to_primitive(thr, -2, DUK_HINT_NONE); /* apparently no hint? */
|
|
|
|
goto recursive_call;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Nothing worked -> not equal. */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
recursive_call:
|
|
|
|
/* Shared code path to call the helper again with arguments on stack top. */
|
|
|
|
{
|
|
|
|
duk_bool_t rc;
|
|
|
|
rc = duk_js_equals_helper(thr,
|
|
|
|
DUK_GET_TVAL_NEGIDX(thr, -2),
|
|
|
|
DUK_GET_TVAL_NEGIDX(thr, -1),
|
|
|
|
0 /*flags:nonstrict*/);
|
|
|
|
duk_pop_2_unsafe(thr);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Comparisons (x >= y, x > y, x <= y, x < y)
|
|
|
|
*
|
|
|
|
* E5 Section 11.8.5: implement 'x < y' and then use negate and eval_left_first
|
|
|
|
* flags to get the rest.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* XXX: this should probably just operate on the stack top, because it
|
|
|
|
* needs to push stuff on the stack anyway...
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_small_int_t duk_js_data_compare(const duk_uint8_t *buf1, const duk_uint8_t *buf2, duk_size_t len1, duk_size_t len2) {
|
|
|
|
duk_size_t prefix_len;
|
|
|
|
duk_small_int_t rc;
|
|
|
|
|
|
|
|
prefix_len = (len1 <= len2 ? len1 : len2);
|
|
|
|
|
|
|
|
/* DUK_MEMCMP() is guaranteed to return zero (equal) for zero length
|
|
|
|
* inputs so no zero length check is needed.
|
|
|
|
*/
|
|
|
|
rc = DUK_MEMCMP((const void *) buf1,
|
|
|
|
(const void *) buf2,
|
|
|
|
(size_t) prefix_len);
|
|
|
|
|
|
|
|
if (rc < 0) {
|
|
|
|
return -1;
|
|
|
|
} else if (rc > 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* prefix matches, lengths matter now */
|
|
|
|
if (len1 < len2) {
|
|
|
|
/* e.g. "x" < "xx" */
|
|
|
|
return -1;
|
|
|
|
} else if (len1 > len2) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2) {
|
|
|
|
/*
|
|
|
|
* String comparison (E5 Section 11.8.5, step 4), which
|
|
|
|
* needs to compare codepoint by codepoint.
|
|
|
|
*
|
|
|
|
* However, UTF-8 allows us to use strcmp directly: the shared
|
|
|
|
* prefix will be encoded identically (UTF-8 has unique encoding)
|
|
|
|
* and the first differing character can be compared with a simple
|
|
|
|
* unsigned byte comparison (which strcmp does).
|
|
|
|
*
|
|
|
|
* This will not work properly for non-xutf-8 strings, but this
|
|
|
|
* is not an issue for compliance.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_ASSERT(h1 != NULL);
|
|
|
|
DUK_ASSERT(h2 != NULL);
|
|
|
|
|
|
|
|
return duk_js_data_compare((const duk_uint8_t *) DUK_HSTRING_GET_DATA(h1),
|
|
|
|
(const duk_uint8_t *) DUK_HSTRING_GET_DATA(h2),
|
|
|
|
(duk_size_t) DUK_HSTRING_GET_BYTELEN(h1),
|
|
|
|
(duk_size_t) DUK_HSTRING_GET_BYTELEN(h2));
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0 /* unused */
|
|
|
|
DUK_INTERNAL duk_small_int_t duk_js_buffer_compare(duk_heap *heap, duk_hbuffer *h1, duk_hbuffer *h2) {
|
|
|
|
/* Similar to String comparison. */
|
|
|
|
|
|
|
|
DUK_ASSERT(h1 != NULL);
|
|
|
|
DUK_ASSERT(h2 != NULL);
|
|
|
|
DUK_UNREF(heap);
|
|
|
|
|
|
|
|
return duk_js_data_compare((const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(heap, h1),
|
|
|
|
(const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(heap, h2),
|
|
|
|
(duk_size_t) DUK_HBUFFER_GET_SIZE(h1),
|
|
|
|
(duk_size_t) DUK_HBUFFER_GET_SIZE(h2));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
DUK_LOCAL duk_bool_t duk__compare_fastint(duk_bool_t retval, duk_int64_t v1, duk_int64_t v2) {
|
|
|
|
DUK_ASSERT(retval == 0 || retval == 1);
|
|
|
|
if (v1 < v2) {
|
|
|
|
return retval ^ 1;
|
|
|
|
} else {
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(DUK_USE_PARANOID_MATH)
|
|
|
|
DUK_LOCAL duk_bool_t duk__compare_number(duk_bool_t retval, duk_double_t d1, duk_double_t d2) {
|
|
|
|
duk_small_int_t c1, s1, c2, s2;
|
|
|
|
|
|
|
|
DUK_ASSERT(retval == 0 || retval == 1);
|
|
|
|
c1 = (duk_small_int_t) DUK_FPCLASSIFY(d1);
|
|
|
|
s1 = (duk_small_int_t) DUK_SIGNBIT(d1);
|
|
|
|
c2 = (duk_small_int_t) DUK_FPCLASSIFY(d2);
|
|
|
|
s2 = (duk_small_int_t) DUK_SIGNBIT(d2);
|
|
|
|
|
|
|
|
if (c1 == DUK_FP_NAN || c2 == DUK_FP_NAN) {
|
|
|
|
return 0; /* Always false, regardless of negation. */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c1 == DUK_FP_ZERO && c2 == DUK_FP_ZERO) {
|
|
|
|
/* For all combinations: +0 < +0, +0 < -0, -0 < +0, -0 < -0,
|
|
|
|
* steps e, f, and g.
|
|
|
|
*/
|
|
|
|
return retval; /* false */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d1 == d2) {
|
|
|
|
return retval; /* false */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c1 == DUK_FP_INFINITE && s1 == 0) {
|
|
|
|
/* x == +Infinity */
|
|
|
|
return retval; /* false */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c2 == DUK_FP_INFINITE && s2 == 0) {
|
|
|
|
/* y == +Infinity */
|
|
|
|
return retval ^ 1; /* true */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c2 == DUK_FP_INFINITE && s2 != 0) {
|
|
|
|
/* y == -Infinity */
|
|
|
|
return retval; /* false */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c1 == DUK_FP_INFINITE && s1 != 0) {
|
|
|
|
/* x == -Infinity */
|
|
|
|
return retval ^ 1; /* true */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d1 < d2) {
|
|
|
|
return retval ^ 1; /* true */
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval; /* false */
|
|
|
|
}
|
|
|
|
#else /* DUK_USE_PARANOID_MATH */
|
|
|
|
DUK_LOCAL duk_bool_t duk__compare_number(duk_bool_t retval, duk_double_t d1, duk_double_t d2) {
|
|
|
|
/* This comparison tree relies doesn't match the exact steps in
|
|
|
|
* E5 Section 11.8.5 but should produce the same results. The
|
|
|
|
* steps rely on exact IEEE semantics for NaNs, etc.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_ASSERT(retval == 0 || retval == 1);
|
|
|
|
if (d1 < d2) {
|
|
|
|
/* In no case should both (d1 < d2) and (d2 < d1) be true.
|
|
|
|
* It's possible that neither is true though, and that's
|
|
|
|
* handled below.
|
|
|
|
*/
|
|
|
|
DUK_ASSERT(!(d2 < d1));
|
|
|
|
|
|
|
|
/* - d1 < d2, both d1/d2 are normals (not Infinity, not NaN)
|
|
|
|
* - d2 is +Infinity, d1 != +Infinity and NaN
|
|
|
|
* - d1 is -Infinity, d2 != -Infinity and NaN
|
|
|
|
*/
|
|
|
|
return retval ^ 1;
|
|
|
|
} else {
|
|
|
|
if (d2 < d1) {
|
|
|
|
/* - !(d1 < d2), both d1/d2 are normals (not Infinity, not NaN)
|
|
|
|
* - d1 is +Infinity, d2 != +Infinity and NaN
|
|
|
|
* - d2 is -Infinity, d1 != -Infinity and NaN
|
|
|
|
*/
|
|
|
|
return retval;
|
|
|
|
} else {
|
|
|
|
/* - d1 and/or d2 is NaN
|
|
|
|
* - d1 and d2 are both +/- 0
|
|
|
|
* - d1 == d2 (including infinities)
|
|
|
|
*/
|
|
|
|
if (duk_double_is_nan(d1) || duk_double_is_nan(d2)) {
|
|
|
|
/* Note: undefined from Section 11.8.5 always
|
|
|
|
* results in false return (see e.g. Section
|
|
|
|
* 11.8.3) - hence special treatment here.
|
|
|
|
*/
|
|
|
|
return 0; /* zero regardless of negation */
|
|
|
|
} else {
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* DUK_USE_PARANOID_MATH */
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) {
|
|
|
|
duk_double_t d1, d2;
|
|
|
|
duk_small_int_t rc;
|
|
|
|
duk_bool_t retval;
|
|
|
|
|
|
|
|
DUK_ASSERT(DUK_COMPARE_FLAG_NEGATE == 1); /* Rely on this flag being lowest. */
|
|
|
|
retval = flags & DUK_COMPARE_FLAG_NEGATE;
|
|
|
|
DUK_ASSERT(retval == 0 || retval == 1);
|
|
|
|
|
|
|
|
/* Fast path for fastints */
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
if (DUK_LIKELY(DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y))) {
|
|
|
|
return duk__compare_fastint(retval,
|
|
|
|
DUK_TVAL_GET_FASTINT(tv_x),
|
|
|
|
DUK_TVAL_GET_FASTINT(tv_y));
|
|
|
|
}
|
|
|
|
#endif /* DUK_USE_FASTINT */
|
|
|
|
|
|
|
|
/* Fast path for numbers (one of which may be a fastint) */
|
|
|
|
#if !defined(DUK_USE_PREFER_SIZE)
|
|
|
|
if (DUK_LIKELY(DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y))) {
|
|
|
|
return duk__compare_number(retval,
|
|
|
|
DUK_TVAL_GET_NUMBER(tv_x),
|
|
|
|
DUK_TVAL_GET_NUMBER(tv_y));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Slow path */
|
|
|
|
|
|
|
|
duk_push_tval(thr, tv_x);
|
|
|
|
duk_push_tval(thr, tv_y);
|
|
|
|
|
|
|
|
if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) {
|
|
|
|
duk_to_primitive(thr, -2, DUK_HINT_NUMBER);
|
|
|
|
duk_to_primitive(thr, -1, DUK_HINT_NUMBER);
|
|
|
|
} else {
|
|
|
|
duk_to_primitive(thr, -1, DUK_HINT_NUMBER);
|
|
|
|
duk_to_primitive(thr, -2, DUK_HINT_NUMBER);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Note: reuse variables */
|
|
|
|
tv_x = DUK_GET_TVAL_NEGIDX(thr, -2);
|
|
|
|
tv_y = DUK_GET_TVAL_NEGIDX(thr, -1);
|
|
|
|
|
|
|
|
if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
|
|
|
|
duk_hstring *h1 = DUK_TVAL_GET_STRING(tv_x);
|
|
|
|
duk_hstring *h2 = DUK_TVAL_GET_STRING(tv_y);
|
|
|
|
DUK_ASSERT(h1 != NULL);
|
|
|
|
DUK_ASSERT(h2 != NULL);
|
|
|
|
|
|
|
|
if (DUK_LIKELY(!DUK_HSTRING_HAS_SYMBOL(h1) && !DUK_HSTRING_HAS_SYMBOL(h2))) {
|
|
|
|
rc = duk_js_string_compare(h1, h2);
|
|
|
|
duk_pop_2_unsafe(thr);
|
|
|
|
if (rc < 0) {
|
|
|
|
return retval ^ 1;
|
|
|
|
} else {
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* One or both are Symbols: fall through to handle in the
|
|
|
|
* generic path. Concretely, ToNumber() will fail.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ordering should not matter (E5 Section 11.8.5, step 3.a). */
|
|
|
|
#if 0
|
|
|
|
if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) {
|
|
|
|
d1 = duk_to_number_m2(thr);
|
|
|
|
d2 = duk_to_number_m1(thr);
|
|
|
|
} else {
|
|
|
|
d2 = duk_to_number_m1(thr);
|
|
|
|
d1 = duk_to_number_m2(thr);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
d1 = duk_to_number_m2(thr);
|
|
|
|
d2 = duk_to_number_m1(thr);
|
|
|
|
|
|
|
|
/* We want to duk_pop_2_unsafe(thr); because the values are numbers
|
|
|
|
* no decref check is needed.
|
|
|
|
*/
|
|
|
|
#if defined(DUK_USE_PREFER_SIZE)
|
|
|
|
duk_pop_2_nodecref_unsafe(thr);
|
|
|
|
#else
|
|
|
|
DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(duk_get_tval(thr, -2)));
|
|
|
|
DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(duk_get_tval(thr, -1)));
|
|
|
|
DUK_ASSERT(duk_get_top(thr) >= 2);
|
|
|
|
thr->valstack_top -= 2;
|
|
|
|
tv_x = thr->valstack_top;
|
|
|
|
tv_y = tv_x + 1;
|
|
|
|
DUK_TVAL_SET_UNDEFINED(tv_x); /* Value stack policy */
|
|
|
|
DUK_TVAL_SET_UNDEFINED(tv_y);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return duk__compare_number(retval, d1, d2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* instanceof
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* E5 Section 11.8.6 describes the main algorithm, which uses
|
|
|
|
* [[HasInstance]]. [[HasInstance]] is defined for only
|
|
|
|
* function objects:
|
|
|
|
*
|
|
|
|
* - Normal functions:
|
|
|
|
* E5 Section 15.3.5.3
|
|
|
|
* - Functions established with Function.prototype.bind():
|
|
|
|
* E5 Section 15.3.4.5.3
|
|
|
|
*
|
|
|
|
* For other objects, a TypeError is thrown.
|
|
|
|
*
|
|
|
|
* Limited Proxy support: don't support 'getPrototypeOf' trap but
|
|
|
|
* continue lookup in Proxy target if the value is a Proxy.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
|
|
|
|
duk_hobject *func;
|
|
|
|
duk_hobject *val;
|
|
|
|
duk_hobject *proto;
|
|
|
|
duk_tval *tv;
|
|
|
|
duk_bool_t skip_first;
|
|
|
|
duk_uint_t sanity;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the values onto the stack first. It would be possible to cover
|
|
|
|
* some normal cases without resorting to the value stack.
|
|
|
|
*
|
|
|
|
* The right hand side could be a light function (as they generally
|
|
|
|
* behave like objects). Light functions never have a 'prototype'
|
|
|
|
* property so E5.1 Section 15.3.5.3 step 3 always throws a TypeError.
|
|
|
|
* Using duk_require_hobject() is thus correct (except for error msg).
|
|
|
|
*/
|
|
|
|
|
|
|
|
duk_push_tval(thr, tv_x);
|
|
|
|
duk_push_tval(thr, tv_y);
|
|
|
|
func = duk_require_hobject(thr, -1);
|
|
|
|
DUK_ASSERT(func != NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For bound objects, [[HasInstance]] just calls the target function
|
|
|
|
* [[HasInstance]]. If that is again a bound object, repeat until
|
|
|
|
* we find a non-bound Function object.
|
|
|
|
*
|
|
|
|
* The bound function chain is now "collapsed" so there can be only
|
|
|
|
* one bound function in the chain.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!DUK_HOBJECT_IS_CALLABLE(func)) {
|
|
|
|
/*
|
|
|
|
* Note: of native Ecmascript objects, only Function instances
|
|
|
|
* have a [[HasInstance]] internal property. Custom objects might
|
|
|
|
* also have it, but not in current implementation.
|
|
|
|
*
|
|
|
|
* XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF?
|
|
|
|
*/
|
|
|
|
DUK_ERROR_TYPE(thr, "invalid instanceof rval");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DUK_HOBJECT_HAS_BOUNDFUNC(func)) {
|
|
|
|
duk_push_tval(thr, &((duk_hboundfunc *) func)->target);
|
|
|
|
duk_replace(thr, -2);
|
|
|
|
func = duk_require_hobject(thr, -1); /* lightfunc throws */
|
|
|
|
|
|
|
|
/* Rely on Function.prototype.bind() never creating bound
|
|
|
|
* functions whose target is not proper.
|
|
|
|
*/
|
|
|
|
DUK_ASSERT(func != NULL);
|
|
|
|
DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'func' is now a non-bound object which supports [[HasInstance]]
|
|
|
|
* (which here just means DUK_HOBJECT_FLAG_CALLABLE). Move on
|
|
|
|
* to execute E5 Section 15.3.5.3.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_ASSERT(func != NULL);
|
|
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(func));
|
|
|
|
DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func));
|
|
|
|
|
|
|
|
/* [ ... lval rval(func) ] */
|
|
|
|
|
|
|
|
/* For lightfuncs, buffers, and pointers start the comparison directly
|
|
|
|
* from the virtual prototype object.
|
|
|
|
*/
|
|
|
|
skip_first = 0;
|
|
|
|
tv = DUK_GET_TVAL_NEGIDX(thr, -2);
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv)) {
|
|
|
|
case DUK_TAG_LIGHTFUNC:
|
|
|
|
val = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
|
|
break;
|
|
|
|
case DUK_TAG_BUFFER:
|
|
|
|
val = thr->builtins[DUK_BIDX_UINT8ARRAY_PROTOTYPE];
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
|
|
break;
|
|
|
|
case DUK_TAG_POINTER:
|
|
|
|
val = thr->builtins[DUK_BIDX_POINTER_PROTOTYPE];
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
|
|
break;
|
|
|
|
case DUK_TAG_OBJECT:
|
|
|
|
skip_first = 1; /* Ignore object itself on first round. */
|
|
|
|
val = DUK_TVAL_GET_OBJECT(tv);
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto pop_and_false;
|
|
|
|
}
|
|
|
|
DUK_ASSERT(val != NULL); /* Loop doesn't actually rely on this. */
|
|
|
|
|
|
|
|
duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_PROTOTYPE); /* -> [ ... lval rval rval.prototype ] */
|
|
|
|
proto = duk_require_hobject(thr, -1);
|
|
|
|
duk_pop_unsafe(thr); /* -> [ ... lval rval ] */
|
|
|
|
|
|
|
|
sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
|
|
|
|
do {
|
|
|
|
/*
|
|
|
|
* Note: prototype chain is followed BEFORE first comparison. This
|
|
|
|
* means that the instanceof lval is never itself compared to the
|
|
|
|
* rval.prototype property. This is apparently intentional, see E5
|
|
|
|
* Section 15.3.5.3, step 4.a.
|
|
|
|
*
|
|
|
|
* Also note:
|
|
|
|
*
|
|
|
|
* js> (function() {}) instanceof Function
|
|
|
|
* true
|
|
|
|
* js> Function instanceof Function
|
|
|
|
* true
|
|
|
|
*
|
|
|
|
* For the latter, h_proto will be Function.prototype, which is the
|
|
|
|
* built-in Function prototype. Because Function.[[Prototype]] is
|
|
|
|
* also the built-in Function prototype, the result is true.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!val) {
|
|
|
|
goto pop_and_false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
|
|
#if defined(DUK_USE_ES6_PROXY)
|
|
|
|
val = duk_hobject_resolve_proxy_target(val);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (skip_first) {
|
|
|
|
skip_first = 0;
|
|
|
|
} else if (val == proto) {
|
|
|
|
goto pop_and_true;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
|
|
val = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, val);
|
|
|
|
} while (--sanity > 0);
|
|
|
|
|
Use DUK_HOBJECT_FLAG_HAVE_FINALIZER for checks
One bottleneck in refzero and mark-and-sweep handling is checking whether an
object has an own or inherited _Finalizer property. This check walked the
prototype chain and did a property lookup for every object. Because a
finalizer is usually not present, the prototype chain would almost always be
walked to completion.
Improve this behavior by:
* Adding a DUK_HOBJECT_FLAG_HAVE_FINALIZER flag. The flag is set when the
object has an own _Finalizer property with a callable value, and cleared
otherwise. The flag is *only* set by duk_set_finalizer(), so any other
means of changing the internal _Finalizer property will leave the flag out
of sync (which causes a finalizer run to be skipped).
* Adding duk_hobject_has_finalizer_fast() which checks for finalizer existence
by walking the prototype chain, but only checking the flag, not the property
table.
* Use the fast finalizer check in refzero and mark-and-sweep.
Out-of sync cases:
* If the flag is set but there is no actual finalizer, the object will go
through finalizer processing when garbage collecting. This is harmless:
the finalizer call will fail and the object will be garbage collected, but
with some potential delay (especially for mark-and-sweep).
* If the flag is cleared but there is an actual finalizer, the finalizer will
be ignored.
Related changes:
* When duk_dump_function() is called, zero DUK_HOBJECT_FLAG_HAVE_FINALIZER on
serialization, so it won't be set when the function is loaded back. If this
is not done, the loaded function will (harmlessly) go through finalizer
processing when garbage collected.
* Update debugger artificial properties to include "have_finalizer" flag.
Other changes:
* A few DUK_UNLIKELY() attributes for prototype sanity limits which are
almost never hit.
8 years ago
|
|
|
if (DUK_UNLIKELY(sanity == 0)) {
|
|
|
|
DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
|
|
|
|
}
|
|
|
|
DUK_UNREACHABLE();
|
|
|
|
|
|
|
|
pop_and_false:
|
|
|
|
duk_pop_2_unsafe(thr);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pop_and_true:
|
|
|
|
duk_pop_2_unsafe(thr);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* in
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* E5 Sections 11.8.7, 8.12.6.
|
|
|
|
*
|
|
|
|
* Basically just a property existence check using [[HasProperty]].
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
|
|
|
|
duk_bool_t retval;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the values onto the stack first. It would be possible to cover
|
|
|
|
* some normal cases without resorting to the value stack (e.g. if
|
|
|
|
* lval is already a string).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* XXX: The ES5/5.1/6 specifications require that the key in 'key in obj'
|
|
|
|
* must be string coerced before the internal HasProperty() algorithm is
|
|
|
|
* invoked. A fast path skipping coercion could be safely implemented for
|
|
|
|
* numbers (as number-to-string coercion has no side effects). For ES2015
|
|
|
|
* proxy behavior, the trap 'key' argument must be in a string coerced
|
|
|
|
* form (which is a shame).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TypeError if rval is not an object or object like (e.g. lightfunc
|
|
|
|
* or plain buffer).
|
First round of lightfunc changes
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
11 years ago
|
|
|
*/
|
|
|
|
duk_push_tval(thr, tv_x);
|
|
|
|
duk_push_tval(thr, tv_y);
|
|
|
|
duk_require_type_mask(thr, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER);
|
|
|
|
|
|
|
|
(void) duk_to_property_key_hstring(thr, -2);
|
|
|
|
|
|
|
|
retval = duk_hobject_hasprop(thr,
|
|
|
|
DUK_GET_TVAL_NEGIDX(thr, -1),
|
|
|
|
DUK_GET_TVAL_NEGIDX(thr, -2));
|
|
|
|
|
|
|
|
duk_pop_2_unsafe(thr);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* typeof
|
|
|
|
*
|
|
|
|
* E5 Section 11.4.3.
|
|
|
|
*
|
|
|
|
* Very straightforward. The only question is what to return for our
|
|
|
|
* non-standard tag / object types.
|
|
|
|
*
|
|
|
|
* There is an unfortunate string constant define naming problem with
|
|
|
|
* typeof return values for e.g. "Object" and "object"; careful with
|
|
|
|
* the built-in string defines. The LC_XXX defines are used for the
|
|
|
|
* lowercase variants now.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_small_uint_t duk_js_typeof_stridx(duk_tval *tv_x) {
|
|
|
|
duk_small_uint_t stridx = 0;
|
|
|
|
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv_x)) {
|
|
|
|
case DUK_TAG_UNDEFINED: {
|
|
|
|
stridx = DUK_STRIDX_LC_UNDEFINED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DUK_TAG_NULL: {
|
|
|
|
/* Note: not a typo, "object" is returned for a null value. */
|
|
|
|
stridx = DUK_STRIDX_LC_OBJECT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DUK_TAG_BOOLEAN: {
|
|
|
|
stridx = DUK_STRIDX_LC_BOOLEAN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DUK_TAG_POINTER: {
|
|
|
|
/* Implementation specific. */
|
|
|
|
stridx = DUK_STRIDX_LC_POINTER;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DUK_TAG_STRING: {
|
|
|
|
duk_hstring *str;
|
|
|
|
|
|
|
|
/* All internal keys are identified as Symbols. */
|
|
|
|
str = DUK_TVAL_GET_STRING(tv_x);
|
|
|
|
DUK_ASSERT(str != NULL);
|
|
|
|
if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(str))) {
|
|
|
|
stridx = DUK_STRIDX_LC_SYMBOL;
|
|
|
|
} else {
|
|
|
|
stridx = DUK_STRIDX_LC_STRING;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DUK_TAG_OBJECT: {
|
|
|
|
duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_x);
|
|
|
|
DUK_ASSERT(obj != NULL);
|
|
|
|
if (DUK_HOBJECT_IS_CALLABLE(obj)) {
|
|
|
|
stridx = DUK_STRIDX_LC_FUNCTION;
|
|
|
|
} else {
|
|
|
|
stridx = DUK_STRIDX_LC_OBJECT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DUK_TAG_BUFFER: {
|
|
|
|
/* Implementation specific. In Duktape 1.x this would be
|
|
|
|
* 'buffer', in Duktape 2.x changed to 'object' because plain
|
|
|
|
* buffers now mimic Uint8Array objects.
|
|
|
|
*/
|
|
|
|
stridx = DUK_STRIDX_LC_OBJECT;
|
|
|
|
break;
|
|
|
|
}
|
First round of lightfunc changes
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
11 years ago
|
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
|
|
stridx = DUK_STRIDX_LC_FUNCTION;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
|
|
case DUK_TAG_FASTINT:
|
|
|
|
#endif
|
|
|
|
default: {
|
|
|
|
/* number */
|
|
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_x));
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
|
|
|
|
stridx = DUK_STRIDX_LC_NUMBER;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_ASSERT_STRIDX_VALID(stridx);
|
|
|
|
return stridx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Array index and length
|
|
|
|
*
|
|
|
|
* Array index: E5 Section 15.4
|
|
|
|
* Array length: E5 Section 15.4.5.1 steps 3.c - 3.d (array length write)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Compure array index from string context, or return a "not array index"
|
|
|
|
* indicator.
|
|
|
|
*/
|
|
|
|
DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_string(const duk_uint8_t *str, duk_uint32_t blen) {
|
|
|
|
duk_uarridx_t res;
|
|
|
|
|
|
|
|
/* Only strings with byte length 1-10 can be 32-bit array indices.
|
|
|
|
* Leading zeroes (except '0' alone), plus/minus signs are not allowed.
|
|
|
|
* We could do a lot of prechecks here, but since most strings won't
|
|
|
|
* start with any digits, it's simpler to just parse the number and
|
|
|
|
* fail quickly.
|
|
|
|
*/
|
|
|
|
|
|
|
|
res = 0;
|
|
|
|
if (blen == 0) {
|
|
|
|
goto parse_fail;
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
duk_uarridx_t dig;
|
|
|
|
dig = (duk_uarridx_t) (*str++) - DUK_ASC_0;
|
|
|
|
|
|
|
|
if (dig <= 9U) {
|
|
|
|
/* Careful overflow handling. When multiplying by 10:
|
|
|
|
* - 0x19999998 x 10 = 0xfffffff0: no overflow, and adding
|
|
|
|
* 0...9 is safe.
|
|
|
|
* - 0x19999999 x 10 = 0xfffffffa: no overflow, adding
|
|
|
|
* 0...5 is safe, 6...9 overflows.
|
|
|
|
* - 0x1999999a x 10 = 0x100000004: always overflow.
|
|
|
|
*/
|
|
|
|
if (DUK_UNLIKELY(res >= 0x19999999UL)) {
|
|
|
|
if (res >= 0x1999999aUL) {
|
|
|
|
/* Always overflow. */
|
|
|
|
goto parse_fail;
|
|
|
|
}
|
|
|
|
DUK_ASSERT(res == 0x19999999UL);
|
|
|
|
if (dig >= 6U) {
|
|
|
|
goto parse_fail;
|
|
|
|
}
|
|
|
|
res = 0xfffffffaUL + dig;
|
|
|
|
DUK_ASSERT(res >= 0xfffffffaUL);
|
|
|
|
DUK_ASSERT_DISABLE(res <= 0xffffffffUL); /* range */
|
|
|
|
} else {
|
|
|
|
res = res * 10U + dig;
|
|
|
|
if (DUK_UNLIKELY(res == 0)) {
|
|
|
|
/* If 'res' is 0, previous 'res' must
|
|
|
|
* have been 0 and we scanned in a zero.
|
|
|
|
* This is only allowed if blen == 1,
|
|
|
|
* i.e. the exact string '0'.
|
|
|
|
*/
|
|
|
|
if (blen == (duk_uint32_t) 1) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
goto parse_fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Because 'dig' is unsigned, catches both values
|
|
|
|
* above '9' and below '0'.
|
|
|
|
*/
|
|
|
|
goto parse_fail;
|
|
|
|
}
|
|
|
|
} while (--blen > 0);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
parse_fail:
|
|
|
|
return DUK_HSTRING_NO_ARRAY_INDEX;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if !defined(DUK_USE_HSTRING_ARRIDX)
|
|
|
|
/* Get array index for a string which is known to be an array index. This helper
|
|
|
|
* is needed when duk_hstring doesn't concretely store the array index, but strings
|
|
|
|
* are flagged as array indices at intern time.
|
|
|
|
*/
|
|
|
|
DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_hstring_fast_known(duk_hstring *h) {
|
|
|
|
const duk_uint8_t *p;
|
|
|
|
duk_uarridx_t res;
|
|
|
|
duk_uint8_t t;
|
|
|
|
|
|
|
|
DUK_ASSERT(h != NULL);
|
|
|
|
DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(h));
|
|
|
|
|
|
|
|
p = DUK_HSTRING_GET_DATA(h);
|
|
|
|
res = 0;
|
|
|
|
for (;;) {
|
|
|
|
t = *p++;
|
|
|
|
if (DUK_UNLIKELY(t == 0)) {
|
|
|
|
/* Scanning to NUL is always safe for interned strings. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
DUK_ASSERT(t >= DUK_ASC_0 && t <= DUK_ASC_9);
|
|
|
|
res = res * 10U + (t - DUK_ASC_0);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_hstring_fast(duk_hstring *h) {
|
|
|
|
DUK_ASSERT(h != NULL);
|
|
|
|
if (!DUK_HSTRING_HAS_ARRIDX(h)) {
|
|
|
|
return DUK_HSTRING_NO_ARRAY_INDEX;
|
|
|
|
}
|
|
|
|
return duk_js_to_arrayindex_hstring_fast_known(h);
|
|
|
|
}
|
|
|
|
#endif /* DUK_USE_HSTRING_ARRIDX */
|