From 4cbf7cd1dd7a26c3016dd56f203b6c5eacde0145 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Thu, 18 Sep 2014 18:20:53 +0300
Subject: [PATCH 1/8] massif-XXX targets for heap use
Print out the massif graph and a grepped "maximum usage" to get some
quick idea of how much memory a certain ecmascript testcase uses at
the moment. Used for e.g. a hello world memory usage test. Usage is:
"make massif-test-dev-hello-world" or "make massif-XXX" for any other
test in ecmascript-testcases/. A few convenience targets like
massif-helloworld are included.
Set --peak-inaccuracy=0.0 for massif run to get as accurate a peak
estimate as possible. (This is not that important because the measure
itself is not 100% relevant.)
Also adds a testcase needed by massif-helloworld target.
---
.gitignore | 2 ++
Makefile | 21 ++++++++++++++++++++
ecmascript-testcases/test-dev-hello-world.js | 9 +++++++++
3 files changed, 32 insertions(+)
create mode 100644 ecmascript-testcases/test-dev-hello-world.js
diff --git a/.gitignore b/.gitignore
index 582c5eb8aa..a3c88909b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,7 @@ xmldoc
FlameGraph
dtrace4linux
flow
+massif-*.out
+ms_print.*
alljoyn-js
ajtcl
diff --git a/Makefile b/Makefile
index 07e63c5416..9a9f4dc2e4 100644
--- a/Makefile
+++ b/Makefile
@@ -294,6 +294,7 @@ clean:
@rm -rf luajs
@rm -f dukweb.js
@rm -rf /tmp/dukweb-test/
+ @rm -f massif-*.out
.PHONY: cleanall
cleanall: clean
@@ -888,3 +889,23 @@ codepolicycheckvim:
.PHONY: big-git-files
big-git-files:
util/find_big_git_files.sh
+
+# Simple heap graph and peak usage using valgrind --tool=massif, for quick
+# and dirty baseline comparison. Say e.g. 'make massif-test-dev-hello-world'.
+# The target name is intentionally not 'massif-%.out' so that the rule is never
+# satisfied and can be executed multiple times without cleaning.
+# Grep/sed hacks from:
+# http://stackoverflow.com/questions/774556/peak-memory-usage-of-a-linux-unix-process
+massif-%: ecmascript-testcases/%.js duk
+ @rm -f $(@).out
+ valgrind --tool=massif --peak-inaccuracy=0.0 --massif-out-file=$(@).out ./duk $< >/dev/null 2>/dev/null
+ @ms_print $(@).out | head -35
+ @echo "[... clipped... ]"
+ @echo ""
+ @echo -n "MAXIMUM: "
+ @cat $(@).out | grep mem_heap_B | sed -e 's/mem_heap_B=\(.*\)/\1/' | sort -g | tail -n 1
+
+# Convenience targets
+massif-helloworld: massif-test-dev-hello-world
+massif-deepmerge: massif-test-dev-deepmerge
+massif-arcfour: massif-test-dev-arcfour
diff --git a/ecmascript-testcases/test-dev-hello-world.js b/ecmascript-testcases/test-dev-hello-world.js
new file mode 100644
index 0000000000..b5dae5eeae
--- /dev/null
+++ b/ecmascript-testcases/test-dev-hello-world.js
@@ -0,0 +1,9 @@
+/*
+ * Minimal test for memory usage baseline testing.
+ */
+
+/*===
+hello world!
+===*/
+
+print('hello world!');
From 1a0aa8574de9d01c898f85119f5845dfd654c894 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Mon, 22 Sep 2014 16:55:05 +0300
Subject: [PATCH 2/8] Add DUK_OPT_LIGHTFUNC_BUILTINS feature option
---
Makefile | 2 ++
doc/feature-options.rst | 7 +++++++
doc/low-memory.rst | 6 ++++++
src/duk_features.h.in | 15 +++++++++++++++
4 files changed, 30 insertions(+)
diff --git a/Makefile b/Makefile
index 9a9f4dc2e4..b058a8561e 100644
--- a/Makefile
+++ b/Makefile
@@ -211,6 +211,7 @@ CCOPTS_FEATURES += -DDUK_OPT_DEBUG_BUFSIZE=512
#CCOPTS_FEATURES += -DDUK_OPT_NO_ES6_PROXY
#CCOPTS_FEATURES += -DDUK_OPT_NO_ZERO_BUFFER_DATA
#CCOPTS_FEATURES += -DDUK_OPT_USER_INITJS='"this.foo = 123"'
+#CCOPTS_FEATURES += -DDUK_OPT_LIGHTFUNC_BUILTINS
CCOPTS_FEATURES += -DDUK_CMDLINE_FANCY
CCOPTS_FEATURES += -DDUK_CMDLINE_ALLOC_LOGGING
CCOPTS_FEATURES += -DDUK_CMDLINE_ALLOC_TORTURE
@@ -228,6 +229,7 @@ CCOPTS_SHARED += -I./dist/src -I./dist/examples/alloc-logging -I./dist/examples/
CCOPTS_NONDEBUG = $(CCOPTS_SHARED) $(CCOPTS_FEATURES)
CCOPTS_NONDEBUG += -Os -fomit-frame-pointer -g -ggdb
#CCOPTS_NONDEBUG += -DDUK_OPT_ASSERTIONS
+
CCOPTS_DEBUG = $(CCOPTS_SHARED) $(CCOPTS_FEATURES)
CCOPTS_DEBUG += -O0 -g -ggdb
CCOPTS_DEBUG += -DDUK_OPT_DEBUG
diff --git a/doc/feature-options.rst b/doc/feature-options.rst
index 1e3cc6a6a9..2004aa6402 100644
--- a/doc/feature-options.rst
+++ b/doc/feature-options.rst
@@ -392,6 +392,13 @@ DUK_OPT_NO_JC
Disable support for the JC format. Reduces code footprint. An attempt
to encode or decode the format causes an error.
+DUK_OPT_LIGHTFUNC_BUILTINS
+--------------------------
+
+Force built-in functions to be lightweight functions. This reduces
+memory footprint by around 14 kB at the cost of some non-compliant
+behavior.
+
Debugging options
=================
diff --git a/doc/low-memory.rst b/doc/low-memory.rst
index 241b23b854..73e9e1a46e 100644
--- a/doc/low-memory.rst
+++ b/doc/low-memory.rst
@@ -61,3 +61,9 @@ Suggested feature options
debuglog lines. Use e.g. the following to reduce this overhead:
- ``-DDUK_OPT_DEBUG_BUFSIZE=2048``
+
+* For very low memory environments, consider using lightweight functions
+ for your Duktape/C bindings and to force Duktape built-ins to be lightweight
+ functions:
+
+ - ``DUK_OPT_LIGHTFUNC_BUILTINS``
diff --git a/src/duk_features.h.in b/src/duk_features.h.in
index 69326b4a6e..79baa8a7c9 100644
--- a/src/duk_features.h.in
+++ b/src/duk_features.h.in
@@ -2512,6 +2512,21 @@ typedef FILE duk_file;
#define DUK_USE_USER_INITJS (DUK_OPT_USER_INITJS)
#endif
+/*
+ * Lightweight functions
+ */
+
+/* Force built-ins to use lightfunc function pointers when possible. This
+ * makes the built-in functions non-compliant with respect to their property
+ * values and such, but is very useful in low memory environments (can save
+ * around 14kB of initial RAM footprint).
+ */
+
+#undef DUK_USE_LIGHTFUNC_BUILTINS
+#if defined(DUK_OPT_LIGHTFUNC_BUILTINS)
+#define DUK_USE_LIGHTFUNC_BUILTINS
+#endif
+
/*
* Miscellaneous
*/
From 4e025f0a7f540ab81454ecb655f95afc2831dc48 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Thu, 18 Sep 2014 00:56:54 +0300
Subject: [PATCH 3/8] Internal lightweight function documentation
---
doc/lightweight-function-value.rst | 111 ----------
doc/lightweight-functions.rst | 337 +++++++++++++++++++++++++++++
2 files changed, 337 insertions(+), 111 deletions(-)
delete mode 100644 doc/lightweight-function-value.rst
create mode 100644 doc/lightweight-functions.rst
diff --git a/doc/lightweight-function-value.rst b/doc/lightweight-function-value.rst
deleted file mode 100644
index cc22ece89d..0000000000
--- a/doc/lightweight-function-value.rst
+++ /dev/null
@@ -1,111 +0,0 @@
-==========================
-Lightweight function value
-==========================
-
-Overview
-========
-
-This document describes the changes necessary to support a lightweight
-function value type, which refers to a native Duktape/C function without
-needing a representative Ecmascript Function object. This is useful to
-minimize footprint of typical Duktape/C binding functions.
-
-Changes needed
-==============
-
-* Add a new tagged type, DUK_TAG_LIGHTFUNC, which points to a Duktape/C
- function.
-
- - A lightweight function can be a constructor, but will never have an
- automatic prototype object (a property slot would be needed to store it).
- A lightweight constructor function can create a replacement object from
- scratch and discard the automatically created instance object.
-
-* The representation depends on the ``duk_tval`` layout:
-
- - For 8-byte packed type: 32-bit function pointer (pointing to the
- Duktape/C function) and 16 bits for function metadata.
-
- - For unpacked type: function pointer and 16 bits options field.
-
-* The options field should contain:
-
- - Function argument count / varargs indicator. Also used to deduce
- a virtual "length" property.
-
- - Depending on how many functions there are with a "length" property
- different from their internal "nargs" property, perhaps a field for
- external "length" virtual property.
-
- - A small magic value, often needed in internals.
-
-* Add support in call handling for calling such a function.
-
-* Add support in traceback handling.
-
-* Add virtual object properties so that lightweight functions will appear
- like ordinary Function objects to some extent, e.g.:
-
- - "length": based on argument count, 0 if vararg.
-
- - "name": as no name can be stored, this should maybe be some useful
- string containing the function pointer, e.g. "lightfunc:0xdeadbeef".
-
- - "fileName": as no name can be stored, this should maybe be something
- like "lightfunc", or perhaps same as "name".
-
- - Perhaps some virtual properties like "caller" and "arguments", as
- given to strict Ecmascript functions.
-
-* It would be nice to be able to convert a lightweight function to a
- normal function object (which would be non-unique) if necessary.
-
-* Add an option to change built-in functions into lightweight functions
- instead of Function objects. This should not be active by default,
- because this change makes the built-ins strictly non-compliant. However,
- this is quite useful in RAM constrained environments.
-
-* Extend the public API to allow the user to push lightweight function
- pointers in addition to ordinary ones. Or perhaps make the default
- behavior to push a lightweight function (arguments permitting).
-
-Motivation
-==========
-
-Normal function representation
-------------------------------
-
-In Duktape 0.11.0 functions are represented as:
-
-* A ``duk_hcompiledfunction`` (a superset of ``duk_hobject``): represents
- an Ecmascript function which may have a set of properties, and points to
- the function's data area (bytecode, constants, inner function refs).
-
-* A ``duk_hnativefunction`` (a superset of ``duk_hobject``): represents
- a Duktape/C function which may also have a set of properties. A pointer
- to the C function is inside the ``duk_hnativefunction`` structure.
-
-This heavyweight representation is a RAM footprint issue, as discussed below.
-
-Ecmascript functions
---------------------
-
-An ordinary Ecmascript function takes over 200 bytes of RAM. There are
-two objects: the function itself and its automatic prototype object.
-The function contains a ``.prototype`` property while the prototype
-contains a ``.constructor`` property, so that both functions require a
-property table. This is the case even for the majority of user functions
-which will never be used as constructors; built-in functions are oddly
-exempt from having an automatic prototype. Taken together these four
-allocations take over 200 bytes.
-
-Duktape/C functions
--------------------
-
-A Duktape/C function takes about 70-80 bytes of RAM. Unlike Ecmascript
-functions, Duktape/C function are already stripped of unnecessary properties
-and don't have an automatic prototype object.
-
-Even so, there are close to 200 built-in functions, so the footprint of
-the ``duk_hnativefunction`` objects is around 16kB, not taking into account
-allocator overhead.
diff --git a/doc/lightweight-functions.rst b/doc/lightweight-functions.rst
new file mode 100644
index 0000000000..bedf6df9e8
--- /dev/null
+++ b/doc/lightweight-functions.rst
@@ -0,0 +1,337 @@
+==========================
+Lightweight function value
+==========================
+
+Overview
+========
+
+A lightweight function (or a "lightfunc") is a plain duk_tval value type
+which refers to a Duktape/C function without needing a representative
+Ecmascript Function object. The duk_tval tagged type encapsulates a
+reference to the native function, as well as a small set of control bits,
+without needing any heap allocations. This is useful in especially low
+memory environments, where the memory footprint of typical Duktape/C
+bindings can be reduced.
+
+A lightfunc has a separate API type (``DUK_TYPE_LIGHTFUNC``) so it is
+a clearly distinguished type for C code. However, for Ecmascript code
+a lightfunc behaves as closely as possible like an ordinary Function
+instance. Various techniques (such as virtual properties) are used to
+achieve this goal as well as possible.
+
+Memory representation
+=====================
+
+The 8-byte representation for a lightfunc is::
+
+ 16 bits 16 bits 32 bits
+ +--------+--------+----------------+
+ | 0xfff5 | flags | function ptr |
+ +--------+--------+----------------+
+
+The flags field is further split into::
+
+ 8 bits 4 bits 4 bits
+ +----------------+--------+--------+
+ | magic | length | nargs |
+ +----------------+--------+--------+
+
+ magic: signed 8-bit value
+ length: 0 to 15
+ nargs: 0 to 14, 15 indicates DUK_VARARGS
+
+Using lightfuncs
+================
+
+duk_push_c_lightfunc()
+----------------------
+
+You can make your own function bindings lightfuncs by simply pushing
+lightfunction values with ``duk_push_c_lightfunc()``. Lightfunc limits:
+
+* Number of stack arguments must be 0 to 14 or varargs.
+
+* Virtual "length" property can be given separately and must be between
+ 0 and 15.
+
+* Magic must be between -128 to 127 (-0x80 to 0x7f).
+
+DUK_OPT_LIGHTFUNC_BUILTINS
+--------------------------
+
+The feature option ``DUK_OPT_LIGHTFUNC_BUILTINS`` converts most built-in
+functions forcibly into lightweight functions, reducing memory usage on
+low memory platforms by around 14 kB.
+
+Behavior notes
+==============
+
+Testcases
+---------
+
+A lot of detailed behavior is described in testcases:
+
+* ``test-dev-lightfunc*.js``
+
+* ``test-dev-lightfunc*.c``
+
+Virtual properties
+------------------
+
+A lightfunc has the following virtual properties:
+
+* ``name``: fixed format of ``lightfunc__`` where ```` is a
+ platform dependent rendering of a function pointer, and ```` is a
+ 16-bit internal flags field encoded as-is.
+
+* ``length``: number between 0 and 15, encoded as 4 bits into the internal
+ flags field.
+
+Lightfunc cannot have a "prototype" property
+--------------------------------------------
+
+A lightfunc can be used as a constructor function, and is in fact always
+constructable. However, lightfuncs cannot have a "prototype" property,
+so when called as a constructor, the automatically created default instance
+object inherits from ``Object.prototype``.
+
+You can still construct objects that inherit from a custom prototype, but
+you need to create and return that value explicitly in the constructor, so
+that it replaces the automatic default instance.
+
+Lightfunc cannot be a setter/getter
+-----------------------------------
+
+A property value slot can either hold a ``duk_tval`` or two ``duk_hobject *``
+pointers for the setter/getter of an accessor (in 32-bit environments
+both take 8 bytes). The setter/getter slot cannot hold two lightfunc
+references (which would take 16 bytes).
+
+As a result, lightfuncs cannot be used as setter/getter values. If you
+give a lightfunc as a setter/getter reference, it will be silently coerced
+into a normal function object: the setter/getter will work, but a normal
+function object will be created (consuming memory).
+
+Lightfunc cannot have a finalizer
+---------------------------------
+
+Lightfuncs cannot have a finalizer because they are primitive values.
+As such they don't have a reference count field nor do they participate
+in garbage collection like actual objects.
+
+Hypothetically, even if lightfuncs were garbage collected somehow, they
+don't have space for a virtual ``_Finalizer`` property. It would be
+possible to set a finalizer on ``Function.prototype`` though and let that
+finalize the lightfuncs.
+
+Implementation notes
+====================
+
+Some changes needed
+-------------------
+
+This list is not exhaustive:
+
+* Add a new tagged type, DUK_TAG_LIGHTFUNC, which points to a Duktape/C
+ function.
+
+ - A lightweight function can be a constructor, but will never have an
+ automatic prototype object (a property slot would be needed to store it).
+ A lightweight constructor function can create a replacement object from
+ scratch and discard the automatically created instance object.
+
+* The representation depends on the ``duk_tval`` layout:
+
+ - For 8-byte packed type: 32-bit function pointer (pointing to the
+ Duktape/C function) and 16 bits for function metadata.
+
+ - For unpacked type: function pointer and 16 bits options field.
+
+* The 16-bit metadata field is divided into the following sub-fields:
+
+ - 8-bit magic value: important to be able to represent built-ins as
+ lightfuncs (they use magic value extensively)
+
+ - 4-bit ``nargs`` (with 15 indicating varargs)
+
+ - 4-bit ``length`` property value
+
+* Regarding Ecmascript semantics, the lightweight function type should
+ behave like a Function object as much as possible.
+
+ - This means, for example, that operators and built-in functions which
+ strictly require an object argument must handle lightweight function
+ values specially.
+
+ - Some property algorithms can be implemented by first checking for
+ lightfunc virtual properties, and if no virtual property matches,
+ replacing the original argument with ``Function.prototype``. This
+ doesn't always work, however. For instance, if getters/setters can
+ be invoked, the ``this`` binding must bind to the original lightfunc,
+ not ``Function prototype``.
+
+* All call sites in code which expect an object need to be considered.
+
+ - For example, if a call site uses ``duk_require_hobject()`` it needs to
+ be changed to allow an object or a lightfunc. There's a specific helper
+ to implement minimal lightfunc support to such call sites by coercing
+ lightfuncs to full Function objects: ``duk_require_hobject_or_lfunc_coerce()``.
+
+* Add support in call handling for calling a lightfunc:
+
+ - Bound function handling
+
+ - Magic and ``nargs``
+
+* Add support in traceback handling:
+
+ - Function name
+
+* Add virtual object properties so that lightweight functions will appear
+ like ordinary Function objects to some extent
+
+* Add reasonable behavior for all coercion operations, e.g. ToObject()
+ should probably coerce a lightfunc into a normal Function with the same
+ internal parameters (such as nargs and magic).
+
+* Add an option to change built-in functions into lightweight functions
+ instead of Function objects. This should not be active by default,
+ because this change makes the built-ins strictly non-compliant. However,
+ this is quite useful in RAM constrained environments.
+
+* Extend the public API to allow the user to push lightweight function
+ pointers in addition to ordinary ones.
+
+ - For now there is no module registration helper which supports lightweight
+ functions.
+
+* Fix operators requiring a function value:
+
+ - ``in``
+ - ``instanceof``
+
+* JSON/JX/JC support for lightfuncs
+
+Automatic conversion of built-ins to lightfuncs
+-----------------------------------------------
+
+Most built-ins can be converted into lightweight functions because they
+don't have a ``.prototype`` property which would prevent such a conversion.
+The built-ins do have a ``.length`` property which doesn't always match the
+actual argument count, but both ``nargs`` and ``length`` are stored in the
+lightfunc value to allow these functions to be represented as lightfuncs.
+
+The top level constructors (like ``Number``) cannot be converted to lightfuncs
+because they have property values (e.g. ``Number.POSITIVE_INFINITY``) which
+require a property table.
+
+Built-in methods use "magic" values extensively, and the 8-bit magic is
+sufficient for everything except the Date built-in. The Date built-in magic
+value was changed to be an index to a table of actual magic values to work
+around this limit.
+
+As a result, almost all built-in methods (except eval, yield, resume, and
+require) are now converted to lightfuncs.
+
+Future work
+===========
+
+More call sites with direct support of lightfuncs
+-------------------------------------------------
+
+Add support for direct lightfunc support in places where object coercion
+(e.g. ``duk_require_hobject_or_lfunc_coerce()``) is used. Such coercion
+has a memory churn impact so it's preferable to avoid it when it matters.
+The best places to improve on are those seen in practical code.
+
+For example, currently enumerating a lightfunc goes through coercion which
+is not ideal.
+
+Improved JX/JC support
+----------------------
+
+Should lightfuncs be visible in a special way in JX/JC encoding? For
+instance::
+
+ {_func:true} ecma function
+ {_cfunc:true} C function
+ {_lfunc:true} lightweight C function
+
+On the other hand C/Ecmascript functions are not distinguished in JX/JC now.
+
+ToLightFunc()
+-------------
+
+There's currently no way to coerce an ordinary native Function into a
+lightfunc. Lightfuncs can only be created through the Duktape API. If
+such a coercion was added, it would need to check compatibility for the
+coercion, at least magic and nargs must match for even the basic calling
+convention guarantees to work.
+
+Better virtual names for forced built-in lightfuncs
+---------------------------------------------------
+
+By matching Duktape/C function pointer and magic value, proper virtual
+names could be given to built-in lightfuncs. The function name table
+goes into code memory (e.g. flash) which is often less restricted than
+RAM.
+
+A similar approach would be to allow user code to provide a hook which
+could try to provide a name for a lightfunc when given its function
+pointer and the 16-bit flags field. User code could then consult symbol
+tables or similar to provide better names.
+
+Improve the Duktape C API
+-------------------------
+
+Right now there is just one call to push a lightfunc on the stack. The
+magic value of the lightfunc can be read. However, the magic value, nargs
+or length of a lightfunc cannot be modified. User can construct a new
+lightfunc from scratch, but won't be able to read e.g. the "nargs" value
+of a lightfunc on the stack.
+
+API questions:
+
+* Add a push variant which has no 'length' or 'magic', so that it matches
+ duk_push_c_function()?
+
+* Add necessary API calls to read and write 'length', 'magic', and 'nargs'
+ of a lightfunc.
+
+API additions are not necessarily preferable if there is not concrete need
+for them.
+
+Symbol file for lightweight functions
+-------------------------------------
+
+* Address/offset + 16-bit flags (or just magic), allows reconstruction of
+ lightfunc name
+
+* Build could provide some symbol information that could be read into a
+ debugger environment to improve traceback verbosity
+
+Improve defineProperty() behavior
+---------------------------------
+
+Object.defineProperty() could throw a TypeError ("not extensible") when a
+new property is created into a lightfunc. Currently this succeeds but of
+course new properties cannot actually be created into a lightfunc.
+
+Improve ToObject() coercion
+---------------------------
+
+Current ToObject() coercion has two logical but confusing issues:
+
+* The result is extensible while the input lightfunc is not. This is
+ useful because it's quite likely the user wants to extend the resulting
+ function if the lightfunc is explicitly object coerced. It also matches
+ the standard Ecmascript behavior for strings: ``new String('foo')``
+ returns an extensible String object.
+
+ Another alternative would be to make the result non-extensible.
+
+* The 'name' property of the coercion result is the lightfunc name, which
+ is a bit confusing because the object is no longer a lightfunc.
+
+ Another alternative would be to make the 'name' differ from the lightfunc
+ name. However, this would be confusing in a different way.
From fef0870107d1cec55f5500e46abbea9453377432 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Thu, 5 Jun 2014 15:33:42 +0300
Subject: [PATCH 4/8] 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
---
api-testcases/test-dev-lightfunc.c | 76 ++
api-testcases/test-get-set-magic.c | 2 +-
api-testcases/test-indirect-eval.c | 4 +
api-testcases/test-is-calls.c | 10 +-
api-testcases/test-magic-modify-during-call.c | 44 +
.../test-bi-duktape-json-lightfunc.js | 60 +
.../test-dev-date-gmtutc-func.js | 8 +-
ecmascript-testcases/test-dev-lightfunc.js | 1079 +++++++++++++++++
.../test-dev-notail-directive.js | 6 +-
src/duk_api_call.c | 36 +-
src/duk_api_internal.h | 7 +
src/duk_api_object.c | 2 +-
src/duk_api_public.h.in | 5 +-
src/duk_api_stack.c | 263 +++-
src/duk_bi_date.c | 145 ++-
src/duk_bi_duktape.c | 13 +-
src/duk_bi_error.c | 18 +-
src/duk_bi_function.c | 17 +-
src/duk_bi_json.c | 16 +-
src/duk_bi_logger.c | 7 +-
src/duk_bi_object.c | 39 +-
src/duk_bi_thread.c | 20 +-
src/duk_debug_hobject.c | 3 +
src/duk_debug_vsnprintf.c | 5 +
src/duk_error_augment.c | 20 +-
src/duk_features.h.in | 1 -
src/duk_heap_markandsweep.c | 2 +-
src/duk_heap_refcount.c | 2 +-
src/duk_hobject_props.c | 108 +-
src/duk_hthread.h | 16 +-
src/duk_hthread_builtins.c | 73 +-
src/duk_hthread_stacks.c | 103 +-
src/duk_js.h | 2 +-
src/duk_js_call.c | 264 ++--
src/duk_js_compiler.c | 4 +
src/duk_js_executor.c | 269 ++--
src/duk_js_ops.c | 31 +-
src/duk_js_var.c | 12 +-
src/duk_tval.h | 86 +-
src/genbuiltins.py | 110 +-
40 files changed, 2539 insertions(+), 449 deletions(-)
create mode 100644 api-testcases/test-dev-lightfunc.c
create mode 100644 api-testcases/test-magic-modify-during-call.c
create mode 100644 ecmascript-testcases/test-bi-duktape-json-lightfunc.js
create mode 100644 ecmascript-testcases/test-dev-lightfunc.js
diff --git a/api-testcases/test-dev-lightfunc.c b/api-testcases/test-dev-lightfunc.c
new file mode 100644
index 0000000000..49283a921f
--- /dev/null
+++ b/api-testcases/test-dev-lightfunc.c
@@ -0,0 +1,76 @@
+/*
+ * Behavior of lightweight functions in various situations.
+ *
+ * Also documents the detailed behavior and limitations of lightfuncs.
+ */
+
+/*===
+FIXME
+still here
+===*/
+
+/* FIXME: test all arg counts and lengths, including varargs */
+
+/* FIXME: duk_to_object() coercion, stack policy */
+
+/* FIXME: duk_push_current_function() for a lightfunc, check magic, perhaps length */
+
+static duk_ret_t test_magic(duk_context *ctx) {
+ /* FIXME */
+ /* magic limits, C api test, check what happens if you exceed */
+ return 0;
+}
+
+static duk_ret_t test_enum(duk_context *ctx) {
+ /* FIXME: push lightfunc here instead of relying on a built-in */
+ duk_eval_string(ctx, "Math.max");
+
+ printf("enum defaults\n");
+ duk_enum(ctx, -1, 0);
+ while (duk_next(ctx, -1, 0 /*get_value*/)) {
+ printf("key: %s\n", duk_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+ duk_pop(ctx);
+ printf("top: %ld\n", (long) duk_get_top(ctx));
+
+ printf("enum nonenumerable\n");
+ duk_enum(ctx, -1, DUK_ENUM_INCLUDE_NONENUMERABLE);
+ while (duk_next(ctx, -1, 0 /*get_value*/)) {
+ printf("key: %s\n", duk_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+ duk_pop(ctx);
+ printf("top: %ld\n", (long) duk_get_top(ctx));
+
+ printf("enum own\n");
+ duk_enum(ctx, -1, DUK_ENUM_OWN_PROPERTIES_ONLY);
+ while (duk_next(ctx, -1, 0 /*get_value*/)) {
+ printf("key: %s\n", duk_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+ duk_pop(ctx);
+ printf("top: %ld\n", (long) duk_get_top(ctx));
+
+ printf("enum own non-enumerable\n");
+ duk_enum(ctx, -1, DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_INCLUDE_NONENUMERABLE);
+ while (duk_next(ctx, -1, 0 /*get_value*/)) {
+ printf("key: %s\n", duk_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+ duk_pop(ctx);
+ printf("top: %ld\n", (long) duk_get_top(ctx));
+
+ return 0;
+}
+
+void test(duk_context *ctx) {
+ /* nargs / length limits, C api test, check what happens if you exceed */
+ /* Example of using lightfunc as a constructor, separate testcase, doc ref */
+
+ TEST_SAFE_CALL(test_magic);
+ TEST_SAFE_CALL(test_enum);
+
+ printf("still here\n");
+ fflush(stdout);
+}
diff --git a/api-testcases/test-get-set-magic.c b/api-testcases/test-get-set-magic.c
index 894e67d0c0..74b5039e23 100644
--- a/api-testcases/test-get-set-magic.c
+++ b/api-testcases/test-get-set-magic.c
@@ -21,7 +21,7 @@ magic: -16657
final top: 2
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
-==> rc=1, result='TypeError: not nativefunction'
+==> rc=1, result='TypeError: unexpected type'
*** test_3 (duk_safe_call)
==> rc=1, result='TypeError: not nativefunction'
*** test_4 (duk_safe_call)
diff --git a/api-testcases/test-indirect-eval.c b/api-testcases/test-indirect-eval.c
index 629e781585..a449029d3d 100644
--- a/api-testcases/test-indirect-eval.c
+++ b/api-testcases/test-indirect-eval.c
@@ -22,6 +22,10 @@ void test(duk_context *ctx) {
duk_get_prop_string(ctx, -1, "testFunc");
rc = duk_pcall(ctx, 0);
printf("rc=%d\n", (int) rc);
+ if (rc != 0) {
+ /* unexpected error */
+ printf("error=%s\n", duk_safe_to_string(ctx, -1));
+ }
duk_pop_2(ctx);
printf("final top: %ld\n", (long) duk_get_top(ctx));
diff --git a/api-testcases/test-is-calls.c b/api-testcases/test-is-calls.c
index 8501a63665..09db41cac0 100644
--- a/api-testcases/test-is-calls.c
+++ b/api-testcases/test-is-calls.c
@@ -1,4 +1,5 @@
/*===
+*** test_1 (duk_safe_call)
00: und=1 null=0 noru=1 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 thr=0 buf=0 dyn=0 fix=0 ptr=0 prim=1 objcoerc=0
01: und=0 null=1 noru=1 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 thr=0 buf=0 dyn=0 fix=0 ptr=0 prim=1 objcoerc=0
02: und=0 null=0 noru=0 bool=1 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 thr=0 buf=0 dyn=0 fix=0 ptr=0 prim=1 objcoerc=1
@@ -19,6 +20,7 @@
17: und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 thr=0 buf=1 dyn=0 fix=1 ptr=0 prim=1 objcoerc=1
18: und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 thr=0 buf=1 dyn=1 fix=0 ptr=0 prim=1 objcoerc=1
19: und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 thr=0 buf=0 dyn=0 fix=0 ptr=1 prim=1 objcoerc=1
+==> rc=0, result='undefined'
===*/
#include
@@ -27,7 +29,7 @@ static duk_ret_t my_c_func(duk_context *ctx) {
return 0;
}
-void test(duk_context *ctx) {
+static duk_ret_t test_1(duk_context *ctx) {
duk_idx_t i, n;
/*
@@ -126,4 +128,10 @@ void test(duk_context *ctx) {
printf(" objcoerc=%d", (int) duk_is_object_coercible(ctx, i));
printf("\n");
}
+
+ return 0;
+}
+
+void test(duk_context *ctx) {
+ TEST_SAFE_CALL(test_1);
}
diff --git a/api-testcases/test-magic-modify-during-call.c b/api-testcases/test-magic-modify-during-call.c
new file mode 100644
index 0000000000..92a2f5d1a7
--- /dev/null
+++ b/api-testcases/test-magic-modify-during-call.c
@@ -0,0 +1,44 @@
+/*
+ * If a function modifies its own 'magic' value while it is executing,
+ * the change is visible immediately.
+ *
+ * This is not necessarily stable behavior so user code should not rely
+ * on this. The current behavior is a side effect of the way the current
+ * call stack state keeping is implemented. This test case just documents
+ * the current behavior.
+ */
+
+/*===
+*** test_1 (duk_safe_call)
+current magic (on entry): 345
+current magic (after set): 456
+final top: 1
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t my_func(duk_context *ctx) {
+ printf("current magic (on entry): %ld\n", (long) duk_get_current_magic(ctx));
+
+ duk_push_current_function(ctx);
+ duk_set_magic(ctx, -1, 456);
+
+ printf("current magic (after set): %ld\n", (long) duk_get_current_magic(ctx));
+
+ return 0;
+}
+
+static duk_ret_t test_1(duk_context *ctx) {
+ duk_push_c_function(ctx, my_func, 2 /*nargs*/);
+ duk_set_magic(ctx, -1, 345);
+
+ duk_push_int(ctx, 123);
+ duk_push_int(ctx, 234);
+ duk_call(ctx, 2);
+
+ printf("final top: %ld\n", (long) duk_get_top(ctx));
+ return 0;
+}
+
+void test(duk_context *ctx) {
+ TEST_SAFE_CALL(test_1);
+}
diff --git a/ecmascript-testcases/test-bi-duktape-json-lightfunc.js b/ecmascript-testcases/test-bi-duktape-json-lightfunc.js
new file mode 100644
index 0000000000..8bc34b245a
--- /dev/null
+++ b/ecmascript-testcases/test-bi-duktape-json-lightfunc.js
@@ -0,0 +1,60 @@
+/*
+ * JSON, JX, and JC serialization of lightfuncs.
+ */
+
+/*---
+{
+ "custom": true,
+ "specialoptions": "DUK_OPT_LIGHTFUNC_BUILTINS"
+}
+---*/
+
+/*===
+undefined undefined
+string [1,2,3,null,4,5,6]
+string {"foo":123}
+string {_func:true}
+string [1,2,3,{_func:true},4,5,6]
+string {foo:123,bar:{_func:true}}
+string {"_func":true}
+string [1,2,3,{"_func":true},4,5,6]
+string {"foo":123,"bar":{"_func":true}}
+===*/
+
+function test() {
+ // FIXME: rely on Math.cos being a lightfunc
+ var lf = Math.cos;
+
+ function json(x) {
+ var res = JSON.stringify(x);
+ print(typeof res, res);
+ }
+
+ function jx(x) {
+ var res = Duktape.enc('jx', x);
+ print(typeof res, res);
+ }
+
+ function jc(x) {
+ var res = Duktape.enc('jc', x);
+ print(typeof res, res);
+ }
+
+ json(lf);
+ json([ 1, 2, 3, lf, 4, 5, 6 ]);
+ json({ foo: 123, bar: lf });
+
+ jx(lf);
+ jx([ 1, 2, 3, lf, 4, 5, 6 ]);
+ jx({ foo: 123, bar: lf });
+
+ jc(lf);
+ jc([ 1, 2, 3, lf, 4, 5, 6 ]);
+ jc({ foo: 123, bar: lf });
+}
+
+try {
+ test();
+} catch (e) {
+ print(e);
+}
diff --git a/ecmascript-testcases/test-dev-date-gmtutc-func.js b/ecmascript-testcases/test-dev-date-gmtutc-func.js
index cd23ca85f8..306f1729bd 100644
--- a/ecmascript-testcases/test-dev-date-gmtutc-func.js
+++ b/ecmascript-testcases/test-dev-date-gmtutc-func.js
@@ -1,7 +1,3 @@
-/*===
-true
-===*/
-
/*
* Date.prototype.toGMTString is required to have the same Function
* object as Date.prototype.toUTCString in E5 Section B.2.6.
@@ -10,4 +6,8 @@ true
* objects are distinct).
*/
+/*===
+true
+===*/
+
print(Date.prototype.toGMTString === Date.prototype.toUTCString);
diff --git a/ecmascript-testcases/test-dev-lightfunc.js b/ecmascript-testcases/test-dev-lightfunc.js
new file mode 100644
index 0000000000..fb33304efa
--- /dev/null
+++ b/ecmascript-testcases/test-dev-lightfunc.js
@@ -0,0 +1,1079 @@
+/*
+ * Behavior of lightweight functions in various situations.
+ *
+ * Also documents the detailed behavior and limitations of lightfuncs.
+ */
+
+/*---
+{
+ "custom": true,
+ "specialoptions": "DUK_OPT_LIGHTFUNC_BUILTINS"
+}
+---*/
+
+/* Some additional Function.prototype properties expected by tests below. */
+try {
+ Object.defineProperties(Function.prototype, {
+ testWritable: { value: 123, writable: true, enumerable: false, configurable: false },
+ testNonWritable: { value: 234, writable: false, enumerable: false, configurable: false }
+ });
+} catch (e) {
+ print(e.stack);
+}
+
+/* Shared object value which coerces to 'length' with a side effect.
+ * Used to verify string coercion and evaluation order.
+ */
+var objLengthKey = { toString: function () { print('toString coerced object (return "length")'); return "length"; } };
+
+function getLightFunc() {
+ /* When using DUK_OPT_LIGHTFUNC_BUILTINS, all built-ins except just a
+ * handful are lightfuncs.
+ *
+ * Use math.max as a test function: it has a non-zero magic, it has
+ * length set to 2, but is still a varargs function.
+ */
+ return Math.max;
+}
+
+function getNormalFunc() {
+ /* Even with DUK_OPT_LIGHTFUNC_BUILTINS, the top level constructors
+ * are not converted to lightfuncs: they have additional properties
+ * like Number.POSITIVE_INFINITY which would be lost.
+ */
+ return Number;
+}
+
+function sanitizeFuncToString(x) {
+ /* Escape Ecmascript comments which appear e.g. when coercing function
+ * values to string. Hide lightfunc pointer which is variable data.
+ */
+ x = String(x);
+ x = x.replace('/*', '(*').replace('*/', '*)');
+ x = x.replace(/light_[0-9a-fA-F]+_/, 'light_PTR_', x);
+ return x;
+}
+
+function isLightFunc(x) {
+ return Duktape.info(x)[0] == 9; // tag
+}
+
+/*===
+light func support test
+info.length: 1
+typeof: function
+[9]
+===*/
+
+function lightFuncSupportTest() {
+ /* Duktape.info() can be used to detect that light functions are
+ * supported.
+ */
+
+ var fun = getLightFunc();
+ var info = Duktape.info(fun);
+
+ /* Value is primitive (no prop allocations etc) but still callable. */
+ print('info.length:', info.length);
+ print('typeof:', typeof fun);
+ print(Duktape.enc('jx', Duktape.info(fun)));
+}
+
+/*===
+typeof test
+function
+===*/
+
+function typeofTest() {
+ /* typeof of lightweight and ordinary functions should look the same
+ * for transparency, so: 'function'.
+ */
+
+ var fun = getLightFunc();
+
+ print(typeof fun);
+}
+
+/*===
+comparison test
+===*/
+
+function comparisonTest() {
+ var lf1 = Math.cos;
+ var lf2 = Math.sin;
+ var lf3 = Array.prototype.forEach;
+ var nf1 = Object(lf1);
+ var nf2 = Object(lf2);
+ var nf3 = Object(lf3);
+ var ef1 = function ecmafunc() {};
+ var vals = [ lf1, lf2, lf3, nf1, nf2, nf3, ef1 ];
+ var i, j;
+
+ /*
+ * Comparison:
+ *
+ * - Lightfunc-to-lightfunc comparison compares both Duktape/C
+ * function pointers and the flags value (magic + other flags).
+ * Because the same Duktape/C function is often shared for very
+ * different functions, it's important to include at least magic
+ * in the comparison.
+ *
+ */
+
+ // FIXME: coerced lightfunc vs. plain lightfunc? Ordinary functions
+ // only compare equal only if the reference is exactly the same, so
+ // perhaps the desired result for lightFunc == Object(lightFunc) is false?
+
+ for (i = 0; i < vals.length; i++) {
+ for (j = 0; j < vals.length; j++) {
+ print(i, j, vals[i] == vals[j], vals[i] === vals[j]);
+ }
+ }
+}
+
+/*===
+toString() test
+function lightfunc() {(* lightfunc *)}
+function lightfunc() {(* lightfunc *)}
+function lightfunc() {(* lightfunc *)}
+true
+true
+===*/
+
+function toStringTest() {
+ /* String coercion of functions is not strictly defined - so here the
+ * coercion output can identify the function as a lightweight function.
+ *
+ * Because the string coercion output includes Ecmascript comment chars,
+ * we need some escaping here.
+ */
+
+ var fun = getLightFunc();
+
+ print(sanitizeFuncToString(String(fun)));
+ print(sanitizeFuncToString(fun.toString()));
+ print(sanitizeFuncToString(Function.prototype.toString.call(fun)));
+
+ /* ToString(fun) and Function.prototype.toString(fun) should match for
+ * lightfuncs.
+ */
+ print(String(fun) === fun.toString());
+ print(String(fun) === Function.prototype.toString.call(fun));
+}
+
+/*===
+toObject() test
+no caching: false
+length: 2 2
+name: lightfunc lightfunc
+typeof: function function
+internal prototype is Function.prototype: true true
+external prototype is not set: true
+internal prototypes match: true
+external prototypes match (do not exist): true
+Math.max test: 9 9
+length: 1 1
+===*/
+
+function toObjectTest() {
+ /* Object coercion creates a normal Duktape/C function object. A few
+ * things to watch out for:
+ *
+ * - varargs vs. fixed args
+ *
+ * The fact that the virtual lightfunc name is copied over to the
+ * non-light function is somewhat misleading, but still maybe the
+ * best option?
+ */
+
+ var lightFunc = Math.max; // Math.max has 'length' 2, but is varargs
+ var normalFunc = Object(lightFunc);
+
+ // Object coercion always results in a new object, there is no "caching"
+ print('no caching:', Object(lightFunc) === Object(lightFunc));
+
+ print('length:', lightFunc.length, normalFunc.length);
+ print('name:', lightFunc.name, normalFunc.name);
+ print('typeof:', typeof lightFunc, typeof normalFunc);
+ print('internal prototype is Function.prototype:',
+ Object.getPrototypeOf(lightFunc) === Function.prototype,
+ Object.getPrototypeOf(normalFunc) === Function.prototype);
+ print('external prototype is not set:',
+ lightFunc.prototype === undefined);
+ print('internal prototypes match:',
+ Object.getPrototypeOf(lightFunc) === Object.getPrototypeOf(normalFunc));
+ print('external prototypes match (do not exist):',
+ lightFunc.prototype === normalFunc.prototype);
+
+ // Although a lightfunc is not extensible, the coercion result is to
+ // match behavior for e.g. strings:
+ //
+ // > Object.isExtensible(Object('foo'))
+ // true
+ print('isExtensible:', Object.isExtensible(lightFunc), Object.isExtensible(normalFunc));
+
+ // Here the max value (9) should be further than what the apparent
+ // 'length' is
+ print('Math.max test:', lightFunc(1, 5, 7, 4, 9, 2), normalFunc(1, 5, 7, 4, 9, 2));
+
+ // Math.cos has 'length' 1 and is not varargs
+ lightFunc = Math.cos;
+ normalFunc = Object(lightFunc);
+ print('length:', lightFunc.length, normalFunc.length);
+
+ // FIXME: other properties
+}
+
+/*===
+call and apply test
+call
+321
+apply
+987
+===*/
+
+function callApplyTest() {
+ /* Lightfuncs inherit from Function.prototype (similarly to how plain
+ * strings inherit from String.prototype). This means you can use
+ * call() and apply().
+ */
+
+ var fun = Math.max; // length 2, varargs
+
+ print('call');
+ print(fun.call('myThis', 123, 321));
+
+ print('apply');
+ print(fun.apply('myThis', [ 123, 321, 987, 345 ]));
+}
+
+/*===
+inherit from Function.prototype test
+testValue
+===*/
+
+function inheritFromFunctionPrototypeTest() {
+ var fun = getLightFunc();
+
+ Function.prototype.inheritTestProperty = 'testValue';
+ print(fun.inheritTestProperty);
+}
+
+/*===
+Object.prototype.toString() test
+[object Function]
+===*/
+
+function objectPrototypeToStringTest() {
+ /* Object.prototype.toString() for a light function should look same as
+ * for actual functions to make them as transparent as possible, so:
+ * "[object Function]".
+ */
+
+ var fun = getLightFunc();
+
+ print(Object.prototype.toString.call(fun));
+}
+
+/*===
+JSON/JX/JC test
+json
+undefined
+undefined
+jx
+{_func:true}
+{_func:true}
+jc
+{"_func":true}
+{"_func":true}
+json
+undefined
+undefined
+jx
+{_func:true}
+{_func:true}
+jc
+{"_func":true}
+{"_func":true}
+json
+{"array":[1,null,2,null,3]}
+{
+ "array": [
+ 1,
+ null,
+ 2,
+ null,
+ 3
+ ]
+}
+jx
+{lf:{_func:true},nf:{_func:true},array:[1,{_func:true},2,{_func:true},3]}
+{
+ lf: {_func:true},
+ nf: {_func:true},
+ array: [
+ 1,
+ {_func:true},
+ 2,
+ {_func:true},
+ 3
+ ]
+}
+jc
+{"lf":{"_func":true},"nf":{"_func":true},"array":[1,{"_func":true},2,{"_func":true},3]}
+{
+ "lf": {"_func":true},
+ "nf": {"_func":true},
+ "array": [
+ 1,
+ {"_func":true},
+ 2,
+ {"_func":true},
+ 3
+ ]
+}
+===*/
+
+function jsonJxJcTest() {
+ /* There's a separate test for this too, but lightweight functions look
+ * like functions in JSON/JX/JC output.
+ */
+
+ // FIXME: should JX/JC distinguish?
+
+ var lightFunc = getLightFunc();
+ var normalFunc = getNormalFunc();
+
+ var testValue1 = lightFunc;
+ var testValue2 = normalFunc;
+ var testValue3 = {
+ lf: lightFunc,
+ nf: normalFunc,
+ array: [ 1, lightFunc, 2, normalFunc, 3 ]
+ };
+
+ [ testValue1, testValue2, testValue3 ].forEach(function (v) {
+ print('json');
+ print(JSON.stringify(v));
+ print(JSON.stringify(v, null, 4));
+ print('jx');
+ print(Duktape.enc('jx', v));
+ print(Duktape.enc('jx', v, null, 4));
+ print('jc');
+ print(Duktape.enc('jc', v));
+ print(Duktape.enc('jc', v, null, 4));
+ });
+}
+
+/*===
+bound function test
+F: function lightfunc() {(* lightfunc *)}
+F type tag: 9
+G: function lightfunc() {(* bound *)}
+G type tag: 6
+G.length: 1
+H: function light_0805b822_002f() {(* bound *)}
+H type tag: 6
+H.length: 0
+I: function light_0805b822_002f() {(* bound *)}
+I type tag: 6
+I.length: 0
+G(123): 234
+G(123,987): 987
+===*/
+
+function boundFunctionTest() {
+ /* A lightweight function can be bound normally. The 'length' property
+ * must be correctly copied from the virtual 'length' of the lightfunc.
+ */
+
+ var F = Math.max; // length 2, varargs
+ print('F:', sanitizeFuncToString(String(F)));
+ print('F type tag:', Duktape.info(F)[0]);
+
+ var G = F.bind('myThis', 234); // bound once
+ print('G:', sanitizeFuncToString(String(G)));
+ print('G type tag:', Duktape.info(G)[0]);
+ print('G.length:', G.length); // length 1, one argument was bound
+
+ var H = G.bind('foo', 345); // bound twice
+ print('H:', sanitizeFuncToString(String(H)));
+ print('H type tag:', Duktape.info(H)[0]);
+ print('H.length:', H.length); // length 0, two arguments are bound
+
+ var I = H.bind('foo', 345); // bound three times
+ print('I:', sanitizeFuncToString(String(I)));
+ print('I type tag:', Duktape.info(I)[0]);
+ print('I.length:', I.length); // length 0, doesn't become negative
+
+ // Another simple test
+ print('G(123):', G(123));
+ print('G(123,987):', G(123, 987));
+}
+
+/*===
+property get test
+length directly: 2
+toString coerced object (return "length")
+objLengthKey coerced to string: length
+toString coerced object (return "length")
+length through object coercion: 2
+read from length -> 2
+read from prototype -> undefined
+read from name -> lightfunc
+toString coerced object (return "length")
+toString coerced object (return "length")
+read from length -> 2
+read from testWritable -> 123
+read from testNonWritable -> 234
+read from call -> function lightfunc() {(* lightfunc *)}
+read from apply -> function lightfunc() {(* lightfunc *)}
+read from nonexistent -> undefined
+===*/
+
+function propertyGetTest() {
+ var lightFunc = getLightFunc();
+
+ /*
+ * Property get is relatively simple. Virtual properties are matched
+ * first, and then we continue with Function.prototype for lookup.
+ *
+ * An inherited getter is a special case.
+ *
+ * NOTE: duk_hobject_props.c must string coerce the key before comparison
+ * because an object may coerce to a virtual key name
+ */
+
+ print('length directly:', lightFunc.length);
+ print('objLengthKey coerced to string:', String(objLengthKey));
+ print('length through object coercion:', lightFunc[objLengthKey]);
+
+ var testKeys = [
+ 'length', 'prototype', 'name', // own properties
+ objLengthKey, // own, object coerces to 'length'
+ 'testWritable', // inherited, writable
+ 'testNonWritable', // inherited, non-writable
+ 'call', 'apply', // inherited, standard built-in
+ 'nonexistent' // non-existent
+ ];
+
+ testKeys.forEach(function (k) {
+ try {
+ print('read from', k, '->', sanitizeFuncToString(lightFunc[k]));
+ } catch (e) {
+ print('read from', k, '->', e.name);
+ }
+ });
+
+ // FIXME: property getter test
+}
+
+/*===
+property put test
+write to length -> silent error
+write to prototype -> silent error
+write to name -> silent error
+toString coerced object (return "length")
+toString coerced object (return "length")
+write to length -> silent error
+write to testWritable -> silent error
+write to testNonWritable -> silent error
+write to call -> silent error
+write to apply -> silent error
+write to nonexistent -> silent error
+write to length -> TypeError
+write to prototype -> TypeError
+write to name -> TypeError
+toString coerced object (return "length")
+toString coerced object (return "length")
+write to length -> TypeError
+write to testWritable -> TypeError
+write to testNonWritable -> TypeError
+write to call -> TypeError
+write to apply -> TypeError
+write to nonexistent -> TypeError
+===*/
+
+function propertyPutTest() {
+ var lightFunc = getLightFunc();
+
+ /*
+ * The own properties of a lightfunc are not writable. It is also not
+ * extensible (having no place to write properties), so property writes
+ * fail with TypeError in almost every case:
+ *
+ * - Own (virtual) property: not writable
+ * - Inherited property, non-writable: not writable
+ * - Inherited property, writable: not extensible
+ * - Non-existent: not extensible
+ * - Inherited property, setter: *setter must be called*
+ *
+ * The setter case is handled by a separate test function.
+ */
+
+ var testKeys = [
+ 'length', 'prototype', 'name', // own properties
+ objLengthKey, // own, object coerces to 'length'
+ 'testWritable', // inherited, writable
+ 'testNonWritable', // inherited, non-writable
+ 'call', 'apply', // inherited, standard built-in
+ 'nonexistent' // non-existent
+ ];
+
+ testKeys.forEach(function (k) {
+ // non-strict, errors are ignored
+ try {
+ lightFunc[k] = 'test';
+ print('write to', k, '->', 'silent error');
+ } catch (e) {
+ print('write to', k, '->', e.name);
+ }
+ });
+
+ testKeys.forEach(function (k) {
+ 'use strict'; // must be strict to cause throwing
+ try {
+ lightFunc[k] = 'test';
+ print('never here:', k);
+ } catch (e) {
+ print('write to', k, '->', e.name);
+ }
+ });
+}
+
+/*===
+property has test
+existence: length -> true
+existence: prototype -> false
+existence: name -> true
+toString coerced object (return "length")
+toString coerced object (return "length")
+existence: length -> true
+existence: testWritable -> true
+existence: testNonWritable -> true
+existence: call -> true
+existence: apply -> true
+existence: nonexistent -> false
+===*/
+
+function propertyHasTest() {
+ var lightFunc = getLightFunc();
+
+ /*
+ * Property existence test must account for own (virtual) properties
+ * and inherited properties.
+ */
+
+ var testKeys = [
+ 'length', 'prototype', 'name', // own
+ objLengthKey, // own, object coerces to 'length'
+ 'testWritable', // inherited
+ 'testNonWritable', // inherited
+ 'call', 'apply', // inherited
+ 'nonexistent' // non-existent
+ ];
+
+ testKeys.forEach(function (k) {
+ print('existence:', k, '->', k in lightFunc);
+ });
+}
+
+/*===
+property delete test
+delete: length -> false
+delete: prototype -> true
+delete: name -> false
+toString coerced object (return "length")
+toString coerced object (return "length")
+delete: length -> false
+delete: testWritable -> true
+delete: testNonWritable -> true
+delete: call -> true
+delete: apply -> true
+delete: nonexistent -> true
+delete: length -> TypeError
+delete: prototype -> true
+delete: name -> TypeError
+toString coerced object (return "length")
+toString coerced object (return "length")
+delete: length -> TypeError
+delete: testWritable -> true
+delete: testNonWritable -> true
+delete: call -> true
+delete: apply -> true
+delete: nonexistent -> true
+non-strict: true
+strict: true
+===*/
+
+function propertyDeleteTest() {
+ var lightFunc = getLightFunc();
+
+ /*
+ * Property deletions only affect own properties. Since all lightfunc
+ * virtual properties are non-configurable:
+ *
+ * - Own (virtual) property: not configurable
+ * - Non-existent property: silent success
+ */
+
+ // existence for own properties and a few inherited
+ var testKeys = [
+ 'length', 'prototype', 'name', // own
+ objLengthKey, // own, object coerces to 'length'
+ 'testWritable', // inherited
+ 'testNonWritable', // inherited
+ 'call', 'apply', // inherited
+ 'nonexistent' // non-existent
+ ];
+
+ testKeys.forEach(function (k) {
+ // non-strict, errors return false but don't throw
+ try {
+ print('delete:', k, '->', delete lightFunc[k]);
+ } catch (e) {
+ print('delete:', k, '->', e.name);
+ }
+ });
+
+ testKeys.forEach(function (k) {
+ 'use strict'; // promote to TypeError
+ try {
+ print('delete:', k, '->', delete lightFunc[k]);
+ } catch (e) {
+ print('delete:', k, '->', e.name);
+ }
+ });
+
+ // Deletion of a non-existent property is a silent success in both
+ // strict and non-strict mode. Below this is demonstrated also for
+ // normal functions.
+
+ var func = function () {};
+ try {
+ (function () { print('non-strict:', delete func.nonexistent); })();
+ } catch (e) {
+ print(e);
+ }
+ try {
+ (function () { 'use strict'; print('strict:', delete func.nonexistent); })();
+ } catch (e) {
+ print(e);
+ }
+}
+
+/*===
+property accessor this binding test
+getter, strict
+strict getter "this" binding test
+typeof this: function
+this == lightFunc: false
+this === lightFunc: true
+this.name: lightfunc
+type tag: 9
+getter retval
+setter, strict
+strict setter "this" binding test
+typeof this: function
+this == lightFunc: false
+this === lightFunc: true
+this.name: lightfunc
+type tag: 9
+getter, non-strict
+non-strict getter "this" binding test
+typeof this: function
+this == lightFunc: false
+this === lightFunc: false
+this.name: lightfunc
+type tag: 6
+getter retval
+setter, non-strict
+non-strict setter "this" binding test
+typeof this: function
+this == lightFunc: false
+this === lightFunc: false
+this.name: lightfunc
+type tag: 6
+===*/
+
+function propertyAccessorThisBindingTest() {
+ var lightFunc = getLightFunc();
+
+ /*
+ * If a getter/setter is triggered by reading/writing an inherited
+ * property, the getter/setter 'this' binding is set to the lightfunc
+ * (and not, e.g., Function.prototype).
+ *
+ * However, if the setter/getter is non-strict, the this binding is
+ * then object coerced. This is similar to what happens to a string:
+ *
+ * > Object.defineProperty(String.prototype, 'foo', { get: function () { print(typeof this); } });
+ * {}
+ * > Object.defineProperty(String.prototype, 'bar', { get: function () { 'use strict'; print(typeof this); } });
+ * {}
+ * > "foo".foo
+ * object
+ * undefined
+ * > "foo".bar
+ * string
+ * undefined
+ */
+
+ Object.defineProperty(Function.prototype, 'testAccessorStrict', {
+ get: function () {
+ 'use strict';
+ print('strict getter "this" binding test');
+ print('typeof this:', typeof this);
+ print('this == lightFunc:', this == lightFunc);
+ print('this === lightFunc:', this === lightFunc);
+ print('this.name:', sanitizeFuncToString(this.name));
+ print('type tag:', Duktape.info(this)[0]);
+ return 'getter retval';
+ },
+ set: function () {
+ 'use strict';
+ print('strict setter "this" binding test');
+ print('typeof this:', typeof this);
+ print('this == lightFunc:', this == lightFunc);
+ print('this === lightFunc:', this === lightFunc);
+ print('this.name:', sanitizeFuncToString(this.name));
+ print('type tag:', Duktape.info(this)[0]);
+ },
+ enumerable: false,
+ configurable: true
+ });
+
+ Object.defineProperty(Function.prototype, 'testAccessorNonStrict', {
+ get: function () {
+ print('non-strict getter "this" binding test');
+ print('typeof this:', typeof this);
+ print('this == lightFunc:', this == lightFunc);
+ print('this === lightFunc:', this === lightFunc);
+ print('this.name:', sanitizeFuncToString(this.name));
+ print('type tag:', Duktape.info(this)[0]);
+ return 'getter retval';
+ },
+ set: function () {
+ print('non-strict setter "this" binding test');
+ print('typeof this:', typeof this);
+ print('this == lightFunc:', this == lightFunc);
+ print('this === lightFunc:', this === lightFunc);
+ print('this.name:', sanitizeFuncToString(this.name));
+ print('type tag:', Duktape.info(this)[0]);
+ },
+ enumerable: false,
+ configurable: true
+ });
+
+ print('getter, strict');
+ print(lightFunc.testAccessorStrict);
+ print('setter, strict');
+ lightFunc.testAccessorStrict = 123;
+
+ print('getter, non-strict');
+ print(lightFunc.testAccessorNonStrict);
+ print('setter, non-strict');
+ lightFunc.testAccessorNonStrict = 123;
+}
+
+/*===
+property misc test
+===*/
+
+function propertyMiscTest() {
+ var lightFunc = getLightFunc();
+ var t, k;
+
+ /*
+ * A lightfunc is considered sealed, frozen, and non-extensible.
+ */
+
+ print('isSealed:', Object.isSealed(lightFunc));
+ print('isFrozen:', Object.isFrozen(lightFunc));
+ print('isExtensible:', Object.isExtensible(lightFunc));
+
+ /*
+ * Enumeration: enumerable keys (both own and inherited). Because all
+ * own properties are non-enumerable, only enumerable inherited keys
+ * print out here.
+ */
+
+ for (k in lightFunc) {
+ print('for-in:', JSON.stringify(k));
+ }
+
+ /*
+ * Object.keys(): only returns own non-enumerable keys, so -nothing-
+ * prints out here.
+ */
+
+ Object.keys(lightFunc).forEach(function (k) {
+ print('Object.keys:', JSON.stringify(k));
+ });
+
+ /*
+ * Own property names: own property names (virtual properties) print out.
+ */
+
+ Object.getOwnPropertyNames(lightFunc).forEach(function (k) {
+ print('Object.getOwnPropertyNames:', JSON.stringify(k));
+ });
+
+ /*
+ * The virtual 'name' of a lightfunc has the form:
+ *
+ * light__
+ *
+ * The flags field includes the magic value and other flags. The magic
+ * value is an important part of a function identity because it may have
+ * a large impact on the Duktape/C function behavior.
+ */
+
+ t = /^light_[0-9a-fA-F]+_[0-9a-fA-F]{4}$/.exec(lightFunc.name);
+ print('lightFunc.name matches regexp:', (t !== null));
+
+ t = lightFunc.name.substring(0, 5) + '_' +
+ lightFunc.name.substring(6).replace(/[0-9a-fA-F]+/g, 'XXX');
+ print('censored lightFunc.name:', t);
+
+ // .prototype maps to Function.prototype
+/*
+ print('call' in lightFunc); // inherited
+ print('prototype' in lightFunc); // own property
+ print(lightFunc.prototype === Function.prototype);
+*/
+}
+
+/*===
+traceback test
+===*/
+
+function tracebackTest() {
+ var err;
+
+ try {
+ decodeURIComponent('%x');
+ } catch (e) {
+ err = e;
+ }
+
+ // FIXME: validate
+ print(err.stack);
+}
+
+/*===
+Duktape.act() test
+===*/
+
+function duktapeActTest() {
+ // FIXME: lightfunc in Duktape.act(); how to test, need intermediate function
+ // to call Ecmascript function, perhaps ./duk --test
+
+ // FIXME: use forEach for now
+
+ [ 'foo' ].forEach(function callback(x) {
+ var e = new Error('for traceback');
+ var i;
+ var a;
+
+ print(e.stack);
+
+ for (i = -1; ; i--) {
+ a = Duktape.act(i);
+ if (!a) { break; }
+ print(i, Duktape.enc('jx', Object.getOwnPropertyNames(a)), sanitizeFuncToString(a.function.name));
+ }
+ });
+}
+
+function exemptBuiltinsTest() {
+
+ function f(v) {
+ return (typeof v) + ' ' + isLightFunc(v);
+ }
+
+ // this is converted to a lightfunc; print for ensuring isLightFunc() works
+ print('Math.max (is lightfunc):', f(Math.max));
+
+ /*
+ * These specific built-ins properties cannot be converted to lightfuncs
+ * because of internal asserts or because a user may write some property
+ * of the function (e.g. "require.id").
+ */
+
+ print('eval:', f(eval));
+ print('yield:', f(Duktape.Thread.yield));
+ print('resume:', f(Duktape.Thread.resume));
+ print('require:', f(require));
+
+ /*
+ * These top-level constructor functions are never converted to lightfuncs
+ * because they have properties that cannot be virtualized.
+ */
+
+ print('Object:', f(Object));
+ print('Function:', f(Function));
+ print('Array:', f(Array));
+ print('String:', f(String));
+ print('Boolean:', f(Boolean));
+ print('Number:', f(Number));
+ print('Date:', f(Date));
+ print('RegExp:', f(RegExp));
+ print('Error:', f(Error));
+ print('EvalError:', f(EvalError));
+ print('RangeError:', f(RangeError));
+ print('ReferenceError:', f(ReferenceError));
+ print('SyntaxError:', f(SyntaxError));
+ print('TypeError:', f(TypeError));
+ print('URIError:', f(URIError));
+ print('Proxy:', f(Proxy));
+
+ print('Duktape.Buffer:', f(Duktape.Buffer));
+ print('Duktape.Pointer:', f(Duktape.Pointer));
+ print('Duktape.Thread:', f(Duktape.Thread));
+ print('Duktape.Logger:', f(Duktape.Logger));
+
+ /*
+ * These globals are not functions at all.
+ */
+
+ print('Duktape:', f(Duktape));
+ print('Math:', f(Math));
+ print('JSON:', f(JSON));
+}
+
+/*
+- Duktape.info() documentation for tags
+- Rename lightfunc/LIGHTFUNC to FUNCTION? Cf. POINTER and BUFFER are the
+ plain primitive type names, while Pointer and Buffer are Objects
+- Portable funcptr printing -- duk_push_funcptr_string(ctx, ptr, sizeof)?
+- Some way of reading a function's magic from Ecmascript code?
+- Magic rename for release (keep magic API funcs as macros)?
+- Bound function object pointing to lightweight func
+
+- interaction with 'caller' property
+- list all function properties and add testcase printing for them
+
+- check virtual name of lightfuncs
+- if built-ins get special handling for lightfunc name, test for that here
+
+- a in b
+- a instanceof b
+
+- equality, plain and strict
+*/
+
+try {
+ print('light func support test');
+ lightFuncSupportTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('typeof test');
+ typeofTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('comparison test');
+ comparisonTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('toString() test');
+ toStringTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('toObject() test');
+ toObjectTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('call and apply test');
+ callApplyTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('inherit from Function.prototype test');
+ inheritFromFunctionPrototypeTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('Object.prototype.toString() test');
+ objectPrototypeToStringTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('JSON/JX/JC test');
+ jsonJxJcTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('bound function test');
+ boundFunctionTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('property get test');
+ propertyGetTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('property put test');
+ propertyPutTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('property has test');
+ propertyHasTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('property delete test');
+ propertyDeleteTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('property accessor this binding test');
+ propertyAccessorThisBindingTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('property misc test');
+ propertyMiscTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('traceback test');
+ tracebackTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('Duktape.act() test');
+ duktapeActTest();
+} catch (e) {
+ print(e.stack);
+}
+try {
+ print('exempt built-ins test');
+ exemptBuiltinsTest();
+} catch (e) {
+ print(e.stack);
+}
diff --git a/ecmascript-testcases/test-dev-notail-directive.js b/ecmascript-testcases/test-dev-notail-directive.js
index de836d45bc..bc9a756ed6 100644
--- a/ecmascript-testcases/test-dev-notail-directive.js
+++ b/ecmascript-testcases/test-dev-notail-directive.js
@@ -11,8 +11,9 @@ test2: act test2func test2 useNotailDirectiveTest
function test1func() {
// tailcall, call stack indices:
// -1 = Duktape.act, -2 = test1func, -3 = useNotailDirectiveTest
+ // Duktape.act.name is not printed directly because it may be a lightfunc
print('test1:',
- Duktape.act(-1).function.name,
+ Duktape.act(-1).function === Duktape.act ? 'act' : '???',
Duktape.act(-2).function.name,
Duktape.act(-3).function.name);
}
@@ -26,8 +27,9 @@ function test2func() {
// no tailcall, call stack indices:
// -1 = Duktape.act, -2 = test1func, -3 = test1, -4 = useNotailDirectiveTest
+ // Duktape.act.name is not printed directly because it may be a lightfunc
print('test2:',
- Duktape.act(-1).function.name,
+ Duktape.act(-1).function === Duktape.act ? 'act' : '???',
Duktape.act(-2).function.name,
Duktape.act(-3).function.name,
Duktape.act(-4).function.name);
diff --git a/src/duk_api_call.c b/src/duk_api_call.c
index 50e461b55a..acf62e662a 100644
--- a/src/duk_api_call.c
+++ b/src/duk_api_call.c
@@ -449,7 +449,13 @@ DUK_EXTERNAL duk_int_t duk_get_current_magic(duk_context *ctx) {
act = duk_hthread_get_current_activation(thr);
if (act) {
- func = act->func;
+ func = DUK_ACT_GET_FUNC(act);
+ if (!func) {
+ duk_tval *tv = &act->tv_func;
+ duk_small_uint_t lf_flags;
+ lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv);
+ return (duk_int_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags);
+ }
DUK_ASSERT(func != NULL);
if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
@@ -461,13 +467,33 @@ DUK_EXTERNAL duk_int_t duk_get_current_magic(duk_context *ctx) {
}
DUK_EXTERNAL duk_int_t duk_get_magic(duk_context *ctx, duk_idx_t index) {
- duk_hnativefunction *nf;
+ duk_hthread *thr = (duk_hthread *) ctx;
+ duk_tval *tv;
+ duk_hobject *h;
DUK_ASSERT(ctx != NULL);
- nf = duk_require_hnativefunction(ctx, index);
- DUK_ASSERT(nf != NULL);
- return (duk_int_t) nf->magic;
+ /* FIXME: hardcoded values for flag partitioning */
+
+ tv = duk_require_tval(ctx, index);
+ if (DUK_TVAL_IS_OBJECT(tv)) {
+ h = DUK_TVAL_GET_OBJECT(tv);
+ DUK_ASSERT(h != NULL);
+ if (!DUK_HOBJECT_HAS_NATIVEFUNCTION(h)) {
+ goto type_error;
+ }
+ return (duk_int_t) ((duk_hnativefunction *) h)->magic;
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ /* FIXME: use shared macro */
+ duk_int32_t tmp = (duk_int32_t) (DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) >> 8);
+ tmp = (tmp << 16) >> 24; /* 0x0000##00 -> 0x##000000 -> sign extend to 0xssssss## */
+ return (duk_int_t) tmp;
+ }
+
+ /* fall through */
+ type_error:
+ DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_UNEXPECTED_TYPE);
+ return 0;
}
DUK_EXTERNAL void duk_set_magic(duk_context *ctx, duk_idx_t index, duk_int_t magic) {
diff --git a/src/duk_api_internal.h b/src/duk_api_internal.h
index c8ace2ba73..55bc6f45e4 100644
--- a/src/duk_api_internal.h
+++ b/src/duk_api_internal.h
@@ -99,6 +99,9 @@ DUK_INTERNAL_DECL duk_hnativefunction *duk_require_hnativefunction(duk_context *
DUK_TAG_OBJECT | \
DUK_GETTAGGED_FLAG_CHECK_CLASS | ((classnum) << DUK_GETTAGGED_CLASS_SHIFT)))
+DUK_INTERNAL_DECL duk_hobject *duk_require_hobject_or_lfunc(duk_context *ctx, duk_idx_t index);
+DUK_INTERNAL_DECL duk_hobject *duk_require_hobject_or_lfunc_coerce(duk_context *ctx, duk_idx_t index);
+
#if 0 /*unused*/
DUK_INTERNAL_DECL void duk_push_unused(duk_context *ctx);
#endif
@@ -120,6 +123,10 @@ DUK_INTERNAL_DECL duk_idx_t duk_push_compiledfunction(duk_context *ctx);
DUK_INTERNAL_DECL void duk_push_c_function_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs);
DUK_INTERNAL_DECL void duk_push_c_function_noconstruct_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs);
+DUK_INTERNAL_DECL void duk_push_string_funcptr(duk_context *ctx, duk_uint8_t *ptr, duk_size_t sz);
+DUK_INTERNAL_DECL void duk_push_lightfunc_name(duk_context *ctx, duk_tval *tv);
+DUK_INTERNAL_DECL void duk_push_lightfunc_tostring(duk_context *ctx, duk_tval *tv);
+
DUK_INTERNAL_DECL duk_bool_t duk_get_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx); /* [] -> [val] */
DUK_INTERNAL_DECL duk_bool_t duk_put_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx); /* [val] -> [] */
DUK_INTERNAL_DECL duk_bool_t duk_del_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx); /* [] -> [] */
diff --git a/src/duk_api_object.c b/src/duk_api_object.c
index 560db385c3..ae9cc35aa2 100644
--- a/src/duk_api_object.c
+++ b/src/duk_api_object.c
@@ -357,8 +357,8 @@ DUK_EXTERNAL void duk_compact(duk_context *ctx, duk_idx_t obj_index) {
DUK_EXTERNAL void duk_enum(duk_context *ctx, duk_idx_t obj_index, duk_uint_t enum_flags) {
DUK_ASSERT(ctx != NULL);
- duk_require_hobject(ctx, obj_index);
duk_dup(ctx, obj_index);
+ duk_require_hobject_or_lfunc_coerce(ctx, -1);
duk_hobject_enumerator_create(ctx, enum_flags); /* [target] -> [enum] */
}
diff --git a/src/duk_api_public.h.in b/src/duk_api_public.h.in
index baa5188149..5f46b447a3 100644
--- a/src/duk_api_public.h.in
+++ b/src/duk_api_public.h.in
@@ -115,6 +115,7 @@ struct duk_number_list_entry {
#define DUK_TYPE_OBJECT 6 /* Ecmascript object: includes objects, arrays, functions, threads */
#define DUK_TYPE_BUFFER 7 /* fixed or dynamic, garbage collected byte buffer */
#define DUK_TYPE_POINTER 8 /* raw void pointer */
+#define DUK_TYPE_LIGHTFUNC 9 /* lightweight function pointer */
/* Value mask types, used by e.g. duk_get_type_mask() */
#define DUK_TYPE_MASK_NONE (1 << DUK_TYPE_NONE)
@@ -126,6 +127,7 @@ struct duk_number_list_entry {
#define DUK_TYPE_MASK_OBJECT (1 << DUK_TYPE_OBJECT)
#define DUK_TYPE_MASK_BUFFER (1 << DUK_TYPE_BUFFER)
#define DUK_TYPE_MASK_POINTER (1 << DUK_TYPE_POINTER)
+#define DUK_TYPE_MASK_LIGHTFUNC (1 << DUK_TYPE_LIGHTFUNC)
#define DUK_TYPE_MASK_THROW (1 << 10) /* internal flag value: throw if mask doesn't match */
/* Coercion hints */
@@ -436,7 +438,8 @@ DUK_EXTERNAL_DECL duk_bool_t duk_is_primitive(duk_context *ctx, duk_idx_t index)
DUK_TYPE_MASK_STRING | \
DUK_TYPE_MASK_OBJECT | \
DUK_TYPE_MASK_BUFFER | \
- DUK_TYPE_MASK_POINTER)
+ DUK_TYPE_MASK_POINTER | \
+ DUK_TYPE_MASK_LIGHTFUNC)
DUK_EXTERNAL_DECL duk_errcode_t duk_get_error_code(duk_context *ctx, duk_idx_t index);
#define duk_is_error(ctx,index) \
diff --git a/src/duk_api_stack.c b/src/duk_api_stack.c
index 725544630f..da6f5cd6c2 100644
--- a/src/duk_api_stack.c
+++ b/src/duk_api_stack.c
@@ -12,6 +12,12 @@
#include "duk_internal.h"
+/*
+ * Forward declarations
+ */
+
+static duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_uint_t flags);
+
/*
* Global state for working around missing variadic macros
*/
@@ -1420,7 +1426,8 @@ DUK_EXTERNAL void *duk_require_heapptr(duk_context *ctx, duk_idx_t index) {
DUK_ASSERT(ctx != NULL);
tv = duk_require_tval(ctx, index);
- if (tv && DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+ DUK_ASSERT(tv != NULL);
+ if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
ret = (void *) DUK_TVAL_GET_HEAPHDR(tv);
DUK_ASSERT(ret != NULL);
return ret;
@@ -1430,6 +1437,49 @@ DUK_EXTERNAL void *duk_require_heapptr(duk_context *ctx, duk_idx_t index) {
return (void *) NULL; /* not reachable */
}
+/* Useful for internal call sites where we either expect an object (function)
+ * or a lightfunc. Returns NULL for a lightfunc.
+ */
+DUK_INTERNAL duk_hobject *duk_require_hobject_or_lfunc(duk_context *ctx, duk_idx_t index) {
+ duk_hthread *thr = (duk_hthread *) ctx;
+ duk_tval *tv;
+
+ DUK_ASSERT(ctx != NULL);
+
+ tv = duk_require_tval(ctx, index);
+ DUK_ASSERT(tv != NULL);
+ if (DUK_TVAL_IS_OBJECT(tv)) {
+ return DUK_TVAL_GET_OBJECT(tv);
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ return NULL;
+ }
+
+ DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_UNEXPECTED_TYPE);
+ return NULL; /* not reachable */
+}
+
+/* Useful for internal call sites where we either expect an object (function)
+ * or a lightfunc. Accepts an object (returned as is) or a lightfunc (coerced
+ * to an object). Return value is never NULL.
+ */
+DUK_INTERNAL duk_hobject *duk_require_hobject_or_lfunc_coerce(duk_context *ctx, duk_idx_t index) {
+ duk_hthread *thr = (duk_hthread *) ctx;
+ duk_tval *tv;
+
+ DUK_ASSERT(ctx != NULL);
+
+ tv = duk_require_tval(ctx, index);
+ if (DUK_TVAL_IS_OBJECT(tv)) {
+ return DUK_TVAL_GET_OBJECT(tv);
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ duk_to_object(ctx, index);
+ return duk_require_hobject(ctx, index);
+ }
+
+ DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_UNEXPECTED_TYPE);
+ return NULL; /* not reachable */
+}
+
DUK_EXTERNAL duk_size_t duk_get_length(duk_context *ctx, duk_idx_t index) {
duk_tval *tv;
@@ -1461,6 +1511,10 @@ DUK_EXTERNAL duk_size_t duk_get_length(duk_context *ctx, duk_idx_t index) {
DUK_ASSERT(h != NULL);
return (duk_size_t) DUK_HBUFFER_GET_SIZE(h);
}
+ case DUK_TAG_LIGHTFUNC: {
+ /* FIXME: return value for virtual length property */
+ return 0;
+ }
default:
/* number */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
@@ -1902,6 +1956,11 @@ DUK_EXTERNAL const char *duk_to_string(duk_context *ctx, duk_idx_t index) {
}
break;
}
+ case DUK_TAG_LIGHTFUNC: {
+ /* Should match Function.prototype.toString() */
+ duk_push_lightfunc_tostring(ctx, tv);
+ break;
+ }
default: {
/* number */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
@@ -2021,6 +2080,12 @@ DUK_EXTERNAL void *duk_to_pointer(duk_context *ctx, duk_idx_t index) {
*/
res = (void *) DUK_TVAL_GET_HEAPHDR(tv);
break;
+ case DUK_TAG_LIGHTFUNC:
+ /* FIXME: function pointers may not cast correctly to void *,
+ * but try anyway?
+ */
+ res = NULL;
+ break;
default:
/* number */
res = NULL;
@@ -2035,8 +2100,8 @@ DUK_EXTERNAL void *duk_to_pointer(duk_context *ctx, duk_idx_t index) {
DUK_EXTERNAL void duk_to_object(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_tval *tv;
- duk_uint_t shared_flags = 0; /* shared flags for a subset of types */
- duk_small_int_t shared_proto = 0;
+ duk_uint_t flags = 0; /* shared flags for a subset of types */
+ duk_small_int_t proto = 0;
DUK_ASSERT(ctx != NULL);
@@ -2052,16 +2117,16 @@ DUK_EXTERNAL void duk_to_object(duk_context *ctx, duk_idx_t index) {
break;
}
case DUK_TAG_BOOLEAN: {
- shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
- DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN);
- shared_proto = DUK_BIDX_BOOLEAN_PROTOTYPE;
+ flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+ DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN);
+ proto = DUK_BIDX_BOOLEAN_PROTOTYPE;
goto create_object;
}
case DUK_TAG_STRING: {
- shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
- DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ |
- DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING);
- shared_proto = DUK_BIDX_STRING_PROTOTYPE;
+ flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+ DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ |
+ DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING);
+ proto = DUK_BIDX_STRING_PROTOTYPE;
goto create_object;
}
case DUK_TAG_OBJECT: {
@@ -2069,29 +2134,78 @@ DUK_EXTERNAL void duk_to_object(duk_context *ctx, duk_idx_t index) {
break;
}
case DUK_TAG_BUFFER: {
- shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
- DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ |
- DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BUFFER);
- shared_proto = DUK_BIDX_BUFFER_PROTOTYPE;
+ flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+ DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ |
+ DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BUFFER);
+ proto = DUK_BIDX_BUFFER_PROTOTYPE;
goto create_object;
}
case DUK_TAG_POINTER: {
- shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
- DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER);
- shared_proto = DUK_BIDX_POINTER_PROTOTYPE;
+ flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+ DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER);
+ proto = DUK_BIDX_POINTER_PROTOTYPE;
goto create_object;
}
+ case DUK_TAG_LIGHTFUNC: {
+ /* Lightfunc coerces to a Function instance with concrete
+ * properties. Since 'length' is virtual for Duktape/C
+ * functions, don't need to define that.
+ *
+ * The result is made extensible to mimic what happens to
+ * strings:
+ * > Object.isExtensible(Object('foo'))
+ * true
+ */
+ duk_small_uint_t lf_flags;
+ duk_small_uint_t nargs;
+ duk_small_uint_t lf_len;
+ duk_c_function func;
+ duk_hnativefunction *nf;
+
+ DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags);
+
+ nargs = DUK_LFUNC_FLAGS_GET_NARGS(lf_flags);
+ if (nargs == 0x0f) {
+ nargs = DUK_VARARGS;
+ }
+ flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+ DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+ DUK_HOBJECT_FLAG_NATIVEFUNCTION |
+ DUK_HOBJECT_FLAG_NEWENV |
+ DUK_HOBJECT_FLAG_STRICT |
+ DUK_HOBJECT_FLAG_NOTAIL |
+ /* DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC: omitted here intentionally */
+ DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION);
+ (void) duk__push_c_function_raw(ctx, func, (duk_idx_t) nargs, flags);
+
+ lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags);
+ if (lf_len != nargs) {
+ /* Explicit length is only needed if it differs from 'nargs'. */
+ duk_push_int(ctx, (duk_int_t) lf_len);
+ duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);
+ }
+ duk_push_lightfunc_name(ctx, tv);
+ duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);
+
+ nf = duk_get_hnativefunction(ctx, -1);
+ DUK_ASSERT(nf != NULL);
+ nf->magic = (duk_int16_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags);
+
+ /* Enable DUKFUNC exotic behavior once properties are set up. */
+ DUK_HOBJECT_SET_EXOTIC_DUKFUNC((duk_hobject *) nf);
+ goto replace_value;
+ }
default: {
- shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+ flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER);
- shared_proto = DUK_BIDX_NUMBER_PROTOTYPE;
+ proto = DUK_BIDX_NUMBER_PROTOTYPE;
goto create_object;
}
}
return;
create_object:
- (void) duk_push_object_helper(ctx, shared_flags, shared_proto);
+ (void) duk_push_object_helper(ctx, flags, proto);
/* Note: Boolean prototype's internal value property is not writable,
* but duk_def_prop_stridx() disregards the write protection. Boolean
@@ -2103,6 +2217,7 @@ DUK_EXTERNAL void duk_to_object(duk_context *ctx, duk_idx_t index) {
duk_dup(ctx, index);
duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
+ replace_value:
duk_replace(ctx, index);
}
@@ -2154,6 +2269,8 @@ DUK_EXTERNAL duk_int_t duk_get_type(duk_context *ctx, duk_idx_t index) {
return DUK_TYPE_BUFFER;
case DUK_TAG_POINTER:
return DUK_TYPE_POINTER;
+ case DUK_TAG_LIGHTFUNC:
+ return DUK_TYPE_LIGHTFUNC;
default:
/* Note: number has no explicit tag (in 8-byte representation) */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
@@ -2188,6 +2305,8 @@ DUK_EXTERNAL duk_uint_t duk_get_type_mask(duk_context *ctx, duk_idx_t index) {
return DUK_TYPE_MASK_BUFFER;
case DUK_TAG_POINTER:
return DUK_TYPE_MASK_POINTER;
+ case DUK_TAG_LIGHTFUNC:
+ return DUK_TYPE_MASK_LIGHTFUNC;
default:
/* Note: number has no explicit tag (in 8-byte representation) */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
@@ -2304,6 +2423,10 @@ DUK_EXTERNAL duk_bool_t duk_is_array(duk_context *ctx, duk_idx_t index) {
}
DUK_EXTERNAL duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t index) {
+ duk_tval *tv = duk_get_tval(ctx, index);
+ if (tv && DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ return 1;
+ }
return duk__obj_flag_any_default_false(ctx,
index,
DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
@@ -2337,11 +2460,7 @@ DUK_EXTERNAL duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t index) {
DUK_EXTERNAL duk_bool_t duk_is_callable(duk_context *ctx, duk_idx_t index) {
/* XXX: currently same as duk_is_function() */
- return duk__obj_flag_any_default_false(ctx,
- index,
- DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
- DUK_HOBJECT_FLAG_NATIVEFUNCTION |
- DUK_HOBJECT_FLAG_BOUND);
+ return duk_is_function(ctx, index);
}
DUK_EXTERNAL duk_bool_t duk_is_dynamic_buffer(duk_context *ctx, duk_idx_t index) {
@@ -2704,16 +2823,27 @@ DUK_INTERNAL duk_hstring *duk_push_this_coercible_to_string(duk_context *ctx) {
DUK_EXTERNAL void duk_push_current_function(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_activation *act;
+ duk_hobject *func;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(ctx != NULL);
DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
+ /* FIXME: lightfunc handling: perhaps we'll need the lightfunc
+ * ptr after all... with all the duk_tval fields.
+ */
+
act = duk_hthread_get_current_activation(thr);
if (act) {
- DUK_ASSERT(act->func != NULL);
- duk_push_hobject(ctx, act->func);
+ /* FIXME: change if tv_func gets always initialized */
+ func = DUK_ACT_GET_FUNC(act);
+ if (func) {
+ duk_push_hobject(ctx, func);
+ } else {
+ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&act->tv_func));
+ duk_push_tval(ctx, &act->tv_func);
+ }
} else {
duk_push_undefined(ctx);
}
@@ -3494,6 +3624,10 @@ DUK_EXTERNAL void duk_error_stash(duk_context *ctx, duk_errcode_t err_code, cons
}
#endif
+/*
+ * Comparison
+ */
+
DUK_EXTERNAL duk_bool_t duk_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_tval *tv1, *tv2;
@@ -3528,3 +3662,78 @@ DUK_EXTERNAL duk_bool_t duk_strict_equals(duk_context *ctx, duk_idx_t index1, du
/* No coercions or other side effects, so safe */
return duk_js_strict_equals(tv1, tv2);
}
+
+/*
+ * Lightfunc
+ */
+
+void duk_push_lightfunc_name(duk_context *ctx, duk_tval *tv) {
+ /* FIXME: add pointer, and perhaps flags here? */
+ duk_c_function func;
+
+ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv));
+
+ /* Lightfunc name, includes Duktape/C native function pointer, which
+ * can often be used to locate the function from a symbol table.
+ * The name also includes the 16-bit duk_tval flags field because it
+ * includes the magic value. Because a single native function often
+ * provides different functionality depending on the magic value, it
+ * seems reasonably to include it in the name.
+ *
+ * On the other hand, a complicated name increases string table
+ * pressure in low memory environments (but only when function name
+ * is accessed).
+ */
+#if 1
+ func = DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv);
+ duk_push_sprintf(ctx, "light_");
+ duk_push_string_funcptr(ctx, (duk_uint8_t *) &func, sizeof(func));
+ duk_push_sprintf(ctx, "_%04x", (unsigned int) DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv));
+ duk_concat(ctx, 3);
+#else
+ duk_push_string(ctx, "light");
+#endif
+}
+
+void duk_push_lightfunc_tostring(duk_context *ctx, duk_tval *tv) {
+ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv));
+
+ duk_push_string(ctx, "function ");
+ duk_push_lightfunc_name(ctx, tv);
+ duk_push_string(ctx, "() {/* light */}");
+ duk_concat(ctx, 3);
+}
+
+/*
+ * Function pointers
+ *
+ * Printing function pointers is non-portable, so we do that by hex printing
+ * bytes from memory.
+ */
+
+void duk_push_string_funcptr(duk_context *ctx, duk_uint8_t *ptr, duk_size_t sz) {
+ duk_uint8_t buf[32 * 2];
+ duk_uint8_t *p, *q;
+ duk_small_uint_t i;
+ duk_small_uint_t t;
+
+ DUK_ASSERT(sz <= 32); /* sanity limit for function pointer size */
+
+ p = buf;
+#if defined(DUK_USE_INTEGER_LE)
+ q = ptr + sz;
+#else
+ q = ptr;
+#endif
+ for (i = 0; i < sz; i++) {
+#if defined(DUK_USE_INTEGER_LE)
+ t = *(--q);
+#else
+ t = *(q++);
+#endif
+ *p++ = duk_lc_digits[t >> 4];
+ *p++ = duk_lc_digits[t & 0x0f];
+ }
+
+ duk_push_lstring(ctx, (const char *) buf, sz * 2);
+}
diff --git a/src/duk_bi_date.c b/src/duk_bi_date.c
index 1e74bb0d95..69b3e12a58 100644
--- a/src/duk_bi_date.c
+++ b/src/duk_bi_date.c
@@ -1747,6 +1747,145 @@ DUK_INTERNAL void duk_bi_date_format_timeval(duk_double_t timeval, duk_uint8_t *
out_buf);
}
+/*
+ * Indirect magic value lookup for Date methods.
+ *
+ * Date methods don't put their control flags into the function magic value
+ * because they wouldn't fit into a LIGHTFUNC's magic field. Instead, the
+ * magic value is set to an index pointing to the array of control flags
+ * below.
+ *
+ * This must be kept in strict sync with genbuiltins.py!
+ */
+
+static duk_uint16_t duk__date_magics[] = {
+ /* 0: toString */
+ DUK__FLAG_TOSTRING_DATE + DUK__FLAG_TOSTRING_TIME + DUK__FLAG_LOCALTIME,
+
+ /* 1: toDateString */
+ DUK__FLAG_TOSTRING_DATE + DUK__FLAG_LOCALTIME,
+
+ /* 2: toTimeString */
+ DUK__FLAG_TOSTRING_TIME + DUK__FLAG_LOCALTIME,
+
+ /* 3: toLocaleString */
+ DUK__FLAG_TOSTRING_DATE + DUK__FLAG_TOSTRING_TIME + DUK__FLAG_TOSTRING_LOCALE + DUK__FLAG_LOCALTIME,
+
+ /* 4: toLocaleDateString */
+ DUK__FLAG_TOSTRING_DATE + DUK__FLAG_TOSTRING_LOCALE + DUK__FLAG_LOCALTIME,
+
+ /* 5: toLocaleTimeString */
+ DUK__FLAG_TOSTRING_TIME + DUK__FLAG_TOSTRING_LOCALE + DUK__FLAG_LOCALTIME,
+
+ /* 6: toUTCString */
+ DUK__FLAG_TOSTRING_DATE + DUK__FLAG_TOSTRING_TIME,
+
+ /* 7: toISOString */
+ DUK__FLAG_TOSTRING_DATE + DUK__FLAG_TOSTRING_TIME + DUK__FLAG_NAN_TO_RANGE_ERROR + DUK__FLAG_SEP_T,
+
+ /* 8: getFullYear */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_YEAR << DUK__FLAG_VALUE_SHIFT),
+
+ /* 9: getUTCFullYear */
+ 0 + (DUK__IDX_YEAR << DUK__FLAG_VALUE_SHIFT),
+
+ /* 10: getMonth */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_MONTH << DUK__FLAG_VALUE_SHIFT),
+
+ /* 11: getUTCMonth */
+ 0 + (DUK__IDX_MONTH << DUK__FLAG_VALUE_SHIFT),
+
+ /* 12: getDate */
+ DUK__FLAG_ONEBASED + DUK__FLAG_LOCALTIME + (DUK__IDX_DAY << DUK__FLAG_VALUE_SHIFT),
+
+ /* 13: getUTCDate */
+ DUK__FLAG_ONEBASED + (DUK__IDX_DAY << DUK__FLAG_VALUE_SHIFT),
+
+ /* 14: getDay */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_WEEKDAY << DUK__FLAG_VALUE_SHIFT),
+
+ /* 15: getUTCDay */
+ 0 + (DUK__IDX_WEEKDAY << DUK__FLAG_VALUE_SHIFT),
+
+ /* 16: getHours */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_HOUR << DUK__FLAG_VALUE_SHIFT),
+
+ /* 17: getUTCHours */
+ 0 + (DUK__IDX_HOUR << DUK__FLAG_VALUE_SHIFT),
+
+ /* 18: getMinutes */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_MINUTE << DUK__FLAG_VALUE_SHIFT),
+
+ /* 19: getUTCMinutes */
+ 0 + (DUK__IDX_MINUTE << DUK__FLAG_VALUE_SHIFT),
+
+ /* 20: getSeconds */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_SECOND << DUK__FLAG_VALUE_SHIFT),
+
+ /* 21: getUTCSeconds */
+ 0 + (DUK__IDX_SECOND << DUK__FLAG_VALUE_SHIFT),
+
+ /* 22: getMilliseconds */
+ DUK__FLAG_LOCALTIME + (DUK__IDX_MILLISECOND << DUK__FLAG_VALUE_SHIFT),
+
+ /* 23: getUTCMilliseconds */
+ 0 + (DUK__IDX_MILLISECOND << DUK__FLAG_VALUE_SHIFT),
+
+ /* 24: setMilliseconds */
+ DUK__FLAG_TIMESETTER + DUK__FLAG_LOCALTIME + (1 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 25: setUTCMilliseconds */
+ DUK__FLAG_TIMESETTER + (1 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 26: setSeconds */
+ DUK__FLAG_TIMESETTER + DUK__FLAG_LOCALTIME + (2 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 27: setUTCSeconds */
+ DUK__FLAG_TIMESETTER + (2 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 28: setMinutes */
+ DUK__FLAG_TIMESETTER + DUK__FLAG_LOCALTIME + (3 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 29: setUTCMinutes */
+ DUK__FLAG_TIMESETTER + (3 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 30: setHours */
+ DUK__FLAG_TIMESETTER + DUK__FLAG_LOCALTIME + (4 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 31: setUTCHours */
+ DUK__FLAG_TIMESETTER + (4 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 32: setDate */
+ DUK__FLAG_LOCALTIME + (1 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 33: setUTCDate */
+ 0 + (1 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 34: setMonth */
+ DUK__FLAG_LOCALTIME + (2 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 35: setUTCMonth */
+ 0 + (2 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 36: setFullYear */
+ DUK__FLAG_NAN_TO_ZERO + DUK__FLAG_LOCALTIME + (3 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 37: setUTCFullYear */
+ DUK__FLAG_NAN_TO_ZERO + (3 << DUK__FLAG_VALUE_SHIFT),
+
+ /* 38: getYear */
+ DUK__FLAG_LOCALTIME + DUK__FLAG_SUB1900 + (DUK__IDX_YEAR << DUK__FLAG_VALUE_SHIFT),
+
+ /* 39: setYear */
+ DUK__FLAG_NAN_TO_ZERO + DUK__FLAG_YEAR_FIXUP + (3 << DUK__FLAG_VALUE_SHIFT),
+};
+
+duk_small_uint_t duk__date_get_indirect_magic(duk_context *ctx) {
+ duk_small_int_t magicidx = (duk_small_uint_t) duk_get_current_magic(ctx);
+ DUK_ASSERT(magicidx >= 0 && magicidx < (duk_small_int_t) (sizeof(duk__date_magics) / sizeof(duk_uint16_t)));
+ return (duk_small_uint_t) duk__date_magics[magicidx];
+}
+
/*
* Constructor calls
*/
@@ -1865,7 +2004,7 @@ DUK_INTERNAL duk_ret_t duk_bi_date_constructor_now(duk_context *ctx) {
*/
DUK_INTERNAL duk_ret_t duk_bi_date_prototype_tostring_shared(duk_context *ctx) {
- duk_small_uint_t flags = (duk_small_uint_t) duk_get_current_magic(ctx);
+ duk_small_uint_t flags = duk__date_get_indirect_magic(ctx);
return duk__to_string_helper(ctx, flags);
}
@@ -1947,7 +2086,7 @@ DUK_INTERNAL duk_ret_t duk_bi_date_prototype_to_json(duk_context *ctx) {
*/
DUK_INTERNAL duk_ret_t duk_bi_date_prototype_get_shared(duk_context *ctx) {
- duk_small_uint_t flags_and_idx = (duk_small_uint_t) duk_get_current_magic(ctx);
+ duk_small_uint_t flags_and_idx = duk__date_get_indirect_magic(ctx);
return duk__get_part_helper(ctx, flags_and_idx);
}
@@ -2032,7 +2171,7 @@ DUK_INTERNAL duk_ret_t duk_bi_date_prototype_get_timezone_offset(duk_context *ct
*/
DUK_INTERNAL duk_ret_t duk_bi_date_prototype_set_shared(duk_context *ctx) {
- duk_small_uint_t flags_and_maxnargs = (duk_small_uint_t) duk_get_current_magic(ctx);
+ duk_small_uint_t flags_and_maxnargs = duk__date_get_indirect_magic(ctx);
return duk__set_part_helper(ctx, flags_and_maxnargs);
}
diff --git a/src/duk_bi_duktape.c b/src/duk_bi_duktape.c
index c62b96c226..46904cf491 100644
--- a/src/duk_bi_duktape.c
+++ b/src/duk_bi_duktape.c
@@ -139,9 +139,16 @@ DUK_INTERNAL duk_ret_t duk_bi_duktape_object_act(duk_context *ctx) {
duk_push_object(ctx);
- h_func = act->func;
- DUK_ASSERT(h_func != NULL);
- duk_push_hobject(ctx, h_func);
+ /* FIXME: push act->tv_func for lightfuncs, or perhaps tv_func just
+ * directly if it is changed to be always initialized.
+ */
+ h_func = DUK_ACT_GET_FUNC(act);
+ if (!h_func) {
+ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&act->tv_func));
+ duk_push_tval(ctx, &act->tv_func);
+ } else {
+ duk_push_hobject(ctx, h_func);
+ }
pc = (duk_uint_fast32_t) act->pc;
duk_push_uint(ctx, (duk_uint_t) pc);
diff --git a/src/duk_bi_error.c b/src/duk_bi_error.c
index 6a088b9c19..3e4d464e27 100644
--- a/src/duk_bi_error.c
+++ b/src/duk_bi_error.c
@@ -165,15 +165,14 @@ DUK_LOCAL duk_ret_t duk__traceback_getter_helper(duk_context *ctx, duk_small_int
flags = (duk_int_t) DUK_FLOOR(d / DUK_DOUBLE_2TO32);
t = (duk_small_int_t) duk_get_type(ctx, -2);
- if (t == DUK_TYPE_OBJECT) {
+ if (t == DUK_TYPE_OBJECT || t == DUK_TYPE_LIGHTFUNC) {
/*
- * Ecmascript/native function call
+ * Ecmascript/native function call or lightfunc call
*/
/* [ ... v1(func) v2(pc+flags) ] */
- h_func = duk_get_hobject(ctx, -2);
- DUK_ASSERT(h_func != NULL);
+ h_func = duk_get_hobject(ctx, -2); /* NULL for lightfunc */
duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME);
duk_get_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME);
@@ -201,7 +200,15 @@ DUK_LOCAL duk_ret_t duk__traceback_getter_helper(duk_context *ctx, duk_small_int
DUK_ASSERT(funcname != NULL);
DUK_ASSERT(filename != NULL);
- if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h_func)) {
+ if (h_func == NULL) {
+ duk_push_sprintf(ctx, "%s light%s%s%s%s%s",
+ (const char *) funcname,
+ (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty),
+ (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcalled : str_empty),
+ (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
+ (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
+ (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
+ } else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h_func)) {
duk_push_sprintf(ctx, "%s %s native%s%s%s%s%s",
(const char *) funcname,
(const char *) filename,
@@ -210,7 +217,6 @@ DUK_LOCAL duk_ret_t duk__traceback_getter_helper(duk_context *ctx, duk_small_int
(const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
(const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
(const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
-
} else {
duk_push_sprintf(ctx, "%s %s:%ld%s%s%s%s%s",
(const char *) funcname,
diff --git a/src/duk_bi_function.c b/src/duk_bi_function.c
index 634bc1ab32..0bdc568af4 100644
--- a/src/duk_bi_function.c
+++ b/src/duk_bi_function.c
@@ -132,14 +132,16 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_context *ctx) {
if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) {
/* XXX: actual source, if available */
- duk_push_sprintf(ctx, "function %s() {/* source code */}", (const char *) func_name);
+ duk_push_sprintf(ctx, "function %s() {/* ecmascript */}", (const char *) func_name);
} else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) {
- duk_push_sprintf(ctx, "function %s() {/* native code */}", (const char *) func_name);
+ duk_push_sprintf(ctx, "function %s() {/* native */}", (const char *) func_name);
} else if (DUK_HOBJECT_HAS_BOUND(obj)) {
duk_push_sprintf(ctx, "function %s() {/* bound */}", (const char *) func_name);
} else {
goto type_error;
}
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ duk_push_lightfunc_tostring(ctx, tv);
} else {
goto type_error;
}
@@ -298,8 +300,9 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) {
/* bound function 'length' property is interesting */
h_target = duk_get_hobject(ctx, -2);
- DUK_ASSERT(h_target != NULL);
- if (DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) {
+ if (h_target == NULL || /* lightfunc */
+ DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) {
+ /* For lightfuncs, simply read the virtual property. */
duk_int_t tmp;
duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH);
tmp = duk_to_int(ctx, -1) - (nargs - 1); /* step 15.a */
@@ -326,7 +329,11 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) {
* function. Not sure if this is correct, because the specification
* is a bit ambiguous on this point but it would make sense.
*/
- if (DUK_HOBJECT_HAS_STRICT(h_target)) {
+ if (h_target == NULL) {
+ /* lightfunc */
+ /* FIXME: assume strict? */
+ DUK_HOBJECT_SET_STRICT(h_bound);
+ } else if (DUK_HOBJECT_HAS_STRICT(h_target)) {
DUK_HOBJECT_SET_STRICT(h_bound);
}
DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(ctx, -1)));
diff --git a/src/duk_bi_json.c b/src/duk_bi_json.c
index 879a04e58f..7521e3f27d 100644
--- a/src/duk_bi_json.c
+++ b/src/duk_bi_json.c
@@ -1341,6 +1341,8 @@ DUK_LOCAL duk_bool_t duk__enc_value1(duk_json_enc_ctx *js_ctx, duk_idx_t idx_hol
if (h != NULL) {
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_JSON);
h = duk_get_hobject(ctx, -1);
+
+ /* FIXME: lightfunc support */
if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it"));
duk_dup(ctx, -2); /* -> [ ... key val toJSON val ] */
@@ -1579,6 +1581,17 @@ DUK_LOCAL void duk__enc_value2(duk_json_enc_ctx *js_ctx) {
break;
}
#endif /* DUK_USE_JX || DUK_USE_JC */
+ case DUK_TAG_LIGHTFUNC: {
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+ /* We only get here when doing non-standard JSON encoding */
+ DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible);
+ DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function);
+#else
+ /* Standard JSON omits functions */
+ DUK_NEVER_HERE();
+#endif
+ break;
+ }
default: {
/* number */
duk_double_t d;
@@ -1855,7 +1868,8 @@ void duk_bi_json_stringify_helper(duk_context *ctx,
{
js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED |
DUK_TYPE_MASK_POINTER |
- DUK_TYPE_MASK_BUFFER;
+ DUK_TYPE_MASK_BUFFER |
+ DUK_TYPE_MASK_LIGHTFUNC;
}
(void) duk_push_dynamic_buffer(ctx, 0);
diff --git a/src/duk_bi_logger.c b/src/duk_bi_logger.c
index c12844c4e5..4b147a618e 100644
--- a/src/duk_bi_logger.c
+++ b/src/duk_bi_logger.c
@@ -39,12 +39,15 @@ DUK_INTERNAL duk_ret_t duk_bi_logger_constructor(duk_context *ctx) {
if (thr->callstack_top >= 2) {
duk_activation *act_caller = thr->callstack + thr->callstack_top - 2;
- if (act_caller->func) {
+ duk_hobject *func_caller;
+
+ func_caller = DUK_ACT_GET_FUNC(act_caller);
+ if (func_caller) {
/* Stripping the filename might be a good idea
* ("/foo/bar/quux.js" -> logger name "quux"),
* but now used verbatim.
*/
- duk_push_hobject(ctx, act_caller->func);
+ duk_push_hobject(ctx, func_caller);
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME);
duk_replace(ctx, 0);
}
diff --git a/src/duk_bi_object.c b/src/duk_bi_object.c
index 77e8536722..37289aca9c 100644
--- a/src/duk_bi_object.c
+++ b/src/duk_bi_object.c
@@ -49,6 +49,15 @@ DUK_INTERNAL duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx) {
duk_insert(ctx, 0);
}
+ /* FIXME: lightfunc hack, rework */
+ {
+ duk_tval *tv = duk_get_tval(ctx, 0);
+ if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ duk_push_hobject_bidx(ctx, DUK_BIDX_FUNCTION_PROTOTYPE);
+ return 1;
+ }
+ }
+
h = duk_require_hobject(ctx, 0);
DUK_ASSERT(h != NULL);
@@ -194,6 +203,8 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context
duk_hobject *h;
duk_bool_t is_freeze;
+ /* FIXME: lightfunc */
+
h = duk_require_hobject(ctx, 0);
DUK_ASSERT(h != NULL);
@@ -212,6 +223,8 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *h;
+ /* FIXME: lightfunc */
+
h = duk_require_hobject(ctx, 0);
DUK_ASSERT(h != NULL);
@@ -230,22 +243,30 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_con
duk_bool_t is_frozen;
duk_bool_t rc;
- h = duk_require_hobject(ctx, 0);
- DUK_ASSERT(h != NULL);
+ /* FIXME: lightfunc */
- is_frozen = duk_get_current_magic(ctx);
- rc = duk_hobject_object_is_sealed_frozen_helper(h, is_frozen /*is_frozen*/);
- duk_push_boolean(ctx, rc);
+ h = duk_require_hobject_or_lfunc(ctx, 0);
+ if (!h) {
+ duk_push_true(ctx); /* frozen and sealed */
+ } else {
+ is_frozen = duk_get_current_magic(ctx);
+ rc = duk_hobject_object_is_sealed_frozen_helper(h, is_frozen /*is_frozen*/);
+ duk_push_boolean(ctx, rc);
+ }
return 1;
}
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx) {
duk_hobject *h;
- h = duk_require_hobject(ctx, 0);
- DUK_ASSERT(h != NULL);
+ /* FIXME: lightfunc */
- duk_push_boolean(ctx, DUK_HOBJECT_HAS_EXTENSIBLE(h));
+ h = duk_require_hobject_or_lfunc(ctx, 0);
+ if (!h) {
+ duk_push_false(ctx);
+ } else {
+ duk_push_boolean(ctx, DUK_HOBJECT_HAS_EXTENSIBLE(h));
+ }
return 1;
}
@@ -265,7 +286,7 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx) {
DUK_ASSERT_TOP(ctx, 1);
- obj = duk_require_hobject(ctx, 0);
+ obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
DUK_ASSERT(obj != NULL);
DUK_UNREF(obj);
diff --git a/src/duk_bi_thread.c b/src/duk_bi_thread.c
index bd85a3c968..f7c48c39ef 100644
--- a/src/duk_bi_thread.c
+++ b/src/duk_bi_thread.c
@@ -52,6 +52,7 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_context *ctx) {
duk_tval tv_tmp;
duk_tval *tv;
duk_hobject *func;
+ duk_hobject *caller_func;
duk_small_int_t is_error;
DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T",
@@ -76,11 +77,12 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_context *ctx) {
DUK_DD(DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"));
goto state_error;
}
- DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */
- DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
- DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL); /* us */
+ DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL); /* caller */
- if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) {
+ caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2);
+ if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func)) {
DUK_DD(DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"));
goto state_error;
}
@@ -207,6 +209,7 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_context *ctx) {
DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_tval tv_tmp;
+ duk_hobject *caller_func;
duk_small_int_t is_error;
DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T",
@@ -235,11 +238,12 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_context *ctx) {
DUK_DD(DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)"));
goto state_error;
}
- DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */
- DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
- DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL); /* us */
+ DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL); /* caller */
- if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) {
+ caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2);
+ if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func)) {
DUK_DD(DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"));
goto state_error;
}
diff --git a/src/duk_debug_hobject.c b/src/duk_debug_hobject.c
index d5f97a3381..f0fb1a790e 100644
--- a/src/duk_debug_hobject.c
+++ b/src/duk_debug_hobject.c
@@ -93,6 +93,9 @@ DUK_LOCAL char duk__get_tval_summary_char(duk_tval *tv) {
case DUK_TAG_POINTER: {
return 'P';
}
+ case DUK_TAG_LIGHTFUNC: {
+ return 'L';
+ }
default:
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
return 'd';
diff --git a/src/duk_debug_vsnprintf.c b/src/duk_debug_vsnprintf.c
index 06b5bfea67..b6496a9b22 100644
--- a/src/duk_debug_vsnprintf.c
+++ b/src/duk_debug_vsnprintf.c
@@ -724,6 +724,11 @@ DUK_LOCAL void duk__print_tval(duk__dprint_state *st, duk_tval *tv) {
duk_fb_sprintf(fb, "pointer:%p", (void *) DUK_TVAL_GET_POINTER(tv));
break;
}
+ case DUK_TAG_LIGHTFUNC: {
+ /* FIXME: func ptr and flags */
+ duk_fb_sprintf(fb, "lightfunc");
+ break;
+ }
default: {
/* IEEE double is approximately 16 decimal digits; print a couple extra */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
diff --git a/src/duk_error_augment.c b/src/duk_error_augment.c
index b55f354fc9..88676ad82e 100644
--- a/src/duk_error_augment.c
+++ b/src/duk_error_augment.c
@@ -224,6 +224,7 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */
for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) {
duk_uint32_t pc;
+ duk_hobject *func;
/*
* Note: each API operation potentially resizes the callstack,
@@ -235,17 +236,24 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
/* [... arr] */
+ /* FIXME: proper lightfunc support */
+#if 0
DUK_ASSERT(thr_callstack->callstack[i].func != NULL);
DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */
+#endif
- /* add function */
- duk_push_hobject(ctx, thr_callstack->callstack[i].func); /* -> [... arr func] */
+ /* Add function object. */
+ func = DUK_ACT_GET_FUNC(thr_callstack->callstack + i);
+ if (func) {
+ duk_push_hobject(ctx, func); /* -> [... arr func] */
+ } else {
+ duk_tval *tv = &(thr_callstack->callstack + i)->tv_func;
+ duk_push_tval(ctx, tv);
+ }
duk_def_prop_index_wec(ctx, -2, arr_idx);
arr_idx++;
- /* add a number containing: pc, activation flags */
-
- /* Add a number containing: pc, activation flag
+ /* Add a number containing: pc, activation flags.
*
* PC points to next instruction, find offending PC. Note that
* PC == 0 for native code.
@@ -322,7 +330,7 @@ DUK_LOCAL void duk__err_augment_builtin_throw(duk_hthread *thr, duk_hthread *thr
act = thr_callstack->callstack + thr_callstack->callstack_top - 1;
DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size);
- func = act->func;
+ func = DUK_ACT_GET_FUNC(act);
if (func) {
duk_uint32_t pc;
duk_uint32_t line;
diff --git a/src/duk_features.h.in b/src/duk_features.h.in
index 79baa8a7c9..1921ee2f90 100644
--- a/src/duk_features.h.in
+++ b/src/duk_features.h.in
@@ -2521,7 +2521,6 @@ typedef FILE duk_file;
* values and such, but is very useful in low memory environments (can save
* around 14kB of initial RAM footprint).
*/
-
#undef DUK_USE_LIGHTFUNC_BUILTINS
#if defined(DUK_OPT_LIGHTFUNC_BUILTINS)
#define DUK_USE_LIGHTFUNC_BUILTINS
diff --git a/src/duk_heap_markandsweep.c b/src/duk_heap_markandsweep.c
index 4c9cfbf62e..8ad8e16cc1 100644
--- a/src/duk_heap_markandsweep.c
+++ b/src/duk_heap_markandsweep.c
@@ -109,7 +109,7 @@ DUK_LOCAL void duk__mark_hobject(duk_heap *heap, duk_hobject *h) {
for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
duk_activation *act = t->callstack + i;
- duk__mark_heaphdr(heap, (duk_heaphdr *) act->func);
+ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_ACT_GET_FUNC(act));
duk__mark_heaphdr(heap, (duk_heaphdr *) act->var_env);
duk__mark_heaphdr(heap, (duk_heaphdr *) act->lex_env);
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
diff --git a/src/duk_heap_refcount.c b/src/duk_heap_refcount.c
index ccdd167738..78fcf981e1 100644
--- a/src/duk_heap_refcount.c
+++ b/src/duk_heap_refcount.c
@@ -119,7 +119,7 @@ DUK_LOCAL void duk__refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h)
for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
duk_activation *act = t->callstack + i;
- duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->func);
+ duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_ACT_GET_FUNC(act));
duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->var_env);
duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->lex_env);
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
diff --git a/src/duk_hobject_props.c b/src/duk_hobject_props.c
index 2baa6b67f2..1f7f2519a0 100644
--- a/src/duk_hobject_props.c
+++ b/src/duk_hobject_props.c
@@ -134,6 +134,12 @@ DUK_LOCAL duk_uint32_t duk__push_tval_to_hstring_arr_idx(duk_context *ctx, duk_t
return arr_idx;
}
+/* String is an own (virtual) property of a lightfunc. */
+static duk_bool_t duk__key_is_lightfunc_ownprop(duk_hthread *thr, duk_hstring *key) {
+ return (key == DUK_HTHREAD_STRING_LENGTH(thr) ||
+ key == DUK_HTHREAD_STRING_NAME(thr));
+}
+
/*
* Helpers for managing property storage size
*/
@@ -2221,6 +2227,30 @@ DUK_INTERNAL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj,
break;
}
+ case DUK_TAG_LIGHTFUNC: {
+ duk_int_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_obj);
+
+ /* FIXME: remaining virtual properties */
+
+ /* Must coerce key: if key is an object, it may coerce to e.g. 'length'. */
+ arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+
+ if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+ duk_int_t lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags);
+ duk_pop(ctx);
+ duk_push_int(ctx, lf_len);
+ return 1;
+ } else if (key == DUK_HTHREAD_STRING_NAME(thr)) {
+ duk_pop(ctx);
+ duk_push_lightfunc_name(ctx, tv_obj);
+ return 1;
+ }
+
+ DUK_DDD(DUK_DDDPRINT("base object is a lightfunc, start lookup from function prototype"));
+ curr = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
+ goto lookup; /* avoid double coercion */
+ }
+
default: {
/* number */
DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
@@ -2399,18 +2429,47 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj,
DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
tv_key = &tv_key_copy;
- if (!DUK_TVAL_IS_OBJECT(tv_obj)) {
+ /*
+ * The 'in' operator requires an object as its right hand side,
+ * throwing a TypeError unconditionally if this is not the case.
+ *
+ * However, lightfuncs need to behave like fully fledged objects
+ * here to be maximally transparent, so we need to handle them
+ * here.
+ */
+
+ /* FIXME: refactor to avoid double call for key coercion */
+ /* FIXME: remaining virtual properties */
+ if (DUK_TVAL_IS_OBJECT(tv_obj)) {
+ obj = DUK_TVAL_GET_OBJECT(tv_obj);
+ DUK_ASSERT(obj != NULL);
+
+ arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj)) {
+ arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+
+ if (duk__key_is_lightfunc_ownprop(thr, key)) {
+ /* FOUND */
+ rc = 1;
+ goto pop_and_return;
+ }
+
+ /* If not found, resume existence check from Function.prototype.
+ * We can just substitute the value in this case; nothing will
+ * need the original base value (as would be the case with e.g.
+ * setters/getters.
+ */
+ obj = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
+ } else {
/* Note: unconditional throw */
DUK_DDD(DUK_DDDPRINT("base object is not an object -> reject"));
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_BASE);
}
- obj = DUK_TVAL_GET_OBJECT(tv_obj);
- DUK_ASSERT(obj != NULL);
/* XXX: fast path for arrays? */
- arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
DUK_ASSERT(key != NULL);
+ DUK_ASSERT(obj != NULL);
DUK_UNREF(arr_idx);
#if defined(DUK_USE_ES6_PROXY)
@@ -2464,6 +2523,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj,
rc = duk__get_property_desc(thr, obj, key, &desc, 0 /*flags*/); /* don't push value */
+ pop_and_return:
duk_pop(ctx); /* [ key ] -> [] */
return rc;
}
@@ -3067,6 +3127,25 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj,
break;
}
+ case DUK_TAG_LIGHTFUNC: {
+ /* All lightfunc own properties are non-writable and the lightfunc
+ * is considered non-extensible. However, the write may be captured
+ * by an inherited setter which means we can't stop the lookup here.
+ */
+
+ /* FIXME: remaining virtual properties */
+
+ arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+
+ if (duk__key_is_lightfunc_ownprop(thr, key)) {
+ goto fail_not_writable;
+ }
+
+ DUK_DDD(DUK_DDDPRINT("base object is a lightfunc, start lookup from function prototype"));
+ curr = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
+ goto lookup; /* avoid double coercion */
+ }
+
default: {
/* number */
DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
@@ -3894,6 +3973,18 @@ DUK_INTERNAL duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj,
arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
goto fail_not_configurable;
}
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj)) {
+ /* Lightfunc virtual properties are non-configurable, so
+ * reject if match any of them.
+ */
+
+ duk_to_string(ctx, -1);
+ key = duk_get_hstring(ctx, -1);
+ DUK_ASSERT(key != NULL);
+
+ if (duk__key_is_lightfunc_ownprop(thr, key)) {
+ goto fail_not_configurable;
+ }
}
/* non-object base, no offending virtual property */
@@ -4372,11 +4463,18 @@ DUK_LOCAL void duk__normalize_property_descriptor(duk_context *ctx) {
* property descriptor with 'missing values' so it's easier to avoid it
* entirely.
*
+ * Note: this is only called for actual objects, not primitive values.
+ * Thist must support virtual properties for full objects (e.g. Strings)
+ * but not for plain values (e.g. lightfuncs).
+ *
* This is a Duktape/C function.
*/
-/* XXX: this is a major target for size optimization */
+/* FIXME: lightfunc support: because this operation can be done on objects,
+ * lightfuncs must also be supported here...
+ */
+/* XXX: this is a major target for size optimization */
DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *obj;
diff --git a/src/duk_hthread.h b/src/duk_hthread.h
index d0cbbfea31..32143100df 100644
--- a/src/duk_hthread.h
+++ b/src/duk_hthread.h
@@ -54,6 +54,8 @@
#define DUK_ACT_FLAG_PREVENT_YIELD (1 << 3) /* activation prevents yield (native call or "new") */
#define DUK_ACT_FLAG_DIRECT_EVAL (1 << 4) /* activation is a direct eval call */
+#define DUK_ACT_GET_FUNC(act) ((act)->func)
+
/*
* Flags for __FILE__ / __LINE__ registered into tracedata
*/
@@ -133,9 +135,14 @@
* Struct defines
*/
-/* Note: it's nice if size is 2^N (now 32 bytes on 32 bit without 'caller' property) */
+/* FIXME: for a memory-code tradeoff, remove 'func' and make it's access either a function
+ * or a macro. This would make the activation 32 bytes long on 32-bit platforms again.
+ */
+
+/* Note: it's nice if size is 2^N (at least for 32-bit platforms). */
struct duk_activation {
- duk_hobject *func; /* function being executed; for bound function calls, this is the final, real function */
+ duk_tval tv_func; /* borrowed: full duk_tval for function being executed; for lightfuncs */
+ duk_hobject *func; /* borrowed: function being executed; for bound function calls, this is the final, real function, NULL for lightfuncs */
duk_hobject *var_env; /* current variable environment (may be NULL if delayed) */
duk_hobject *lex_env; /* current lexical environment (may be NULL if delayed) */
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
@@ -179,11 +186,6 @@ struct duk_activation {
* (calling) valstack. This works for everything except tail
* calls, which must not "cumulate" valstack temps.
*/
-
-#if defined(DUK_USE_32BIT_PTRS) && !defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
- /* Minor optimization: pad structure to 2^N size on 32-bit platforms. */
- duk_int_t unused1; /* pad to 2^N */
-#endif
};
/* Note: it's nice if size is 2^N (not 4x4 = 16 bytes on 32 bit) */
diff --git a/src/duk_hthread_builtins.c b/src/duk_hthread_builtins.c
index 9beee48b89..1ffa4c3bee 100644
--- a/src/duk_hthread_builtins.c
+++ b/src/duk_hthread_builtins.c
@@ -6,6 +6,10 @@
#include "duk_internal.h"
+/* FIXME: lightfunc check for other function values - constructors like
+ * Number etc?
+ */
+
/*
* Encoding constants, must match genbuiltins.py
*/
@@ -43,6 +47,17 @@
* by genbuiltins.py.
*/
+#ifdef DUK_USE_LIGHTFUNC_BUILTINS
+/* FIXME: test function */
+static int duk__lightfunc_test(duk_context *ctx) {
+ int v1 = duk_get_int(ctx, 0);
+ int v2 = duk_get_int(ctx, 1);
+ fprintf(stderr, "Lightfunc called, top=%d, args: %d %d\n", duk_get_top(ctx), v1, v2);
+ duk_push_int(ctx, v1 + v2);
+ return 1;
+}
+#endif
+
DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
duk_context *ctx = (duk_context *) thr;
duk_bitdecoder_ctx bd_ctx;
@@ -96,6 +111,7 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
}
/* XXX: set magic directly here? (it could share the c_nargs arg) */
+ DUK_D(DUK_DPRINT("FIXME: lightweight potential?"));
duk_push_c_function_noexotic(ctx, c_func, c_nargs);
h = duk_require_hobject(ctx, -1);
@@ -338,6 +354,7 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
c_func_getter = duk_bi_native_functions[natidx_getter];
c_func_setter = duk_bi_native_functions[natidx_setter];
+ DUK_D(DUK_DPRINT("FIXME: lightweight potential?"));
duk_push_c_function_noconstruct_noexotic(ctx, c_func_getter, 0); /* always 0 args */
duk_push_c_function_noconstruct_noexotic(ctx, c_func_setter, 1); /* always 1 arg */
@@ -377,6 +394,9 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
duk_int16_t magic;
duk_c_function c_func;
duk_hnativefunction *h_func;
+#if defined(DUK_USE_LIGHTFUNC_BUILTINS)
+ duk_small_int_t lightfunc_eligible;
+#endif
stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS);
natidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS);
@@ -393,6 +413,43 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
(long) i, (long) j, (long) stridx, (long) natidx, (long) c_length,
(c_nargs == DUK_VARARGS ? (long) -1 : (long) c_nargs)));
+ /* Cast converts magic to 16-bit signed value */
+ magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0);
+
+#if defined(DUK_USE_LIGHTFUNC_BUILTINS)
+ /* FIXME: hardcoded values, use constants */
+ /* FIXME: fix awkward control flow */
+ lightfunc_eligible =
+ ((c_nargs >= 0 && c_nargs <= 0x0e) || (c_nargs == DUK_VARARGS)) &&
+ (c_length <= 0x0f) &&
+ (magic >= -0x80 && magic <= 0x7f);
+ if (stridx == DUK_STRIDX_EVAL ||
+ stridx == DUK_STRIDX_YIELD ||
+ stridx == DUK_STRIDX_RESUME ||
+ stridx == DUK_STRIDX_REQUIRE) {
+ /* These functions have trouble working as lightfuncs.
+ * Some of them have specific asserts and some may have
+ * additional properties (e.g. 'require.id' may be written).
+ */
+ DUK_D(DUK_DPRINT("reject as lightfunc: stridx=%d, i=%d, j=%d", (int) stridx, (int) i, (int) j));
+ lightfunc_eligible = 0;
+ }
+
+ if (lightfunc_eligible) {
+ duk_tval tv_lfunc;
+ duk_small_uint_t lf_flags =
+ ((magic << 8) & 0xff00UL) |
+ (c_length << 4) |
+ (c_nargs == DUK_VARARGS ? 0x0f : c_nargs);
+ DUK_TVAL_SET_LIGHTFUNC(&tv_lfunc, c_func, lf_flags);
+ duk_push_tval(ctx, &tv_lfunc);
+ DUK_D(DUK_DPRINT("built-in function eligible as light function: i=%d, j=%d c_length=%ld, c_nargs=%ld, magic=%ld", (int) i, (int) j, (long) c_length, (long) c_nargs, (long) magic));
+ goto lightfunc_skip;
+ }
+#endif /* DUK_USE_LIGHTFUNC_BUILTINS */
+
+ DUK_D(DUK_DPRINT("built-in function NOT ELIGIBLE as light function: i=%d, j=%d c_length=%ld, c_nargs=%ld, magic=%ld", (int) i, (int) j, (long) c_length, (long) c_nargs, (long) magic));
+
/* [ (builtin objects) ] */
duk_push_c_function_noconstruct_noexotic(ctx, c_func, c_nargs);
@@ -415,8 +472,6 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
/* XXX: any way to avoid decoding magic bit; there are quite
* many function properties and relatively few with magic values.
*/
- /* Cast converts magic to 16-bit signed value */
- magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0);
h_func->magic = magic;
/* [ (builtin objects) func ] */
@@ -439,6 +494,10 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
* function valued properties of built-in objects now.
*/
+#if defined(DUK_USE_LIGHTFUNC_BUILTINS)
+ lightfunc_skip:
+#endif
+
duk_def_prop_stridx(ctx, i, stridx, DUK_PROPDESC_FLAGS_WC);
/* [ (builtin objects) ] */
@@ -577,6 +636,16 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
}
#endif
+#ifdef DUK_USE_LIGHTFUNC_BUILTINS /* FIXME: lightfunc testing hack */
+ {
+ duk_tval tv_lfunc;
+ DUK_TVAL_SET_LIGHTFUNC(&tv_lfunc, duk__lightfunc_test, DUK_LFUNC_FLAGS_PACK(0, 2, 2));
+ duk_push_string(ctx, "fixme_lightfunc_test");
+ duk_push_tval(ctx, &tv_lfunc);
+ duk_def_prop(ctx, DUK_BIDX_DUKTAPE, DUK_PROPDESC_FLAGS_WC);
+ }
+#endif
+
/*
* Pop built-ins from stack: they are now INCREF'd and
* reachable from the builtins[] array.
diff --git a/src/duk_hthread_stacks.c b/src/duk_hthread_stacks.c
index c731b2894f..b2eb8642c7 100644
--- a/src/duk_hthread_stacks.c
+++ b/src/duk_hthread_stacks.c
@@ -128,7 +128,8 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
idx = thr->callstack_top;
while (idx > new_top) {
- duk_activation *p;
+ duk_activation *act;
+ duk_hobject *func;
#ifdef DUK_USE_REFERENCE_COUNTING
duk_hobject *tmp;
#endif
@@ -137,48 +138,49 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
DUK_ASSERT_DISABLE(idx >= 0); /* unsigned */
DUK_ASSERT((duk_size_t) idx < thr->callstack_size); /* true, despite side effect resizes */
- p = thr->callstack + idx;
- DUK_ASSERT(p->func != NULL);
+ act = thr->callstack + idx;
+ /* With lightfuncs, act 'func' may be NULL */
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
/*
* Restore 'caller' property for non-strict callee functions.
*/
- if (!DUK_HOBJECT_HAS_STRICT(p->func)) {
+ func = DUK_ACT_GET_FUNC(act);
+ if (func != NULL && !DUK_HOBJECT_HAS_STRICT(func)) {
duk_tval *tv_caller;
duk_tval tv_tmp;
duk_hobject *h_tmp;
- tv_caller = duk_hobject_find_existing_entry_tval_ptr(p->func, DUK_HTHREAD_STRING_CALLER(thr));
+ tv_caller = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_CALLER(thr));
- /* The p->prev_caller should only be set if the entry for 'caller'
+ /* The act->prev_caller should only be set if the entry for 'caller'
* exists (as it is only set in that case, and the property is not
* configurable), but handle all the cases anyway.
*/
if (tv_caller) {
DUK_TVAL_SET_TVAL(&tv_tmp, tv_caller);
- if (p->prev_caller) {
- /* Just transfer the refcount from p->prev_caller to tv_caller,
+ if (act->prev_caller) {
+ /* Just transfer the refcount from act->prev_caller to tv_caller,
* so no need for a refcount update. This is the expected case.
*/
- DUK_TVAL_SET_OBJECT(tv_caller, p->prev_caller);
- p->prev_caller = NULL;
+ DUK_TVAL_SET_OBJECT(tv_caller, act->prev_caller);
+ act->prev_caller = NULL;
} else {
DUK_TVAL_SET_NULL(tv_caller); /* no incref needed */
- DUK_ASSERT(p->prev_caller == NULL);
+ DUK_ASSERT(act->prev_caller == NULL);
}
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
} else {
- h_tmp = p->prev_caller;
+ h_tmp = act->prev_caller;
if (h_tmp) {
- p->prev_caller = NULL;
+ act->prev_caller = NULL;
DUK_HOBJECT_DECREF(thr, h_tmp); /* side effects */
}
}
- p = thr->callstack + idx; /* avoid side effects */
- DUK_ASSERT(p->prev_caller == NULL);
+ act = thr->callstack + idx; /* avoid side effects */
+ DUK_ASSERT(act->prev_caller == NULL);
}
#endif
@@ -192,46 +194,49 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
* environment is created for e.g. an eval call, it must not be closed.
*/
- if (!DUK_HOBJECT_HAS_NEWENV(p->func)) {
+ func = DUK_ACT_GET_FUNC(act);
+ if (func != NULL && !DUK_HOBJECT_HAS_NEWENV(func)) {
DUK_DDD(DUK_DDDPRINT("skip closing environments, envs not owned by this activation"));
goto skip_env_close;
}
- DUK_ASSERT(p->lex_env == p->var_env);
- if (p->var_env != NULL) {
+ /* FIXME: lightfunc handling, explicit check or comment */
+
+ DUK_ASSERT(act->lex_env == act->var_env);
+ if (act->var_env != NULL) {
DUK_DDD(DUK_DDDPRINT("closing var_env record %p -> %!O",
- (void *) p->var_env, (duk_heaphdr *) p->var_env));
- duk_js_close_environment_record(thr, p->var_env, p->func, p->idx_bottom);
- p = thr->callstack + idx; /* avoid side effect issues */
+ (void *) act->var_env, (duk_heaphdr *) act->var_env));
+ duk_js_close_environment_record(thr, act->var_env, DUK_ACT_GET_FUNC(act), act->idx_bottom);
+ act = thr->callstack + idx; /* avoid side effect issues */
}
#if 0
- if (p->lex_env != NULL) {
- if (p->lex_env == p->var_env) {
+ if (act->lex_env != NULL) {
+ if (act->lex_env == act->var_env) {
/* common case, already closed, so skip */
DUK_DD(DUK_DDPRINT("lex_env and var_env are the same and lex_env "
"already closed -> skip closing lex_env"));
;
} else {
DUK_DD(DUK_DDPRINT("closing lex_env record %p -> %!O",
- (void *) p->lex_env, (duk_heaphdr *) p->lex_env));
- duk_js_close_environment_record(thr, p->lex_env, p->func, p->idx_bottom);
- p = thr->callstack + idx; /* avoid side effect issues */
+ (void *) act->lex_env, (duk_heaphdr *) act->lex_env));
+ duk_js_close_environment_record(thr, act->lex_env, DUK_ACT_GET_FUNC(act), act->idx_bottom);
+ act = thr->callstack + idx; /* avoid side effect issues */
}
}
#endif
- DUK_ASSERT((p->lex_env == NULL) ||
- ((duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
- (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
- (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
- (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
+ DUK_ASSERT((act->lex_env == NULL) ||
+ ((duk_hobject_find_existing_entry_tval_ptr(act->lex_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
+ (duk_hobject_find_existing_entry_tval_ptr(act->lex_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
+ (duk_hobject_find_existing_entry_tval_ptr(act->lex_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
+ (duk_hobject_find_existing_entry_tval_ptr(act->lex_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
- DUK_ASSERT((p->var_env == NULL) ||
- ((duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
- (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
- (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
- (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
+ DUK_ASSERT((act->var_env == NULL) ||
+ ((duk_hobject_find_existing_entry_tval_ptr(act->var_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
+ (duk_hobject_find_existing_entry_tval_ptr(act->var_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
+ (duk_hobject_find_existing_entry_tval_ptr(act->var_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
+ (duk_hobject_find_existing_entry_tval_ptr(act->var_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
skip_env_close:
@@ -239,7 +244,7 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
* Update preventcount
*/
- if (p->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
+ if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
DUK_ASSERT(thr->callstack_preventcount >= 1);
thr->callstack_preventcount--;
}
@@ -254,34 +259,34 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
*/
#ifdef DUK_USE_REFERENCE_COUNTING
- tmp = p->var_env;
+ tmp = act->var_env;
#endif
- p->var_env = NULL;
+ act->var_env = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
DUK_HOBJECT_DECREF(thr, tmp);
- p = thr->callstack + idx; /* avoid side effect issues */
+ act = thr->callstack + idx; /* avoid side effect issues */
#endif
#ifdef DUK_USE_REFERENCE_COUNTING
- tmp = p->lex_env;
+ tmp = act->lex_env;
#endif
- p->lex_env = NULL;
+ act->lex_env = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
DUK_HOBJECT_DECREF(thr, tmp);
- p = thr->callstack + idx; /* avoid side effect issues */
+ act = thr->callstack + idx; /* avoid side effect issues */
#endif
/* Note: this may cause a corner case situation where a finalizer
* may see a currently reachable activation whose 'func' is NULL.
*/
#ifdef DUK_USE_REFERENCE_COUNTING
- tmp = p->func;
+ tmp = DUK_ACT_GET_FUNC(act);
#endif
- p->func = NULL;
+ act->func = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
DUK_HOBJECT_DECREF(thr, tmp);
- p = thr->callstack + idx; /* avoid side effect issues */
- DUK_UNREF(p);
+ act = thr->callstack + idx; /* avoid side effect issues */
+ DUK_UNREF(act);
#endif
}
@@ -293,8 +298,8 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
*/
#if 0
if (thr->callstack_top > 0) {
- duk_activation *p = thr->callstack + thr->callstack_top - 1;
- p->idx_retval = 0;
+ duk_activation *act = thr->callstack + thr->callstack_top - 1;
+ act->idx_retval = 0;
}
#endif
diff --git a/src/duk_js.h b/src/duk_js.h
index 112d046690..19d996b173 100644
--- a/src/duk_js.h
+++ b/src/duk_js.h
@@ -86,7 +86,7 @@ void duk_js_push_closure(duk_hthread *thr,
/* call handling */
DUK_INTERNAL_DECL duk_int_t duk_handle_call(duk_hthread *thr, duk_idx_t num_stack_args, duk_small_uint_t call_flags);
DUK_INTERNAL_DECL duk_int_t duk_handle_safe_call(duk_hthread *thr, duk_safe_call_function func, duk_idx_t num_stack_args, duk_idx_t num_stack_res);
-DUK_INTERNAL_DECL void duk_handle_ecma_call_setup(duk_hthread *thr, duk_idx_t num_stack_args, duk_small_uint_t call_flags);
+DUK_INTERNAL_DECL duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr, duk_idx_t num_stack_args, duk_small_uint_t call_flags);
/* bytecode execution */
DUK_INTERNAL_DECL void duk_js_execute_bytecode(duk_hthread *entry_thread);
diff --git a/src/duk_js_call.c b/src/duk_js_call.c
index 0b030b673b..9316aa47c0 100644
--- a/src/duk_js_call.c
+++ b/src/duk_js_call.c
@@ -318,43 +318,58 @@ void duk__handle_createargs_for_call(duk_hthread *thr,
* function. This would make call time handling much easier.
*/
+/* FIXME: lightfunc handling */
DUK_LOCAL
void duk__handle_bound_chain_for_call(duk_hthread *thr,
duk_idx_t idx_func,
duk_idx_t *p_num_stack_args, /* may be changed by call */
- duk_hobject **p_func, /* changed by call */
duk_bool_t is_constructor_call) {
duk_context *ctx = (duk_context *) thr;
duk_idx_t num_stack_args;
+ duk_tval *tv_func;
duk_hobject *func;
duk_uint_t sanity;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(p_num_stack_args != NULL);
- DUK_ASSERT(p_func != NULL);
- DUK_ASSERT(*p_func != NULL);
- DUK_ASSERT(DUK_HOBJECT_HAS_BOUND(*p_func));
+
+ /* On entry, item at idx_func is a bound, non-lightweight function,
+ * but we don't rely on that below.
+ */
num_stack_args = *p_num_stack_args;
- func = *p_func;
sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
do {
duk_idx_t i, len;
- if (!DUK_HOBJECT_HAS_BOUND(func)) {
+ tv_func = duk_require_tval(ctx, idx_func);
+ DUK_ASSERT(tv_func != NULL);
+
+ if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
+ /* Lightweight function: never bound, so terminate. */
break;
+ } else if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ if (!DUK_HOBJECT_HAS_BOUND(func)) {
+ /* Normal non-bound function. */
+ break;
+ }
+ } else {
+ /* Function.prototype.bind() should never let this happen,
+ * ugly error message is enough.
+ */
+ DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
}
-
- DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p", (void *) func));
+ DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv_func) != NULL);
/* XXX: this could be more compact by accessing the internal properties
* directly as own properties (they cannot be inherited, and are not
* externally visible).
*/
- DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%ld",
- (void *) func, (long) num_stack_args));
+ DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%ld: %!T",
+ (void *) DUK_TVAL_GET_OBJECT(tv), (long) num_stack_args, tv_func));
/* [ ... func this arg1 ... argN ] */
@@ -389,25 +404,32 @@ void duk__handle_bound_chain_for_call(duk_hthread *thr,
/* [ ... func this arg1 ... argN ] */
duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_TARGET);
- duk_replace(ctx, idx_func); /* replace also in stack; not strictly necessary */
- func = duk_require_hobject(ctx, idx_func);
+ duk_replace(ctx, idx_func); /* replace in stack */
- DUK_DDD(DUK_DDDPRINT("bound function handled, num_stack_args=%ld, idx_func=%ld",
- (long) num_stack_args, (long) idx_func));
+ DUK_DDD(DUK_DDDPRINT("bound function handled, num_stack_args=%ld, idx_func=%ld, curr func=%!T",
+ (long) num_stack_args, (long) idx_func, duk_get_tval(ctx, idx_func)));
} while (--sanity > 0);
if (sanity == 0) {
DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT);
}
- DUK_DDD(DUK_DDDPRINT("final non-bound function is: %p", (void *) func));
+ DUK_DDD(DUK_DDDPRINT("final non-bound function is: %!T", duk_get_tval(ctx, idx_func)));
- DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
- DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
+#ifdef DUK_USE_ASSERTIONS
+ tv_func = duk_require_tval(ctx, idx_func);
+ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_func) || DUK_TVAL_IS_OBJECT(tv_func));
+ if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ DUK_ASSERT(func != NULL);
+ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+ DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) ||
+ DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
+ }
+#endif
/* write back */
*p_num_stack_args = num_stack_args;
- *p_func = func;
}
/*
@@ -561,8 +583,15 @@ void duk__coerce_effective_this_binding(duk_hthread *thr,
duk_hobject *func,
duk_idx_t idx_this) {
duk_context *ctx = (duk_context *) thr;
+ duk_small_int_t strict;
- if (DUK_HOBJECT_HAS_STRICT(func)) {
+ if (func) {
+ strict = DUK_HOBJECT_HAS_STRICT(func);
+ } else {
+ strict = 1; /* FIXME: lightweight, bit? */
+ }
+
+ if (strict) {
DUK_DDD(DUK_DDDPRINT("this binding: strict -> use directly"));
} else {
duk_tval *tv_this = duk_require_tval(ctx, idx_this);
@@ -652,7 +681,8 @@ duk_int_t duk_handle_call(duk_hthread *thr,
duk_idx_t nargs; /* # argument registers target function wants (< 0 => "as is") */
duk_idx_t nregs; /* # total registers target function wants on entry (< 0 => "as is") */
duk_size_t vs_min_size;
- duk_hobject *func; /* 'func' on stack (borrowed reference) */
+ duk_hobject *func; /* 'func' on stack (borrowed reference) */
+ duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) */
duk_activation *act;
duk_hobject *env;
duk_jmpbuf our_jmpbuf;
@@ -908,35 +938,54 @@ duk_int_t duk_handle_call(duk_hthread *thr,
}
/*
- * Check the function type, handle bound function chains,
- * and prepare parameters for the rest of the call handling.
- * Also figure out the effective 'this' binding, which
- * replaces the current value at idx_func + 1.
+ * Check the function type, handle bound function chains, and prepare
+ * parameters for the rest of the call handling. Also figure out the
+ * effective 'this' binding, which replaces the current value at
+ * idx_func + 1.
+ *
+ * If the target function is a 'bound' one, follow the chain of 'bound'
+ * functions until a non-bound function is found. During this process,
+ * bound arguments are 'prepended' to existing ones, and the "this"
+ * binding is overridden. See E5 Section 15.3.4.5.1.
*
- * If the target function is a 'bound' one, follow the chain
- * of 'bound' functions until a non-bound function is found.
- * During this process, bound arguments are 'prepended' to
- * existing ones, and the "this" binding is overridden.
- * See E5 Section 15.3.4.5.1.
+ * Lightfunc detection happens here too. Note that lightweight functions
+ * can be wrapped by (non-lightweight) bound functions so we must resolve
+ * the bound function chain first.
*/
if (!duk_is_callable(thr, idx_func)) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
}
- func = duk_get_hobject(thr, idx_func);
- DUK_ASSERT(func != NULL);
+ tv_func = duk_get_tval(ctx, idx_func);
+ DUK_ASSERT(tv_func != NULL);
+ /* FIXME: avoid calling duk_is_callable() in the first place? */
+
+ if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ if (DUK_HOBJECT_HAS_BOUND(func)) {
+ duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+ }
+ }
+ tv_func = NULL; /* invalidated */
+
+ tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
+ DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) ||
+ DUK_TVAL_IS_LIGHTFUNC(tv_func));
- if (DUK_HOBJECT_HAS_BOUND(func)) {
- /* slow path for bound functions */
- duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, &func, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+ if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ } else {
+ func = NULL;
}
- DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func) ||
- DUK_HOBJECT_IS_NATIVEFUNCTION(func));
+
+ DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func));
+ DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPILEDFUNCTION(func) ||
+ DUK_HOBJECT_IS_NATIVEFUNCTION(func)));
duk__coerce_effective_this_binding(thr, func, idx_func + 1);
DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T",
(duk_tval *) duk_get_tval(ctx, idx_func + 1)));
+ tv_func = NULL; /* invalidated */
/* These base values are never used, but if the compiler doesn't know
* that DUK_ERROR() won't return, these are needed to silence warnings.
@@ -946,7 +995,21 @@ duk_int_t duk_handle_call(duk_hthread *thr,
nargs = 0; DUK_UNREF(nargs);
nregs = 0; DUK_UNREF(nregs);
- if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+ if (func == NULL) {
+ duk_small_uint_t lf_flags;
+
+ DUK_DDD(DUK_DDDPRINT("lightfunc call handling"));
+ tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
+ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_func));
+ lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_func);
+ func = NULL;
+ nargs = lf_flags & 0x0f; /* FIXME: constants */
+ if (nargs == 0x0f) {
+ nargs = -1; /* vararg */
+ }
+ nregs = nargs;
+ /* FIXME: extract magic here from lf_flags directly */
+ } else if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
nargs = ((duk_hcompiledfunction *) func)->nargs;
nregs = ((duk_hcompiledfunction *) func)->nregs;
DUK_ASSERT(nregs >= nargs);
@@ -981,7 +1044,8 @@ duk_int_t duk_handle_call(duk_hthread *thr,
vs_min_size = (thr->valstack_bottom - thr->valstack) + /* bottom of current func */
idx_args; /* bottom of new func */
vs_min_size += (nregs >= 0 ? nregs : num_stack_args); /* num entries of new func at entry */
- if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+ if (func == NULL || DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+ /* FIXME: lightweight funcs must be handled above */
vs_min_size += DUK_VALSTACK_API_ENTRY_MINIMUM; /* Duktape/C API guaranteed entries (on top of args) */
}
vs_min_size += DUK_VALSTACK_INTERNAL_EXTRA, /* + spare */
@@ -1023,17 +1087,17 @@ duk_int_t duk_handle_call(duk_hthread *thr,
thr->callstack_top++;
DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
DUK_ASSERT(thr->valstack_top > thr->valstack_bottom); /* at least effective 'this' */
- DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+ DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func));
act->flags = 0;
- if (DUK_HOBJECT_HAS_STRICT(func)) {
+ if (func == NULL || DUK_HOBJECT_HAS_STRICT(func)) {
act->flags |= DUK_ACT_FLAG_STRICT;
}
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT;
/*act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;*/
}
- if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+ if (func == NULL || DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
/*act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;*/
}
if (call_flags & DUK_CALL_FLAG_DIRECT_EVAL) {
@@ -1045,7 +1109,7 @@ duk_int_t duk_handle_call(duk_hthread *thr,
*/
act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;
- act->func = func;
+ act->func = func; /* FIXME: this is an issue */
act->var_env = NULL;
act->lex_env = NULL;
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
@@ -1056,16 +1120,25 @@ duk_int_t duk_handle_call(duk_hthread *thr,
#if 0 /* topmost activation idx_retval is considered garbage, no need to init */
act->idx_retval = 0;
#endif
+ if (func == NULL) {
+ /* FIXME: use macro, avoid block */
+ /* FIXME: setting tv_func in other cases */
+ tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
+ DUK_TVAL_SET_TVAL(&act->tv_func, tv_func); /* borrowed, no refcount */
+ }
if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
/* duk_hthread_callstack_unwind() will decrease this on unwind */
thr->callstack_preventcount++;
}
+ /* FIXME: lightfunc? */
DUK_HOBJECT_INCREF(thr, func); /* act->func */
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
- duk__update_func_caller_prop(thr, func);
+ if (func) {
+ duk__update_func_caller_prop(thr, func);
+ }
act = thr->callstack + thr->callstack_top - 1;
#endif
@@ -1089,9 +1162,9 @@ duk_int_t duk_handle_call(duk_hthread *thr,
* Delayed creation (on demand) is handled in duk_js_var.c.
*/
- DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); /* bound function chain has already been resolved */
+ DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func)); /* bound function chain has already been resolved */
- if (!DUK_HOBJECT_HAS_NEWENV(func)) {
+ if (func != NULL && !DUK_HOBJECT_HAS_NEWENV(func)) {
/* use existing env (e.g. for non-strict eval); cannot have
* an own 'arguments' object (but can refer to the existing one)
*/
@@ -1105,9 +1178,9 @@ duk_int_t duk_handle_call(duk_hthread *thr,
goto env_done;
}
- DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
+ DUK_ASSERT(func == NULL || DUK_HOBJECT_HAS_NEWENV(func));
- if (!DUK_HOBJECT_HAS_CREATEARGS(func)) {
+ if (func == NULL || !DUK_HOBJECT_HAS_CREATEARGS(func)) {
/* no need to create environment record now; leave as NULL */
DUK_ASSERT(act->lex_env == NULL);
DUK_ASSERT(act->var_env == NULL);
@@ -1156,7 +1229,7 @@ duk_int_t duk_handle_call(duk_hthread *thr,
* Determine call type; then setup activation and call
*/
- if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+ if (func != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
goto ecmascript_call;
} else {
goto native_call;
@@ -1177,7 +1250,7 @@ duk_int_t duk_handle_call(duk_hthread *thr,
DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
- DUK_ASSERT(((duk_hnativefunction *) func)->func != NULL);
+ DUK_ASSERT(func == NULL || ((duk_hnativefunction *) func)->func != NULL);
/* [... func this | arg1 ... argN] ('this' must precede new bottom) */
@@ -1191,7 +1264,13 @@ duk_int_t duk_handle_call(duk_hthread *thr,
* other invalid
*/
- rc = ((duk_hnativefunction *) func)->func((duk_context *) thr);
+ if (func) {
+ rc = ((duk_hnativefunction *) func)->func((duk_context *) thr);
+ } else {
+ /* FIXME: lightfunc */
+ duk_c_function funcptr = DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv_func);
+ rc = funcptr((duk_context *) thr);
+ }
if (rc < 0) {
duk_error_throw_from_negative_rc(thr, rc);
@@ -1796,18 +1875,24 @@ duk_int_t duk_handle_safe_call(duk_hthread *thr,
* The callstack of the target contains an earlier Ecmascript call in case
* of an Ecmascript-to-Ecmascript call (whose idx_retval is updated), or
* is empty in case of an initial Duktape.Thread.resume().
+ *
+ * The first thing to do here is to figure out whether an ecma-to-ecma
+ * call is actually possible. It's not always the case if the target is
+ * a bound function; the final function may be native. In that case,
+ * return an error so caller can fall back to a normal call path.
*/
DUK_INTERNAL
-void duk_handle_ecma_call_setup(duk_hthread *thr,
- duk_idx_t num_stack_args,
- duk_small_uint_t call_flags) {
+duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
+ duk_idx_t num_stack_args,
+ duk_small_uint_t call_flags) {
duk_context *ctx = (duk_context *) thr;
duk_size_t entry_valstack_bottom_index;
duk_idx_t idx_func; /* valstack index of 'func' and retval (relative to entry valstack_bottom) */
duk_idx_t idx_args; /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */
duk_idx_t nargs; /* # argument registers target function wants (< 0 => never for ecma calls) */
duk_idx_t nregs; /* # total registers target function wants on entry (< 0 => never for ecma calls) */
+ duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) */
duk_hobject *func; /* 'func' on stack (borrowed reference) */
duk_activation *act;
duk_hobject *env;
@@ -1840,8 +1925,8 @@ void duk_handle_ecma_call_setup(duk_hthread *thr,
our_callstack_index = thr->callstack_top - 1;
DUK_ASSERT_DISABLE(our_callstack_index >= 0);
DUK_ASSERT(our_callstack_index < thr->callstack_size);
- DUK_ASSERT(thr->callstack[our_callstack_index].func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[our_callstack_index].func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + our_callstack_index) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + our_callstack_index)));
/* No entry in the catchstack which would actually catch a
* throw can refer to the callstack entry being reused.
@@ -1883,34 +1968,61 @@ void duk_handle_ecma_call_setup(duk_hthread *thr,
}
/*
- * Check the function type, handle bound function chains,
- * and prepare parameters for the rest of the call handling.
- * Also figure out the effective 'this' binding, which replaces
- * the current value at idx_func + 1.
+ * Check the function type, handle bound function chains, and prepare
+ * parameters for the rest of the call handling. Also figure out the
+ * effective 'this' binding, which replaces the current value at
+ * idx_func + 1.
+ *
+ * If the target function is a 'bound' one, follow the chain of 'bound'
+ * functions until a non-bound function is found. During this process,
+ * bound arguments are 'prepended' to existing ones, and the "this"
+ * binding is overridden. See E5 Section 15.3.4.5.1.
*
- * If the target function is a 'bound' one, follow the chain
- * of 'bound' functions until a non-bound function is found.
- * During this process, bound arguments are 'prepended' to
- * existing ones, and the "this" binding is overridden.
- * See E5 Section 15.3.4.5.1.
+ * If the final target function cannot be handled by an ecma-to-ecma
+ * call, return to the caller with a return value indicating this case.
+ * The bound chain is resolved and the caller can resume with a plain
+ * function call.
*/
if (!duk_is_callable(thr, idx_func)) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
}
- func = duk_get_hobject(thr, idx_func);
- DUK_ASSERT(func != NULL);
-
- if (DUK_HOBJECT_HAS_BOUND(func)) {
- /* slow path for bound functions */
- duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, &func, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+ tv_func = duk_get_tval(ctx, idx_func);
+ DUK_ASSERT(tv_func != NULL);
+ /* FIXME: avoid calling duk_is_callable() in the first place? */
+
+ if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ if (DUK_HOBJECT_HAS_BOUND(func)) {
+ duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+ }
}
+ tv_func = NULL; /* invalidated */
+
+ tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
+ DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) ||
+ DUK_TVAL_IS_LIGHTFUNC(tv_func));
+
+ if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+ DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(func));
+ DUK_DDD(DUK_DDDPRINT("final target is a native function, cannot do ecma-to-ecma call"));
+ return 0;
+ }
+ } else {
+ DUK_DDD(DUK_DDDPRINT("final target is a lightfunc, cannot do ecma-to-ecma call"));
+ return 0;
+ }
+
+ DUK_ASSERT(func != NULL);
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func)); /* caller must ensure this */
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func));
duk__coerce_effective_this_binding(thr, func, idx_func + 1);
DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T",
duk_get_tval(ctx, idx_func + 1)));
+ tv_func = NULL; /* invalidated */
nargs = ((duk_hcompiledfunction *) func)->nargs;
nregs = ((duk_hcompiledfunction *) func)->nregs;
@@ -2035,7 +2147,7 @@ void duk_handle_ecma_call_setup(duk_hthread *thr,
DUK_ACT_FLAG_STRICT | DUK_ACT_FLAG_TAILCALLED :
DUK_ACT_FLAG_TAILCALLED);
- DUK_ASSERT(act->func == func); /* already updated */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(act) == func); /* already updated */
DUK_ASSERT(act->var_env == NULL); /* already NULLed (by unwind) */
DUK_ASSERT(act->lex_env == NULL); /* already NULLed (by unwind) */
DUK_ASSERT(act->pc == 0); /* already zeroed */
@@ -2112,8 +2224,8 @@ void duk_handle_ecma_call_setup(duk_hthread *thr,
DUK_ASSERT(thr->callstack_top < thr->callstack_size);
DUK_ASSERT(thr->callstack_top >= 1);
act = thr->callstack + thr->callstack_top - 1;
- DUK_ASSERT(act->func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(act->func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(act)));
act->idx_retval = entry_valstack_bottom_index + idx_func;
}
@@ -2242,4 +2354,6 @@ void duk_handle_ecma_call_setup(duk_hthread *thr,
* Return to bytecode executor, which will resume execution from
* the topmost activation.
*/
+
+ return 1;
}
diff --git a/src/duk_js_compiler.c b/src/duk_js_compiler.c
index bce839002b..b6dba69ad0 100644
--- a/src/duk_js_compiler.c
+++ b/src/duk_js_compiler.c
@@ -1834,6 +1834,10 @@ duk_regconst_t duk__ispec_toregconst_raw(duk_compiler_ctx *comp_ctx,
DUK_UNREACHABLE();
break;
}
+ case DUK_TAG_LIGHTFUNC: {
+ DUK_UNREACHABLE();
+ break;
+ }
default: {
/* number */
duk_reg_t dest;
diff --git a/src/duk_js_executor.c b/src/duk_js_executor.c
index c4d3f70017..4c4a049897 100644
--- a/src/duk_js_executor.c
+++ b/src/duk_js_executor.c
@@ -10,41 +10,6 @@
DUK_LOCAL_DECL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_small_uint_t retval_count);
-/*
- * Helper for finding the final non-bound function in a "bound function" chain.
- */
-
-/* XXX: overlap with other helpers, rework */
-DUK_LOCAL duk_hobject *duk__find_nonbound_function(duk_hthread *thr, duk_hobject *func) {
- duk_context *ctx = (duk_context *) thr;
- duk_uint_t sanity;
-
- DUK_ASSERT(thr != NULL);
- DUK_ASSERT(func != NULL);
- DUK_ASSERT(DUK_HOBJECT_HAS_BOUND(func));
-
- sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
- do {
- if (!DUK_HOBJECT_HAS_BOUND(func)) {
- break;
- }
-
- duk_push_hobject(ctx, func);
- duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);
- func = duk_require_hobject(ctx, -1);
- duk_pop_2(ctx);
- } while (--sanity > 0);
-
- if (sanity == 0) {
- DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT);
- }
-
- DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
- DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
-
- return func;
-}
-
/*
* Arithmetic, binary, and logical helpers.
*
@@ -477,8 +442,8 @@ DUK_LOCAL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_
DUK_ASSERT(thr != NULL);
DUK_ASSERT_DISABLE(act_idx >= 0); /* unsigned */
- DUK_ASSERT(thr->callstack[act_idx].func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[act_idx].func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + act_idx) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + act_idx)));
DUK_ASSERT_DISABLE(thr->callstack[act_idx].idx_retval >= 0); /* unsigned */
thr->valstack_bottom = thr->valstack + thr->callstack[act_idx].idx_bottom;
@@ -496,7 +461,7 @@ DUK_LOCAL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_
* top to 'nregs' always.
*/
- h_func = (duk_hcompiledfunction *) thr->callstack[act_idx].func;
+ h_func = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(thr->callstack + act_idx);
(void) duk_valstack_resize_raw((duk_context *) thr,
(thr->valstack_bottom - thr->valstack) + /* bottom of current func */
@@ -554,11 +519,11 @@ DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx
*/
DUK_ASSERT(thr->callstack_top >= 1);
- DUK_ASSERT(thr->callstack[thr->callstack_top - 1].func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[thr->callstack_top - 1].func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
thr->valstack_bottom = thr->valstack + (thr->callstack + thr->callstack_top - 1)->idx_bottom;
- duk_set_top((duk_context *) thr, ((duk_hcompiledfunction *) (thr->callstack + thr->callstack_top - 1)->func)->nregs);
+ duk_set_top((duk_context *) thr, ((duk_hcompiledfunction *) DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))->nregs);
/*
* Reset PC: resume execution from catch or finally jump slot.
@@ -600,7 +565,7 @@ DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx
}
DUK_ASSERT(act->lex_env != NULL);
DUK_ASSERT(act->var_env != NULL);
- DUK_ASSERT(act->func != NULL);
+ DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
DUK_UNREF(act); /* unreferenced without assertions */
act = thr->callstack + thr->callstack_top - 1;
@@ -654,8 +619,8 @@ DUK_LOCAL void duk__handle_label(duk_hthread *thr, duk_size_t cat_idx) {
act = thr->callstack + thr->callstack_top - 1;
- DUK_ASSERT(act->func != NULL);
- DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(act->func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(act)));
/* +0 = break, +1 = continue */
act->pc = thr->catchstack[cat_idx].pc_base + (thr->heap->lj.type == DUK_LJ_TYPE_CONTINUE ? 1 : 0);
@@ -668,7 +633,7 @@ DUK_LOCAL void duk__handle_label(duk_hthread *thr, duk_size_t cat_idx) {
#if defined(DUK_USE_ASSERTIONS)
act = thr->callstack + thr->callstack_top - 1;
DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) ==
- (duk_size_t) ((duk_hcompiledfunction *) act->func)->nregs);
+ (duk_size_t) ((duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act))->nregs);
#endif
}
@@ -683,8 +648,8 @@ DUK_LOCAL void duk__handle_yield(duk_hthread *thr, duk_hthread *resumer, duk_siz
* lj.value1 is correct.
*/
- DUK_ASSERT(resumer->callstack[act_idx].func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(resumer->callstack[act_idx].func)); /* resume caller must be an ecmascript func */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(resumer->callstack + act_idx) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(resumer->callstack + act_idx))); /* resume caller must be an ecmascript func */
DUK_DDD(DUK_DDDPRINT("resume idx_retval is %ld", (long) resumer->callstack[act_idx].idx_retval));
@@ -755,11 +720,11 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); /* unchanged by Duktape.Thread.resume() */
DUK_ASSERT(thr->callstack_top >= 2); /* Ecmascript activation + Duktape.Thread.resume() activation */
- DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL &&
- DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func) &&
- ((duk_hnativefunction *) (thr->callstack + thr->callstack_top - 1)->func)->func == duk_bi_thread_resume);
- DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL &&
- DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)); /* an Ecmascript function */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL &&
+ DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)) &&
+ ((duk_hnativefunction *) DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))->func == duk_bi_thread_resume);
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL &&
+ DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2))); /* an Ecmascript function */
DUK_ASSERT_DISABLE((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0); /* unsigned */
tv = &thr->heap->lj.value2; /* resumee */
@@ -775,12 +740,12 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
resumee->callstack_top >= 2); /* YIELDED: Ecmascript activation + Duktape.Thread.yield() activation */
DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
- ((resumee->callstack + resumee->callstack_top - 1)->func != NULL &&
- DUK_HOBJECT_IS_NATIVEFUNCTION((resumee->callstack + resumee->callstack_top - 1)->func) &&
- ((duk_hnativefunction *) (resumee->callstack + resumee->callstack_top - 1)->func)->func == duk_bi_thread_yield));
+ (DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 1) != NULL &&
+ DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 1)) &&
+ ((duk_hnativefunction *) DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 1))->func == duk_bi_thread_yield));
DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
- ((resumee->callstack + resumee->callstack_top - 2)->func != NULL &&
- DUK_HOBJECT_IS_COMPILEDFUNCTION((resumee->callstack + resumee->callstack_top - 2)->func))); /* an Ecmascript function */
+ (DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 2) != NULL &&
+ DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 2)))); /* an Ecmascript function */
DUK_ASSERT_DISABLE(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
(resumee->callstack + resumee->callstack_top - 2)->idx_retval >= 0); /* idx_retval unsigned */
DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_INACTIVE ||
@@ -845,7 +810,8 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
retval = DUK__LONGJMP_RESTART;
goto wipe_and_return;
} else {
- int call_flags;
+ duk_small_uint_t call_flags;
+ duk_bool_t setup_rc;
/* resumee: [... initial_func] (currently actually: [initial_func]) */
@@ -857,9 +823,13 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
call_flags = DUK_CALL_FLAG_IS_RESUME; /* is resume, not a tailcall */
- duk_handle_ecma_call_setup(resumee,
- 1, /* num_stack_args */
- call_flags); /* call_flags */
+ setup_rc = duk_handle_ecma_call_setup(resumee,
+ 1, /* num_stack_args */
+ call_flags); /* call_flags */
+ if (setup_rc == 0) {
+ /* FIXME: cannot happen? if not, document */
+ DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
+ }
resumee->resumer = thr;
resumee->state = DUK_HTHREAD_STATE_RUNNING;
@@ -894,11 +864,11 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(thr != entry_thread); /* Duktape.Thread.yield() should prevent */
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); /* unchanged from Duktape.Thread.yield() */
DUK_ASSERT(thr->callstack_top >= 2); /* Ecmascript activation + Duktape.Thread.yield() activation */
- DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL &&
- DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func) &&
- ((duk_hnativefunction *) (thr->callstack + thr->callstack_top - 1)->func)->func == duk_bi_thread_yield);
- DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL &&
- DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)); /* an Ecmascript function */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL &&
+ DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)) &&
+ ((duk_hnativefunction *) DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))->func == duk_bi_thread_yield);
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL &&
+ DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2))); /* an Ecmascript function */
DUK_ASSERT_DISABLE((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0); /* unsigned */
resumer = thr->resumer;
@@ -906,11 +876,11 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(resumer != NULL);
DUK_ASSERT(resumer->state == DUK_HTHREAD_STATE_RESUMED); /* written by a previous RESUME handling */
DUK_ASSERT(resumer->callstack_top >= 2); /* Ecmascript activation + Duktape.Thread.resume() activation */
- DUK_ASSERT((resumer->callstack + resumer->callstack_top - 1)->func != NULL &&
- DUK_HOBJECT_IS_NATIVEFUNCTION((resumer->callstack + resumer->callstack_top - 1)->func) &&
- ((duk_hnativefunction *) (resumer->callstack + resumer->callstack_top - 1)->func)->func == duk_bi_thread_resume);
- DUK_ASSERT((resumer->callstack + resumer->callstack_top - 2)->func != NULL &&
- DUK_HOBJECT_IS_COMPILEDFUNCTION((resumer->callstack + resumer->callstack_top - 2)->func)); /* an Ecmascript function */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 1) != NULL &&
+ DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 1)) &&
+ ((duk_hnativefunction *) DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 1))->func == duk_bi_thread_resume);
+ DUK_ASSERT(DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 2) != NULL &&
+ DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 2))); /* an Ecmascript function */
DUK_ASSERT_DISABLE((resumer->callstack + resumer->callstack_top - 2)->idx_retval >= 0); /* unsigned */
if (thr->heap->lj.iserror) {
@@ -1024,7 +994,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
(long) (thr->callstack + thr->callstack_top - 2)->idx_retval,
(duk_tval *) &thr->heap->lj.value1));
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)); /* must be ecmascript */
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2))); /* must be ecmascript */
tv1 = thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval;
DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
@@ -1049,11 +1019,11 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(thr->resumer != NULL);
DUK_ASSERT(thr->resumer->callstack_top >= 2); /* Ecmascript activation + Duktape.Thread.resume() activation */
- DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func != NULL &&
- DUK_HOBJECT_IS_NATIVEFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func) &&
- ((duk_hnativefunction *) (thr->resumer->callstack + thr->resumer->callstack_top - 1)->func)->func == duk_bi_thread_resume); /* Duktape.Thread.resume() */
- DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func != NULL &&
- DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func)); /* an Ecmascript function */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1) != NULL &&
+ DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1)) &&
+ ((duk_hnativefunction *) DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1))->func == duk_bi_thread_resume); /* Duktape.Thread.resume() */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 2) != NULL &&
+ DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 2))); /* an Ecmascript function */
DUK_ASSERT_DISABLE((thr->resumer->callstack + thr->resumer->callstack_top - 2)->idx_retval >= 0); /* unsigned */
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);
@@ -1223,11 +1193,11 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(thr->resumer != NULL);
DUK_ASSERT(thr->resumer->callstack_top >= 2); /* Ecmascript activation + Duktape.Thread.resume() activation */
- DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func != NULL &&
- DUK_HOBJECT_IS_NATIVEFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func) &&
- ((duk_hnativefunction *) (thr->resumer->callstack + thr->resumer->callstack_top - 1)->func)->func == duk_bi_thread_resume); /* Duktape.Thread.resume() */
- DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func != NULL &&
- DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func)); /* an Ecmascript function */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1) != NULL &&
+ DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1)) &&
+ ((duk_hnativefunction *) DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1))->func == duk_bi_thread_resume); /* Duktape.Thread.resume() */
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 2) != NULL &&
+ DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 2))); /* an Ecmascript function */
resumer = thr->resumer;
@@ -1319,7 +1289,7 @@ duk_bool_t duk__handle_fast_return(duk_hthread *thr,
* it would have matched the entry level check).
*/
DUK_ASSERT(thr->callstack_top >= 2);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)); /* must be ecmascript */
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2))); /* must be ecmascript */
tv1 = thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval;
DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
@@ -1380,7 +1350,7 @@ DUK_LOCAL void duk__executor_interrupt(duk_hthread *thr) {
DUK_ASSERT(thr->callstack_top > 0);
act = thr->callstack + thr->callstack_top - 1;
- fun = (duk_hcompiledfunction *) act->func;
+ fun = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act);
DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION((duk_hobject *) fun));
DUK_UNREF(fun);
@@ -1510,8 +1480,8 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
DUK_ASSERT(entry_thread != NULL);
DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR((duk_heaphdr *) entry_thread);
DUK_ASSERT(entry_thread->callstack_top >= 1); /* at least one activation, ours */
- DUK_ASSERT((entry_thread->callstack + entry_thread->callstack_top - 1)->func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((entry_thread->callstack + entry_thread->callstack_top - 1)->func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(entry_thread->callstack + entry_thread->callstack_top - 1) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(entry_thread->callstack + entry_thread->callstack_top - 1)));
thr = entry_thread;
@@ -1628,14 +1598,14 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
DUK_ASSERT(thr != NULL);
DUK_ASSERT(thr->callstack_top >= 1);
- DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);
- DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
+ DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
+ DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
/* XXX: shrink check flag? */
/* assume that thr->valstack_bottom has been set-up before getting here */
act = thr->callstack + thr->callstack_top - 1;
- fun = (duk_hcompiledfunction *) act->func;
+ fun = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act);
bcode = DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(fun);
DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= fun->nregs);
@@ -2727,8 +2697,9 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
duk_small_uint_t flag_tailcall;
duk_small_uint_t flag_evalcall;
duk_tval *tv_func;
- duk_hobject *obj_func; /* target function, possibly a bound function */
- duk_hobject *obj_final_func; /* final target function, non-bound function */
+ duk_hobject *obj_func;
+ duk_bool_t setup_rc;
+ duk_idx_t num_stack_args;
/* A -> flags
* B -> base register for call (base -> func, base+1 -> this, base+2 -> arg1 ... base+2+N-1 -> argN)
@@ -2756,66 +2727,90 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
}
#endif
- tv_func = DUK__REGP(idx);
- if (!DUK_TVAL_IS_OBJECT(tv_func)) {
- DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "call target not an object");
- }
- obj_func = DUK_TVAL_GET_OBJECT(tv_func);
-
/*
* To determine whether to use an optimized Ecmascript-to-Ecmascript
* call, we need to know whether the final, non-bound function is an
- * Ecmascript function. We need to follow the "bound" chain to do that;
- * the "bound" chain will be followed for the second time when calling.
- * This overhead only affects bound functions (in particular, helper
- * functions should not be called if the immediate target function is
- * not bound).
+ * Ecmascript function.
*
- * Even so, this awkward solution could be avoided by e.g. replicating
- * final, non-bound target function flags to the bound function objects
- * (so that a bound function would e.g. have both a "BOUND" flag and
- * a "COMPILEDFUNCTION" flag). Also, bound functions could also keep
- * a direct reference to the final non-bound function ("shortcut").
+ * This is now implemented so that we start to do an ecma-to-ecma call
+ * setup which will resolve the bound chain as the first thing. If the
+ * final function is not eligible, the return value indicates that the
+ * ecma-to-ecma call is not possible. The setup will overwrite the call
+ * target at DUK__REGP(idx) with the final, non-bound function (which
+ * may be a lightfunc), and fudge arguments if necessary.
+ *
+ * FIXME: the call setup won't do "effective this binding" resolution
+ * if an ecma-to-ecma call is not possible. This is quite confusing,
+ * so perhaps add a helper for doing bound function and effective this
+ * binding resolution - and call that explicitly? Ecma-to-ecma call
+ * setup and normal function handling can then assume this prestep has
+ * been done by the caller.
*/
- if (DUK_HOBJECT_HAS_BOUND(obj_func)) {
- obj_final_func = duk__find_nonbound_function(thr, obj_func);
- } else {
- obj_final_func = obj_func;
- }
-
duk_set_top(ctx, (duk_idx_t) (idx + c + 2)); /* [ ... func this arg1 ... argN ] */
- if (DUK_HOBJECT_IS_COMPILEDFUNCTION(obj_final_func)) {
- /*
- * Ecmascript-to-Ecmascript call: avoid C recursion
- * by being clever.
+ call_flags = 0;
+ if (flag_tailcall) {
+ /* We request a tailcall, but in some corner cases
+ * call handling can decide that a tailcall is
+ * actually not possible.
+ * See: test-bug-tailcall-preventyield-assert.c.
*/
+ call_flags |= DUK_CALL_FLAG_IS_TAILCALL;
+ }
- /* Compared to duk_handle_call():
- * - protected call: never
- * - ignore recursion limit: never
+ /* Compared to duk_handle_call():
+ * - protected call: never
+ * - ignore recursion limit: never
+ */
+ num_stack_args = c;
+ setup_rc = duk_handle_ecma_call_setup(thr,
+ num_stack_args,
+ call_flags);
+
+ if (setup_rc) {
+ /* Ecma-to-ecma call possible, may or may not be a tailcall.
+ * Avoid C recursion by being clever.
*/
+ DUK_DDD(DUK_DDDPRINT("ecma-to-ecma call setup possible, restart execution"));
+ goto restart_execution;
+ }
- /* XXX: optimize flag handling, by coordinating with bytecode */
+ DUK_DDD(DUK_DDDPRINT("ecma-to-ecma call not possible, target is native (may be lightfunc)"));
- call_flags = 0;
- if (flag_tailcall) {
- /* We request a tailcall, but in some corner cases
- * call handling can decide that a tailcall is
- * actually not possible.
- * See: test-bug-tailcall-preventyield-assert.c.
- */
- call_flags |= DUK_CALL_FLAG_IS_TAILCALL;
- }
+ /* Recompute argument count: bound function handling may have shifted. */
+ num_stack_args = duk_get_top(ctx) - (idx + 2);
+ DUK_DDD(DUK_DDDPRINT("recomputed arg count: %ld\n", (long) num_stack_args));
- duk_handle_ecma_call_setup(thr,
- c, /* num_stack_args */
- call_flags); /* call_flags */
+ tv_func = DUK__REGP(idx); /* Relookup if relocated */
+ if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
+ call_flags = 0; /* not protected, respect reclimit, not constructor */
- /* restart execution -> starts executing new function */
- goto restart_execution;
+ /* FIXME: eval needs special handling if it can also be a lightfunc
+ * (already excepted from lightfunc status, explain here).
+ */
+ /* FIXME: the call target bound chain is already resolved by the ecma
+ * call setup attempt, document here... Perhaps the bound chain resolution
+ * could be a shared helper instead so that we could skip it here.
+ */
+ duk_handle_call(thr,
+ num_stack_args,
+ call_flags);
+
+ /* FIXME: who should restore? */
+ duk_require_stack_top(ctx, fun->nregs); /* may have shrunk by inner calls, must recheck */
+ duk_set_top(ctx, fun->nregs);
+
+ /* No need to reinit setjmp() catchpoint, as call handling
+ * will store and restore our state.
+ */
} else {
+ /* Call setup checks callability. */
+ DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_func));
+ obj_func = DUK_TVAL_GET_OBJECT(tv_func);
+ DUK_ASSERT(obj_func != NULL);
+ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(obj_func));
+
/*
* Other cases, use C recursion.
*
@@ -2841,8 +2836,8 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
}
duk_handle_call(thr,
- c, /* num_stack_args */
- call_flags); /* call_flags */
+ num_stack_args,
+ call_flags);
/* XXX: who should restore? */
duk_require_stack_top(ctx, (duk_idx_t) fun->nregs); /* may have shrunk by inner calls, must recheck */
diff --git a/src/duk_js_ops.c b/src/duk_js_ops.c
index 3fd67ad890..b2c70429cc 100644
--- a/src/duk_js_ops.c
+++ b/src/duk_js_ops.c
@@ -87,6 +87,9 @@ DUK_INTERNAL duk_bool_t duk_js_toboolean(duk_tval *tv) {
void *p = DUK_TVAL_GET_POINTER(tv);
return (p != NULL ? 1 : 0);
}
+ case DUK_TAG_LIGHTFUNC: {
+ return 1;
+ }
default: {
/* number */
int c;
@@ -220,6 +223,10 @@ DUK_INTERNAL duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv) {
void *p = DUK_TVAL_GET_POINTER(tv);
return (p != NULL ? 1.0 : 0.0);
}
+ case DUK_TAG_LIGHTFUNC: {
+ /* +(function(){}) -> NaN */
+ return DUK_DOUBLE_NAN;
+ }
default: {
/* number */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
@@ -574,6 +581,19 @@ DUK_INTERNAL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, d
return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0;
}
}
+ 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;
+ }
default: {
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y));
@@ -943,7 +963,7 @@ DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_
duk_push_tval(ctx, tv_x);
duk_push_tval(ctx, tv_y);
- func = duk_require_hobject(ctx, -1);
+ func = duk_require_hobject(ctx, -1); /* FIXME: lightfunc */
/*
* For bound objects, [[HasInstance]] just calls the target function
@@ -1082,9 +1102,12 @@ DUK_INTERNAL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv
* form (which is a shame).
*/
+ /* TypeError if rval is not an object (or lightfunc which should behave
+ * like a Function instance).
+ */
duk_push_tval(ctx, tv_x);
duk_push_tval(ctx, tv_y);
- (void) duk_require_hobject(ctx, -1); /* TypeError if rval not object */
+ duk_require_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC);
duk_to_string(ctx, -2); /* coerce lval with ToString() */
retval = duk_hobject_hasprop(thr, duk_get_tval(ctx, -1), duk_get_tval(ctx, -2));
@@ -1148,6 +1171,10 @@ DUK_INTERNAL duk_hstring *duk_js_typeof(duk_hthread *thr, duk_tval *tv_x) {
stridx = DUK_STRIDX_LC_BUFFER;
break;
}
+ case DUK_TAG_LIGHTFUNC: {
+ stridx = DUK_STRIDX_LC_FUNCTION;
+ break;
+ }
default: {
/* number */
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
diff --git a/src/duk_js_var.c b/src/duk_js_var.c
index f489a4cd22..ce9fd7d791 100644
--- a/src/duk_js_var.c
+++ b/src/duk_js_var.c
@@ -513,9 +513,9 @@ void duk_js_init_activation_environment_records_delayed(duk_hthread *thr,
duk_hobject *func;
duk_hobject *env;
- func = act->func;
+ func = DUK_ACT_GET_FUNC(act);
DUK_ASSERT(func != NULL);
- DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); /* bound functions are never in act->func */
+ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); /* bound functions are never in act 'func' */
/*
* Delayed initialization only occurs for 'NEWENV' functions.
@@ -568,7 +568,7 @@ DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject
DUK_ASSERT(thr != NULL);
DUK_ASSERT(env != NULL);
- DUK_ASSERT(func != NULL);
+ /* FIXME: DUK_ASSERT(func != NULL); */
if (!DUK_HOBJECT_IS_DECENV(env) || DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
DUK_DDD(DUK_DDDPRINT("environment record not a declarative record, "
@@ -609,7 +609,7 @@ DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject
}
#endif
- if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+ if (func != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
duk_hobject *varmap;
duk_hstring *key;
duk_tval *tv;
@@ -828,7 +828,7 @@ duk_bool_t duk__getid_activation_regs(duk_hthread *thr,
DUK_ASSERT(act != NULL);
DUK_ASSERT(out != NULL);
- func = act->func;
+ func = DUK_ACT_GET_FUNC(act);
DUK_ASSERT(func != NULL);
DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
@@ -945,7 +945,7 @@ duk_bool_t duk__get_identifier_reference(duk_hthread *thr,
goto fail_not_found;
}
- func = act->func;
+ func = DUK_ACT_GET_FUNC(act);
DUK_ASSERT(func != NULL);
DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
diff --git a/src/duk_tval.h b/src/duk_tval.h
index 6c48d4d410..c73f4207dc 100644
--- a/src/duk_tval.h
+++ b/src/duk_tval.h
@@ -49,9 +49,10 @@ typedef union duk_double_union duk_tval;
#define DUK_TAG_BOOLEAN 0xfff3UL /* embed: 0 or 1 (false or true) */
/* DUK_TAG_NUMBER would logically go here, but it has multiple 'tags' */
#define DUK_TAG_POINTER 0xfff4UL /* embed: void ptr */
-#define DUK_TAG_STRING 0xfff5UL /* embed: duk_hstring ptr */
-#define DUK_TAG_OBJECT 0xfff6UL /* embed: duk_hobject ptr */
-#define DUK_TAG_BUFFER 0xfff7UL /* embed: duk_hbuffer ptr */
+#define DUK_TAG_LIGHTFUNC 0xfff5UL /* embed: func ptr */
+#define DUK_TAG_STRING 0xfff6UL /* embed: duk_hstring ptr */
+#define DUK_TAG_OBJECT 0xfff7UL /* embed: duk_hobject ptr */
+#define DUK_TAG_BUFFER 0xfff8UL /* embed: duk_hbuffer ptr */
/* for convenience */
#define DUK_XTAG_UNDEFINED_ACTUAL 0xfff10000UL
@@ -98,6 +99,28 @@ typedef union duk_double_union duk_tval;
} while (0)
#endif /* DUK_USE_64BIT_OPS */
+#ifdef DUK_USE_64BIT_OPS
+/* Double casting for pointer to avoid gcc warning (cast from pointer to integer of different size) */
+#ifdef DUK_USE_DOUBLE_ME
+#define DUK__TVAL_SET_LIGHTFUNC(v,fp,flags) do { \
+ (v)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_LIGHTFUNC) << 16) | \
+ ((duk_uint64_t) (flags)) | \
+ (((duk_uint64_t) (duk_uint32_t) (fp)) << 32); \
+ } while (0)
+#else
+#define DUK__TVAL_SET_LIGHTFUNC(v,fp,flags) do { \
+ (v)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_LIGHTFUNC) << 48) | \
+ (((duk_uint64_t) (flags)) << 32) | \
+ ((duk_uint64_t) (duk_uint32_t) (fp)); \
+ } while (0)
+#endif
+#else /* DUK_USE_64BIT_OPS */
+#define DUK__TVAL_SET_LIGHTFUNC(v,fp,flags) do { \
+ (v)->ui[DUK_DBL_IDX_UI0] = (((duk_uint32_t) DUK_TAG_LIGHTFUNC) << 16) | ((duk_uint32_t) (flags)); \
+ (v)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (fp); \
+ } while (0)
+#endif /* DUK_USE_64BIT_OPS */
+
/* select actual setters */
#ifdef DUK_USE_FULL_TVAL
#define DUK_TVAL_SET_UNDEFINED_ACTUAL(v) DUK__TVAL_SET_UNDEFINED_ACTUAL_FULL((v))
@@ -115,6 +138,7 @@ typedef union duk_double_union duk_tval;
#define DUK_TVAL_SET_NAN(v) DUK__TVAL_SET_NAN_NOTFULL((v))
#endif
+#define DUK_TVAL_SET_LIGHTFUNC(v,fp,flags) DUK__TVAL_SET_LIGHTFUNC((v),(fp),(flags))
#define DUK_TVAL_SET_STRING(v,h) DUK__TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_STRING)
#define DUK_TVAL_SET_OBJECT(v,h) DUK__TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_OBJECT)
#define DUK_TVAL_SET_BUFFER(v,h) DUK__TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_BUFFER)
@@ -125,6 +149,12 @@ typedef union duk_double_union duk_tval;
/* getters */
#define DUK_TVAL_GET_BOOLEAN(v) ((int) (v)->us[DUK_DBL_IDX_US1])
#define DUK_TVAL_GET_NUMBER(v) ((v)->d)
+#define DUK_TVAL_GET_LIGHTFUNC(v,out_fp,out_flags) do { \
+ (out_flags) = (v)->ui[DUK_DBL_IDX_UI0] & 0xffffUL; \
+ (out_fp) = (duk_c_function) (v)->ui[DUK_DBL_IDX_UI1]; \
+ } while (0)
+#define DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(v) ((duk_c_function) ((v)->ui[DUK_DBL_IDX_UI1]))
+#define DUK_TVAL_GET_LIGHTFUNC_FLAGS(v) (((int) (v)->ui[DUK_DBL_IDX_UI0]) & 0xffffUL)
#define DUK_TVAL_GET_STRING(v) ((duk_hstring *) (v)->vp[DUK_DBL_IDX_VP1])
#define DUK_TVAL_GET_OBJECT(v) ((duk_hobject *) (v)->vp[DUK_DBL_IDX_VP1])
#define DUK_TVAL_GET_BUFFER(v) ((duk_hbuffer *) (v)->vp[DUK_DBL_IDX_VP1])
@@ -141,6 +171,7 @@ typedef union duk_double_union duk_tval;
#define DUK_TVAL_IS_BOOLEAN(v) (DUK_TVAL_GET_TAG((v)) == DUK_TAG_BOOLEAN)
#define DUK_TVAL_IS_BOOLEAN_TRUE(v) ((v)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_BOOLEAN_TRUE)
#define DUK_TVAL_IS_BOOLEAN_FALSE(v) ((v)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_BOOLEAN_FALSE)
+#define DUK_TVAL_IS_LIGHTFUNC(v) (DUK_TVAL_GET_TAG((v)) == DUK_TAG_LIGHTFUNC)
#define DUK_TVAL_IS_STRING(v) (DUK_TVAL_GET_TAG((v)) == DUK_TAG_STRING)
#define DUK_TVAL_IS_OBJECT(v) (DUK_TVAL_GET_TAG((v)) == DUK_TAG_OBJECT)
#define DUK_TVAL_IS_BUFFER(v) (DUK_TVAL_GET_TAG((v)) == DUK_TAG_BUFFER)
@@ -165,6 +196,7 @@ typedef struct duk_tval_struct duk_tval;
struct duk_tval_struct {
duk_small_uint_t t;
+ duk_small_uint_t v_flags;
union {
duk_double_t d;
duk_small_int_t i;
@@ -176,6 +208,7 @@ struct duk_tval_struct {
duk_hthread *hthread;
duk_hbuffer *hbuffer;
duk_heaphdr *heaphdr;
+ duk_c_function lightfunc;
} v;
};
@@ -184,9 +217,10 @@ struct duk_tval_struct {
#define DUK_TAG_NULL 2
#define DUK_TAG_BOOLEAN 3
#define DUK_TAG_POINTER 4
-#define DUK_TAG_STRING 5
-#define DUK_TAG_OBJECT 6
-#define DUK_TAG_BUFFER 7
+#define DUK_TAG_LIGHTFUNC 5
+#define DUK_TAG_STRING 6
+#define DUK_TAG_OBJECT 7
+#define DUK_TAG_BUFFER 8
/* DUK__TAG_NUMBER is intentionally first, as it is the default clause in code
* to support the 8-byte representation. Further, it is a non-heap-allocated
@@ -219,6 +253,17 @@ struct duk_tval_struct {
(tv)->v.d = (val); \
} while (0)
+#define DUK_TVAL_SET_POINTER(tv,hptr) do { \
+ (tv)->t = DUK_TAG_POINTER; \
+ (tv)->v.voidptr = (hptr); \
+ } while (0)
+
+#define DUK_TVAL_SET_LIGHTFUNC(tv,fp,flags) do { \
+ (tv)->t = DUK_TAG_LIGHTFUNC; \
+ (tv)->v_flags = (flags); \
+ (tv)->v.lightfunc = (duk_c_function) (fp); \
+ } while (0)
+
#define DUK_TVAL_SET_STRING(tv,hptr) do { \
(tv)->t = DUK_TAG_STRING; \
(tv)->v.hstring = (hptr); \
@@ -234,11 +279,6 @@ struct duk_tval_struct {
(tv)->v.hbuffer = (hptr); \
} while (0)
-#define DUK_TVAL_SET_POINTER(tv,hptr) do { \
- (tv)->t = DUK_TAG_POINTER; \
- (tv)->v.voidptr = (hptr); \
- } while (0)
-
#define DUK_TVAL_SET_NAN(tv) do { \
/* in non-packed representation we don't care about which NaN is used */ \
(tv)->t = DUK__TAG_NUMBER; \
@@ -250,15 +290,20 @@ struct duk_tval_struct {
/* getters */
#define DUK_TVAL_GET_BOOLEAN(tv) ((tv)->v.i)
#define DUK_TVAL_GET_NUMBER(tv) ((tv)->v.d)
+#define DUK_TVAL_GET_POINTER(tv) ((tv)->v.voidptr)
+#define DUK_TVAL_GET_LIGHTFUNC(tv,out_fp,out_flags) do { \
+ (out_flags) = (duk_uint32_t) (tv)->v_flags; \
+ (out_fp) = (tv)->v.lightfunc; \
+ } while (0)
+#define DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv) ((tv)->v.lightfunc)
+#define DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) ((duk_uint32_t) ((tv)->v_flags))
#define DUK_TVAL_GET_STRING(tv) ((tv)->v.hstring)
#define DUK_TVAL_GET_OBJECT(tv) ((tv)->v.hobject)
#define DUK_TVAL_GET_BUFFER(tv) ((tv)->v.hbuffer)
-#define DUK_TVAL_GET_POINTER(tv) ((tv)->v.voidptr)
#define DUK_TVAL_GET_HEAPHDR(tv) ((tv)->v.heaphdr)
/* decoding */
#define DUK_TVAL_GET_TAG(tv) ((tv)->t)
-#define DUK_TVAL_IS_NUMBER(tv) ((tv)->t == DUK__TAG_NUMBER)
#define DUK_TVAL_IS_UNDEFINED(tv) ((tv)->t == DUK_TAG_UNDEFINED)
#define DUK_TVAL_IS_UNDEFINED_ACTUAL(tv) (((tv)->t == DUK_TAG_UNDEFINED) && ((tv)->v.i == 0))
#define DUK_TVAL_IS_UNDEFINED_UNUSED(tv) (((tv)->t == DUK_TAG_UNDEFINED) && ((tv)->v.i != 0))
@@ -266,10 +311,12 @@ struct duk_tval_struct {
#define DUK_TVAL_IS_BOOLEAN(tv) ((tv)->t == DUK_TAG_BOOLEAN)
#define DUK_TVAL_IS_BOOLEAN_TRUE(tv) (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i != 0))
#define DUK_TVAL_IS_BOOLEAN_FALSE(tv) (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i == 0))
+#define DUK_TVAL_IS_NUMBER(tv) ((tv)->t == DUK__TAG_NUMBER)
+#define DUK_TVAL_IS_POINTER(tv) ((tv)->t == DUK_TAG_POINTER)
+#define DUK_TVAL_IS_LIGHTFUNC(tv) ((tv)->t == DUK_TAG_LIGHTFUNC)
#define DUK_TVAL_IS_STRING(tv) ((tv)->t == DUK_TAG_STRING)
#define DUK_TVAL_IS_OBJECT(tv) ((tv)->t == DUK_TAG_OBJECT)
#define DUK_TVAL_IS_BUFFER(tv) ((tv)->t == DUK_TAG_BUFFER)
-#define DUK_TVAL_IS_POINTER(tv) ((tv)->t == DUK_TAG_POINTER)
#define DUK_TVAL_IS_HEAP_ALLOCATED(tv) ((tv)->t >= DUK_TAG_STRING)
@@ -282,4 +329,15 @@ struct duk_tval_struct {
#define DUK_TVAL_SET_BOOLEAN_TRUE(v) DUK_TVAL_SET_BOOLEAN(v, 1)
#define DUK_TVAL_SET_BOOLEAN_FALSE(v) DUK_TVAL_SET_BOOLEAN(v, 0)
+/* Lightfunc flags packing and unpacking. */
+/* Sign extend: 0x0000##00 -> 0x##000000 -> sign extend to 0xssssss## */
+#define DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags) \
+ ((((duk_int32_t) (lf_flags)) << 16) >> 24)
+#define DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags) \
+ (((lf_flags) >> 4) & 0x0f)
+#define DUK_LFUNC_FLAGS_GET_NARGS(lf_flags) \
+ ((lf_flags) & 0x0f)
+#define DUK_LFUNC_FLAGS_PACK(magic,length,nargs) \
+ ((magic) << 8) | ((length) << 4) | (nargs)
+
#endif /* DUK_TVAL_H_INCLUDED */
diff --git a/src/genbuiltins.py b/src/genbuiltins.py
index 7c2d783852..e31816ed9e 100644
--- a/src/genbuiltins.py
+++ b/src/genbuiltins.py
@@ -130,29 +130,6 @@ def create_double(x):
PROPDESC_FLAG_CONFIGURABLE = (1 << 2)
PROPDESC_FLAG_ACCESSOR = (1 << 3) # unused now
-# magic values for Date built-in, must match duk_bi_date.c
-BI_DATE_FLAG_NAN_TO_ZERO = (1 << 0)
-BI_DATE_FLAG_NAN_TO_RANGE_ERROR = (1 << 1)
-BI_DATE_FLAG_ONEBASED = (1 << 2)
-BI_DATE_FLAG_EQUIVYEAR = (1 << 3)
-BI_DATE_FLAG_LOCALTIME = (1 << 4)
-BI_DATE_FLAG_SUB1900 = (1 << 5)
-BI_DATE_FLAG_TOSTRING_DATE = (1 << 6)
-BI_DATE_FLAG_TOSTRING_TIME = (1 << 7)
-BI_DATE_FLAG_TOSTRING_LOCALE = (1 << 8)
-BI_DATE_FLAG_TIMESETTER = (1 << 9)
-BI_DATE_FLAG_YEAR_FIXUP = (1 << 10)
-BI_DATE_FLAG_SEP_T = (1 << 11)
-BI_DATE_FLAG_VALUE_SHIFT = 12
-BI_DATE_IDX_YEAR = 0
-BI_DATE_IDX_MONTH = 1
-BI_DATE_IDX_DAY = 2
-BI_DATE_IDX_HOUR = 3
-BI_DATE_IDX_MINUTE = 4
-BI_DATE_IDX_SECOND = 5
-BI_DATE_IDX_MILLISECOND = 6
-BI_DATE_IDX_WEEKDAY = 7
-
# magic values for Array built-in
BI_ARRAY_ITER_EVERY = 0
BI_ARRAY_ITER_SOME = 1
@@ -665,58 +642,65 @@ def internal(x):
{ 'name': internal('Value'), 'value': DBL_NAN, 'attributes': 'w' }
],
+
+ # NOTE: The magic values for Date prototype are special. The actual control
+ # flags needed for the built-ins don't fit into LIGHTFUNC magic field, so
+ # the values here are indices to duk__date_magics[] in duk_bi_date.c which
+ # contains the actual control flags. Magic values here must be kept in strict
+ # sync with duk_bi_date.c!
+
'functions': [
- { 'name': 'toString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_DATE + BI_DATE_FLAG_TOSTRING_TIME + BI_DATE_FLAG_LOCALTIME } },
- { 'name': 'toDateString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_DATE + BI_DATE_FLAG_LOCALTIME } },
- { 'name': 'toTimeString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_TIME + BI_DATE_FLAG_LOCALTIME } },
- { 'name': 'toLocaleString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_DATE + BI_DATE_FLAG_TOSTRING_TIME + BI_DATE_FLAG_TOSTRING_LOCALE + BI_DATE_FLAG_LOCALTIME } },
- { 'name': 'toLocaleDateString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_DATE + BI_DATE_FLAG_TOSTRING_LOCALE + BI_DATE_FLAG_LOCALTIME } },
- { 'name': 'toLocaleTimeString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_TIME + BI_DATE_FLAG_TOSTRING_LOCALE + BI_DATE_FLAG_LOCALTIME } },
- { 'name': 'toUTCString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_DATE + BI_DATE_FLAG_TOSTRING_TIME } },
- { 'name': 'toISOString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TOSTRING_DATE + BI_DATE_FLAG_TOSTRING_TIME + BI_DATE_FLAG_NAN_TO_RANGE_ERROR + BI_DATE_FLAG_SEP_T } },
+ { 'name': 'toString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 } },
+ { 'name': 'toDateString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 1 } },
+ { 'name': 'toTimeString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 2 } },
+ { 'name': 'toLocaleString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 3 } },
+ { 'name': 'toLocaleDateString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 4 } },
+ { 'name': 'toLocaleTimeString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 5 } },
+ { 'name': 'toUTCString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 6 } },
+ { 'name': 'toISOString', 'native': 'duk_bi_date_prototype_tostring_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 7 } },
{ 'name': 'toJSON', 'native': 'duk_bi_date_prototype_to_json', 'length': 1 },
{ 'name': 'valueOf', 'native': 'duk_bi_date_prototype_value_of', 'length': 0 },
{ 'name': 'getTime', 'native': 'duk_bi_date_prototype_value_of', 'length': 0 }, # Native function shared on purpose
- { 'name': 'getFullYear', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_YEAR << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCFullYear', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_YEAR << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getMonth', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_MONTH << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCMonth', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_MONTH << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getDate', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_ONEBASED + BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_DAY << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCDate', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_ONEBASED + (BI_DATE_IDX_DAY << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getDay', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_WEEKDAY << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCDay', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_WEEKDAY << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getHours', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_HOUR << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCHours', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_HOUR << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getMinutes', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_MINUTE << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCMinutes', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_MINUTE << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getSeconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_SECOND << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCSeconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_SECOND << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getMilliseconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (BI_DATE_IDX_MILLISECOND << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'getUTCMilliseconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 0 + (BI_DATE_IDX_MILLISECOND << BI_DATE_FLAG_VALUE_SHIFT) } },
+ { 'name': 'getFullYear', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 8 } },
+ { 'name': 'getUTCFullYear', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 9 } },
+ { 'name': 'getMonth', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 10 } },
+ { 'name': 'getUTCMonth', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 11 } },
+ { 'name': 'getDate', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 12 } },
+ { 'name': 'getUTCDate', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 13 } },
+ { 'name': 'getDay', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 14 } },
+ { 'name': 'getUTCDay', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 15 } },
+ { 'name': 'getHours', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 16 } },
+ { 'name': 'getUTCHours', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 17 } },
+ { 'name': 'getMinutes', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 18 } },
+ { 'name': 'getUTCMinutes', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 19 } },
+ { 'name': 'getSeconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 20 } },
+ { 'name': 'getUTCSeconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 21 } },
+ { 'name': 'getMilliseconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 22 } },
+ { 'name': 'getUTCMilliseconds', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'magic': { 'type': 'plain', 'value': 23 } },
{ 'name': 'getTimezoneOffset', 'native': 'duk_bi_date_prototype_get_timezone_offset', 'length': 0 },
{ 'name': 'setTime', 'native': 'duk_bi_date_prototype_set_time', 'length': 1 },
- { 'name': 'setMilliseconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + BI_DATE_FLAG_LOCALTIME + (1 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCMilliseconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + (1 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setSeconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + BI_DATE_FLAG_LOCALTIME + (2 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCSeconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + (2 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setMinutes', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + BI_DATE_FLAG_LOCALTIME + (3 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCMinutes', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + (3 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setHours', 'native': 'duk_bi_date_prototype_set_shared', 'length': 4, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + BI_DATE_FLAG_LOCALTIME + (4 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCHours', 'native': 'duk_bi_date_prototype_set_shared', 'length': 4, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_TIMESETTER + (4 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setDate', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (1 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCDate', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': 0 + (1 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setMonth', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + (2 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCMonth', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': 0 + (2 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setFullYear', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_NAN_TO_ZERO + BI_DATE_FLAG_LOCALTIME + (3 << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setUTCFullYear', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_NAN_TO_ZERO + (3 << BI_DATE_FLAG_VALUE_SHIFT) } },
+ { 'name': 'setMilliseconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': 24 } },
+ { 'name': 'setUTCMilliseconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': 25 } },
+ { 'name': 'setSeconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': 26 } },
+ { 'name': 'setUTCSeconds', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': 27 } },
+ { 'name': 'setMinutes', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': 28 } },
+ { 'name': 'setUTCMinutes', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': 29 } },
+ { 'name': 'setHours', 'native': 'duk_bi_date_prototype_set_shared', 'length': 4, 'varargs': True, 'magic': { 'type': 'plain', 'value': 30 } },
+ { 'name': 'setUTCHours', 'native': 'duk_bi_date_prototype_set_shared', 'length': 4, 'varargs': True, 'magic': { 'type': 'plain', 'value': 31 } },
+ { 'name': 'setDate', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': 32 } },
+ { 'name': 'setUTCDate', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'magic': { 'type': 'plain', 'value': 33 } },
+ { 'name': 'setMonth', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': 34 } },
+ { 'name': 'setUTCMonth', 'native': 'duk_bi_date_prototype_set_shared', 'length': 2, 'varargs': True, 'magic': { 'type': 'plain', 'value': 35 } },
+ { 'name': 'setFullYear', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': 36 } },
+ { 'name': 'setUTCFullYear', 'native': 'duk_bi_date_prototype_set_shared', 'length': 3, 'varargs': True, 'magic': { 'type': 'plain', 'value': 37 } },
# Non-standard extensions: E5 Section B.2.4, B.2.5, B.2.6
#
# 'length' values are not given explicitly but follows the general rule.
# The lengths below agree with V8.
- { 'name': 'getYear', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'section_b': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_LOCALTIME + BI_DATE_FLAG_SUB1900 + (BI_DATE_IDX_YEAR << BI_DATE_FLAG_VALUE_SHIFT) } },
- { 'name': 'setYear', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'section_b': True, 'magic': { 'type': 'plain', 'value': BI_DATE_FLAG_NAN_TO_ZERO + BI_DATE_FLAG_YEAR_FIXUP + (3 << BI_DATE_FLAG_VALUE_SHIFT) } },
+ { 'name': 'getYear', 'native': 'duk_bi_date_prototype_get_shared', 'length': 0, 'section_b': True, 'magic': { 'type': 'plain', 'value': 38 } },
+ { 'name': 'setYear', 'native': 'duk_bi_date_prototype_set_shared', 'length': 1, 'section_b': True, 'magic': { 'type': 'plain', 'value': 39 } },
# Note: toGMTString() is required to initially be the same Function object as the initial
# Date.prototype.toUTCString. In other words: Date.prototype.toGMTString === Date.prototype.toUTCString --> true.
From 04cecd1c11631755a5a46550bfbd5fa36e066426 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Tue, 30 Sep 2014 15:24:17 +0300
Subject: [PATCH 5/8] Expand low memory / timing sensitive notes
---
doc/low-memory.rst | 274 +++++++++++++++++++++++++++++++++++++--
doc/timing-sensitive.rst | 3 +
2 files changed, 263 insertions(+), 14 deletions(-)
diff --git a/doc/low-memory.rst b/doc/low-memory.rst
index 73e9e1a46e..22acbd1d2a 100644
--- a/doc/low-memory.rst
+++ b/doc/low-memory.rst
@@ -2,25 +2,55 @@
Low memory environments
=======================
-One important portability target are low memory environments. The default
-Duktape options are quite memory conservative, and significant Ecmascript
-programs can be executed with, say, 1 megabyte of memory. Currently realistic
-memory targets are roughly:
+Overview
+========
-* 256kB flash memory (code) and 256kB system RAM
+This document describes suggested feature options for reducing Duktape
+memory usage for memory-constrained environments, which are one important
+portability target for Duktape.
+
+The default Duktape options are quite memory conservative, and significant
+Ecmascript programs can be executed with e.g. 1MB of memory. Currently
+realistic memory targets are roughly:
+
+* 256-384kB flash memory (code) and 256kB system RAM
- Duktape compiled with default options is feasible
-* 256kB flash memory (code) and 128kB system RAM
+ - Duktape compiles to around 200-210kB of code (x86), so 256kB is
+ technically feasible but leaves little space for user bindings,
+ hardware initialization, communications, etc; 384kB is a more
+ realistic flash target
+
+* 256-384kB flash memory (code) and 128kB system RAM
- Duktape feature options are needed to reduce memory usage
- - A custom memory allocation with manually tuned pools may be required
+ - A custom pool-based memory allocation with manually tuned pools
+ may be required
- - Only very small programs can currently be executed
+ - Aggressive measures like lightweight functions, 16-bit fields for
+ various internal structures (strings, buffers, objects), pointer
+ compression, external strings, etc may need to be used
-This document describes suggested feature options for reducing Duktape
-memory usage for memory-constrained environments.
+There are four basic goals for low memory optimization:
+
+1. Reduce Duktape code (flash) footprint. This is currently a low priority
+ item because flash size doesn't seem to be a bottleneck for most users.
+
+2. Reduce initial memory usage of a Duktape heap. This provides a baseline
+ for memory usage which won't be available for user code (technically some
+ memory can be reclaimed by deleting some built-ins after heap creation).
+
+3. Minimize the growth of the Duktape heap relative to the scope and
+ complexity of user code, so that as large programs as possible can be
+ compiled and executed in a given space. Important contributing factors
+ include the footprint of user-defined Ecmascript and Duktape/C functions,
+ the size of compiled bytecode, etc.
+
+4. Make remaining memory allocations as friendly as possible for the memory
+ allocator, especially a pool-based memory allocator. Concretely, prefer
+ small chunks over large contiguous allocations.
Suggested feature options
=========================
@@ -58,12 +88,228 @@ Suggested feature options
- ``DUK_OPT_NO_REGEXP_SUPPORT``.
* Duktape debug code uses a large, static temporary buffer for formatting
- debuglog lines. Use e.g. the following to reduce this overhead:
+ debug log lines. If you're running with debugging enabled, use e.g.
+ the following to reduce this overhead:
- ``-DDUK_OPT_DEBUG_BUFSIZE=2048``
-* For very low memory environments, consider using lightweight functions
- for your Duktape/C bindings and to force Duktape built-ins to be lightweight
- functions:
+More aggressive options
+=======================
+
+These may be needed for very low memory environments (e.g. 128kB system RAM):
+
+* Consider using lightweight functions for your Duktape/C bindings and to
+ force Duktape built-ins to be lightweight functions:
- ``DUK_OPT_LIGHTFUNC_BUILTINS``
+
+Notes on potential low memory measures
+======================================
+
+Pointer compression
+-------------------
+
+Can be applied throughout (where it matters) for three pointer types:
+
+* Compressed 16-bit Duktape heap pointers, assuming Duktape heap pointers
+ can fit into 16 bits, e.g. max 256kB memory pool with 4-byte alignment
+
+* Compressed 16-bit function pointers, assuming C function pointers can
+ fit into 16 bits
+
+* Compressed 16-bit non-Duktape-heap data pointers, assuming C data
+ pointers can fit into 16 bits
+
+Pointer compression can be quite slow because often memory mappings are not
+linear, so the required operations are not trivial. NULL also needs specific
+handling.
+
+Heap headers
+------------
+
+* Compressed 16-bit heap pointers
+
+* 16-bit field for refcount
+
+* Move one struct specific field (e.g. 16-bit string length) into the unused
+ bits of the ``duk_heaphdr`` 32-bit flags field
+
+Objects
+-------
+
+* Tweak growth factors to keep objects always or nearly always compact
+
+* 16-bit field for property count, array size, etc.
+
+* Drop hash part entirely: it's rarely needed in low memory environments
+ and hash part size won't need to be tracked
+
+* Compressed pointers
+
+Strings
+-------
+
+* Use an indirect string type which stores string data behind a pointer
+ (same as dynamic buffer); allow user code to indicate which C strings
+ are immutable and can be used in this way
+
+* Allow user code to move a string to e.g. memory-mapped flash when it
+ is interned or when the compiler interns its constants (this is referred
+ to as "static strings" or "external strings")
+
+* Memory map built-in strings (about 2kB bit packed) directly from flash
+
+* 16-bit fields for string char and byte length
+
+* 16-bit string hash
+
+* Rework string table to avoid current issues: (1) large reallocations,
+ (2) rehashing needs both old and new string table as it's not in-place.
+ Multiple options, including:
+
+ - Separate chaining (open hashing, closed addressing) with a fixed or
+ bounded top level hash table
+
+ - Various tree structures like red-black trees
+
+* Compressed pointers
+
+Duktape/C function footprint
+----------------------------
+
+* Lightweight functions, converting built-ins into lightweight functions
+
+* Lightweight functions for user Duktape/C binding functions
+
+* Magic value to share native code cheaply for multiple function objects
+
+* Compressed pointers
+
+Ecmascript function footprint
+-----------------------------
+
+* Motivation
+
+ - Small lexically nested callbacks are often used in Ecmascript code,
+ so it's important to keep their size small
+
+* Reduce property count:
+
+ - _pc2line: can be dropped, lose line numbers in tracebacks
+
+ - _formals: can be dropped for most functions (affects debugging)
+
+ - _varmap: can be dropped for most functions (affects debugging)
+
+* Reduce compile-time maximum alloc size for bytecode: currently each
+ instruction takes 8 bytes, 4 bytes for the instruction itself and 4 bytes
+ for line number. Change this into two allocations so that the maximum
+ allocation size is not double that of final bytecode, as that is awkward
+ for pool allocators.
+
+* Improve property format, e.g. ``_formals`` is now a regular array which
+ is quite wasteful; it could be converted to a ``\xFF`` separated string
+ for instance.
+
+* Spawn ``.prototype`` on demand to eliminate one unnecessary object per
+ function
+
+* Use virtual properties when possible, e.g. if ``nargs`` equals desired
+ ``length``, use virtual property for it (either non-writable or create
+ concrete property when written)
+
+* Write bytecode and pc2line to flash during compilation
+
+* Compressed pointers
+
+Contiguous allocations
+----------------------
+
+Unbounded contiguous allocations are a problem for pool allocators. There
+are at least the following sources for these:
+
+* Large user strings and buffers. Not much can be done about these without
+ a full rework of the Duktape C programming model (which assumes string and
+ buffer data is available as plain ``const char *``).
+
+* Bytecode/const buffer for long Ecmascript functions:
+
+ - Bytecode and constants can be placed in separate buffers.
+
+ - Bytecode could be "segmented" so that bytecode would be stored in chunks
+ (e.g. 64 opcodes = 256 bytes). An explicit JUMP to jump from page to page
+ could make the executor impact minimal.
+
+ - During compilation Duktape uses a single buffer to track bytecode
+ instructions and their line numbers. This takes 8 bytes per instruction
+ while the final bytecode takes 4 bytes per instruction. This is easy to
+ fix by using two separate buffers.
+
+* Value stacks of Duktape threads. Start from 1kB and grow without
+ (practical) bound depending on call nesting.
+
+* Catch and call stacks of Duktape threads. Also contiguous but since these
+ are much smaller, they're unlikely to be a problem before the value stack
+ becomes one.
+
+Notes on function memory footprint
+==================================
+
+Normal function representation
+------------------------------
+
+In Duktape 1.0.0 functions are represented as:
+
+* A ``duk_hcompiledfunction`` (a superset of ``duk_hobject``): represents
+ an Ecmascript function which may have a set of properties, and points to
+ the function's data area (bytecode, constants, inner function refs).
+
+* A ``duk_hnativefunction`` (a superset of ``duk_hobject``): represents
+ a Duktape/C function which may also have a set of properties. A pointer
+ to the C function is inside the ``duk_hnativefunction`` structure.
+
+In Duktape 1.1.0 a lightfunc type is available:
+
+* A lightfunc is an 8-byte ``duk_tval`` with no heap allocations, and
+ provides a cheap way to represent many Duktape/C functions.
+
+RAM footprints for each type are discussed below.
+
+Ecmascript functions
+--------------------
+
+An ordinary Ecmascript function takes around 300-500 bytes of RAM. There are
+three objects involved:
+
+- a function template
+- a function instance (multiple instances can be created from one template)
+- automatic prototype object allocated for the function instance
+
+The function template is used to instantiate a function. The resulting
+function is not dependent on the template after creation, so that the
+template can be garbage collected. However, the template often remains
+reachable in callback style programming, through the enclosing function's
+inner function templates table.
+
+The function instance contains a ``.prototype`` property while the prototype
+contains a ``.constructor`` property, so that both functions require a
+property table. This is the case even for the majority of user functions
+which will never be used as constructors; built-in functions are oddly exempt
+from having an automatic prototype.
+
+Duktape/C functions
+-------------------
+
+A Duktape/C function takes about 70-80 bytes of RAM. Unlike Ecmascript
+functions, Duktape/C function are already stripped of unnecessary properties
+and don't have an automatic prototype object.
+
+Even so, there are close to 200 built-in functions, so the footprint of
+the ``duk_hnativefunction`` objects is around 14-16kB, not taking into account
+allocator overhead.
+
+Duktape/C lightfuncs
+--------------------
+
+Lightfuncs require only a ``duk_tval``, 8 bytes. There are no additional heap
+allocations.
diff --git a/doc/timing-sensitive.rst b/doc/timing-sensitive.rst
index ab82bae567..7d28a39851 100644
--- a/doc/timing-sensitive.rst
+++ b/doc/timing-sensitive.rst
@@ -2,6 +2,9 @@
Timing sensitive environments
=============================
+Overview
+========
+
Timing sensitive environments include e.g. games. In these environments
long blocking times are problematic. Stop-and-go garbage collection is
also a potential issue.
From 5e56112176ff3160aa6c919c89926a9c1bcae286 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Wed, 1 Oct 2014 00:56:15 +0300
Subject: [PATCH 6/8] Second round of lightfunc support improvements
* 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.
---
api-testcases/test-dev-lightfunc.c | 945 ++++++-
api-testcases/test-to-defaultvalue.c | 2 +-
ecmascript-testcases/test-bi-duktape-act.js | 11 +-
ecmascript-testcases/test-bi-duktape.js | 6 +-
.../test-dev-lightfunc-accessor.js | 162 ++
.../test-dev-lightfunc-finalizer.js | 59 +
ecmascript-testcases/test-dev-lightfunc.js | 2227 +++++++++++++++--
src/duk_api_call.c | 8 +-
src/duk_api_internal.h | 7 +-
src/duk_api_public.h.in | 3 +
src/duk_api_stack.c | 143 +-
src/duk_bi_buffer.c | 2 +
src/duk_bi_date.c | 2 +-
src/duk_bi_duktape.c | 12 +-
src/duk_bi_error.c | 7 +-
src/duk_bi_function.c | 3 +-
src/duk_bi_json.c | 5 +-
src/duk_bi_object.c | 55 +-
src/duk_bi_proxy.c | 4 +-
src/duk_bi_thread.c | 3 +-
src/duk_debug_vsnprintf.c | 9 +-
src/duk_error_augment.c | 13 +-
src/duk_hobject_props.c | 68 +-
src/duk_hthread.h | 2 +-
src/duk_hthread_builtins.c | 43 +-
src/duk_hthread_stacks.c | 5 +-
src/duk_js_call.c | 172 +-
src/duk_js_executor.c | 21 +-
src/duk_js_ops.c | 16 +-
src/duk_js_var.c | 2 +-
src/duk_strings.c | 1 -
src/duk_strings.h | 2 -
src/duk_tval.h | 18 +-
website/api/concepts.html | 5 +-
website/api/duk_get_magic.txt | 5 +
website/api/duk_is_lightfunc.txt | 17 +
website/api/duk_push_c_function.txt | 3 +
website/api/duk_push_c_lightfunc.txt | 58 +
website/api/duk_to_boolean.txt | 6 +-
website/api/duk_to_defaultvalue.txt | 3 +-
website/api/duk_to_int.txt | 6 +-
website/api/duk_to_int32.txt | 6 +-
website/api/duk_to_lstring.txt | 7 +-
website/api/duk_to_number.txt | 6 +-
website/api/duk_to_primitive.txt | 2 +
website/api/duk_to_string.txt | 7 +-
website/api/duk_to_uint16.txt | 6 +-
website/api/duk_to_uint32.txt | 6 +-
website/api/ref-custom-type-coercion.html | 4 +
website/guide/custombehavior.html | 4 +-
website/guide/customjson.html | 9 +-
website/guide/duktapebuiltins.html | 13 +
website/guide/functionobjects.html | 44 +-
website/guide/stacktypes.html | 65 +-
website/guide/typealgorithms.html | 128 +-
55 files changed, 3864 insertions(+), 584 deletions(-)
create mode 100644 ecmascript-testcases/test-dev-lightfunc-accessor.js
create mode 100644 ecmascript-testcases/test-dev-lightfunc-finalizer.js
create mode 100644 website/api/duk_is_lightfunc.txt
create mode 100644 website/api/duk_push_c_lightfunc.txt
create mode 100644 website/api/ref-custom-type-coercion.html
diff --git a/api-testcases/test-dev-lightfunc.c b/api-testcases/test-dev-lightfunc.c
index 49283a921f..1c3683f115 100644
--- a/api-testcases/test-dev-lightfunc.c
+++ b/api-testcases/test-dev-lightfunc.c
@@ -1,29 +1,803 @@
/*
- * Behavior of lightweight functions in various situations.
+ * Behavior of lightweight functions from C code in various situations.
*
* Also documents the detailed behavior and limitations of lightfuncs.
*/
+static duk_ret_t my_addtwo_lfunc(duk_context *ctx) {
+ printf("addtwo entry top: %ld\n", (long) duk_get_top(ctx));
+
+ duk_push_current_function(ctx);
+ duk_get_prop_string(ctx, -1, "length");
+ printf("addtwo 'length' property: %s\n", duk_safe_to_string(ctx, -1));
+ duk_pop(ctx);
+ printf("addtwo duk_get_length: %ld\n", (long) duk_get_length(ctx, -1));
+ printf("addtwo magic: %ld\n", (long) duk_get_magic(ctx, -1));
+ printf("current magic: %ld\n", (long) duk_get_current_magic(ctx));
+ duk_pop(ctx);
+
+ duk_push_number(ctx, duk_require_number(ctx, 0) + duk_require_number(ctx, 1));
+ printf("addtwo final top: %ld\n", (long) duk_get_top(ctx));
+ return 1;
+}
+
+static duk_ret_t my_dummy_func(duk_context *ctx) {
+ (void) ctx;
+ return DUK_RET_INTERNAL_ERROR;
+}
+
/*===
-FIXME
-still here
+*** test_is_lightfunc (duk_safe_call)
+0: is_lightfunc: 0
+1: is_lightfunc: 0
+2: is_lightfunc: 0
+3: is_lightfunc: 0
+4: is_lightfunc: 1
+==> rc=0, result='undefined'
===*/
-/* FIXME: test all arg counts and lengths, including varargs */
+static duk_ret_t test_is_lightfunc(duk_context *ctx) {
+ duk_idx_t i, n;
+
+ /* Just a few spot checks. */
-/* FIXME: duk_to_object() coercion, stack policy */
+ duk_push_undefined(ctx);
+ duk_push_null(ctx);
+ duk_push_object(ctx);
+ duk_push_c_function(ctx, my_dummy_func, 0);
+ duk_push_c_lightfunc(ctx, my_dummy_func, 0, 0, 0);
-/* FIXME: duk_push_current_function() for a lightfunc, check magic, perhaps length */
+ for (i = 0, n = duk_get_top(ctx); i < n; i++) {
+ printf("%ld: is_lightfunc: %ld\n", (long) i, (long) duk_is_lightfunc(ctx, i));
+ }
+
+ return 0;
+}
+
+/*===
+*** test_simple_push (duk_safe_call)
+top before lfunc push: 2
+push retval: 2
+top after lfunc push: 3
+type at top: 9
+typemask at top: 0x0200
+addtwo entry top: 2
+addtwo 'length' property: 3
+addtwo duk_get_length: 3
+addtwo magic: -66
+current magic: -66
+addtwo final top: 3
+result: 357
+final top: 3
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_simple_push(duk_context *ctx) {
+ duk_idx_t ret;
+
+ duk_set_top(ctx, 0);
+ duk_push_undefined(ctx); /* dummy padding */
+ duk_push_undefined(ctx);
+
+ printf("top before lfunc push: %ld\n", (long) duk_get_top(ctx));
+ ret = duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, -0x42 /*magic*/);
+ printf("push retval: %ld\n", (long) ret);
+ printf("top after lfunc push: %ld\n", (long) duk_get_top(ctx));
+ printf("type at top: %ld\n", (long) duk_get_type(ctx, -1));
+ printf("typemask at top: 0x%04lx\n", (long) duk_get_type_mask(ctx, -1));
+
+ duk_push_string(ctx, "dummy this");
+ duk_push_int(ctx, 123);
+ duk_push_int(ctx, 234);
+ duk_push_int(ctx, 345);
+ duk_call_method(ctx, 3 /*nargs*/); /* [ ... lfunc this 123 234 345 ] -> [ ... retval ] */
+ printf("result: %s\n", duk_safe_to_string(ctx, -1));
+
+ printf("final top: %ld\n", (long) duk_get_top(ctx));
+ return 0;
+}
+
+/*===
+*** test_magic (duk_safe_call)
+i=-256, res=Error: invalid call args
+i=-255, res=Error: invalid call args
+i=-254, res=Error: invalid call args
+i=-253, res=Error: invalid call args
+i=-252, res=Error: invalid call args
+i=-251, res=Error: invalid call args
+i=-250, res=Error: invalid call args
+i=-249, res=Error: invalid call args
+i=-248, res=Error: invalid call args
+i=-247, res=Error: invalid call args
+i=-246, res=Error: invalid call args
+i=-245, res=Error: invalid call args
+i=-244, res=Error: invalid call args
+i=-243, res=Error: invalid call args
+i=-242, res=Error: invalid call args
+i=-241, res=Error: invalid call args
+i=-240, res=Error: invalid call args
+i=-239, res=Error: invalid call args
+i=-238, res=Error: invalid call args
+i=-237, res=Error: invalid call args
+i=-236, res=Error: invalid call args
+i=-235, res=Error: invalid call args
+i=-234, res=Error: invalid call args
+i=-233, res=Error: invalid call args
+i=-232, res=Error: invalid call args
+i=-231, res=Error: invalid call args
+i=-230, res=Error: invalid call args
+i=-229, res=Error: invalid call args
+i=-228, res=Error: invalid call args
+i=-227, res=Error: invalid call args
+i=-226, res=Error: invalid call args
+i=-225, res=Error: invalid call args
+i=-224, res=Error: invalid call args
+i=-223, res=Error: invalid call args
+i=-222, res=Error: invalid call args
+i=-221, res=Error: invalid call args
+i=-220, res=Error: invalid call args
+i=-219, res=Error: invalid call args
+i=-218, res=Error: invalid call args
+i=-217, res=Error: invalid call args
+i=-216, res=Error: invalid call args
+i=-215, res=Error: invalid call args
+i=-214, res=Error: invalid call args
+i=-213, res=Error: invalid call args
+i=-212, res=Error: invalid call args
+i=-211, res=Error: invalid call args
+i=-210, res=Error: invalid call args
+i=-209, res=Error: invalid call args
+i=-208, res=Error: invalid call args
+i=-207, res=Error: invalid call args
+i=-206, res=Error: invalid call args
+i=-205, res=Error: invalid call args
+i=-204, res=Error: invalid call args
+i=-203, res=Error: invalid call args
+i=-202, res=Error: invalid call args
+i=-201, res=Error: invalid call args
+i=-200, res=Error: invalid call args
+i=-199, res=Error: invalid call args
+i=-198, res=Error: invalid call args
+i=-197, res=Error: invalid call args
+i=-196, res=Error: invalid call args
+i=-195, res=Error: invalid call args
+i=-194, res=Error: invalid call args
+i=-193, res=Error: invalid call args
+i=-192, res=Error: invalid call args
+i=-191, res=Error: invalid call args
+i=-190, res=Error: invalid call args
+i=-189, res=Error: invalid call args
+i=-188, res=Error: invalid call args
+i=-187, res=Error: invalid call args
+i=-186, res=Error: invalid call args
+i=-185, res=Error: invalid call args
+i=-184, res=Error: invalid call args
+i=-183, res=Error: invalid call args
+i=-182, res=Error: invalid call args
+i=-181, res=Error: invalid call args
+i=-180, res=Error: invalid call args
+i=-179, res=Error: invalid call args
+i=-178, res=Error: invalid call args
+i=-177, res=Error: invalid call args
+i=-176, res=Error: invalid call args
+i=-175, res=Error: invalid call args
+i=-174, res=Error: invalid call args
+i=-173, res=Error: invalid call args
+i=-172, res=Error: invalid call args
+i=-171, res=Error: invalid call args
+i=-170, res=Error: invalid call args
+i=-169, res=Error: invalid call args
+i=-168, res=Error: invalid call args
+i=-167, res=Error: invalid call args
+i=-166, res=Error: invalid call args
+i=-165, res=Error: invalid call args
+i=-164, res=Error: invalid call args
+i=-163, res=Error: invalid call args
+i=-162, res=Error: invalid call args
+i=-161, res=Error: invalid call args
+i=-160, res=Error: invalid call args
+i=-159, res=Error: invalid call args
+i=-158, res=Error: invalid call args
+i=-157, res=Error: invalid call args
+i=-156, res=Error: invalid call args
+i=-155, res=Error: invalid call args
+i=-154, res=Error: invalid call args
+i=-153, res=Error: invalid call args
+i=-152, res=Error: invalid call args
+i=-151, res=Error: invalid call args
+i=-150, res=Error: invalid call args
+i=-149, res=Error: invalid call args
+i=-148, res=Error: invalid call args
+i=-147, res=Error: invalid call args
+i=-146, res=Error: invalid call args
+i=-145, res=Error: invalid call args
+i=-144, res=Error: invalid call args
+i=-143, res=Error: invalid call args
+i=-142, res=Error: invalid call args
+i=-141, res=Error: invalid call args
+i=-140, res=Error: invalid call args
+i=-139, res=Error: invalid call args
+i=-138, res=Error: invalid call args
+i=-137, res=Error: invalid call args
+i=-136, res=Error: invalid call args
+i=-135, res=Error: invalid call args
+i=-134, res=Error: invalid call args
+i=-133, res=Error: invalid call args
+i=-132, res=Error: invalid call args
+i=-131, res=Error: invalid call args
+i=-130, res=Error: invalid call args
+i=-129, res=Error: invalid call args
+i=-128, res=1
+i=-127, res=1
+i=-126, res=1
+i=-125, res=1
+i=-124, res=1
+i=-123, res=1
+i=-122, res=1
+i=-121, res=1
+i=-120, res=1
+i=-119, res=1
+i=-118, res=1
+i=-117, res=1
+i=-116, res=1
+i=-115, res=1
+i=-114, res=1
+i=-113, res=1
+i=-112, res=1
+i=-111, res=1
+i=-110, res=1
+i=-109, res=1
+i=-108, res=1
+i=-107, res=1
+i=-106, res=1
+i=-105, res=1
+i=-104, res=1
+i=-103, res=1
+i=-102, res=1
+i=-101, res=1
+i=-100, res=1
+i=-99, res=1
+i=-98, res=1
+i=-97, res=1
+i=-96, res=1
+i=-95, res=1
+i=-94, res=1
+i=-93, res=1
+i=-92, res=1
+i=-91, res=1
+i=-90, res=1
+i=-89, res=1
+i=-88, res=1
+i=-87, res=1
+i=-86, res=1
+i=-85, res=1
+i=-84, res=1
+i=-83, res=1
+i=-82, res=1
+i=-81, res=1
+i=-80, res=1
+i=-79, res=1
+i=-78, res=1
+i=-77, res=1
+i=-76, res=1
+i=-75, res=1
+i=-74, res=1
+i=-73, res=1
+i=-72, res=1
+i=-71, res=1
+i=-70, res=1
+i=-69, res=1
+i=-68, res=1
+i=-67, res=1
+i=-66, res=1
+i=-65, res=1
+i=-64, res=1
+i=-63, res=1
+i=-62, res=1
+i=-61, res=1
+i=-60, res=1
+i=-59, res=1
+i=-58, res=1
+i=-57, res=1
+i=-56, res=1
+i=-55, res=1
+i=-54, res=1
+i=-53, res=1
+i=-52, res=1
+i=-51, res=1
+i=-50, res=1
+i=-49, res=1
+i=-48, res=1
+i=-47, res=1
+i=-46, res=1
+i=-45, res=1
+i=-44, res=1
+i=-43, res=1
+i=-42, res=1
+i=-41, res=1
+i=-40, res=1
+i=-39, res=1
+i=-38, res=1
+i=-37, res=1
+i=-36, res=1
+i=-35, res=1
+i=-34, res=1
+i=-33, res=1
+i=-32, res=1
+i=-31, res=1
+i=-30, res=1
+i=-29, res=1
+i=-28, res=1
+i=-27, res=1
+i=-26, res=1
+i=-25, res=1
+i=-24, res=1
+i=-23, res=1
+i=-22, res=1
+i=-21, res=1
+i=-20, res=1
+i=-19, res=1
+i=-18, res=1
+i=-17, res=1
+i=-16, res=1
+i=-15, res=1
+i=-14, res=1
+i=-13, res=1
+i=-12, res=1
+i=-11, res=1
+i=-10, res=1
+i=-9, res=1
+i=-8, res=1
+i=-7, res=1
+i=-6, res=1
+i=-5, res=1
+i=-4, res=1
+i=-3, res=1
+i=-2, res=1
+i=-1, res=1
+i=0, res=1
+i=1, res=1
+i=2, res=1
+i=3, res=1
+i=4, res=1
+i=5, res=1
+i=6, res=1
+i=7, res=1
+i=8, res=1
+i=9, res=1
+i=10, res=1
+i=11, res=1
+i=12, res=1
+i=13, res=1
+i=14, res=1
+i=15, res=1
+i=16, res=1
+i=17, res=1
+i=18, res=1
+i=19, res=1
+i=20, res=1
+i=21, res=1
+i=22, res=1
+i=23, res=1
+i=24, res=1
+i=25, res=1
+i=26, res=1
+i=27, res=1
+i=28, res=1
+i=29, res=1
+i=30, res=1
+i=31, res=1
+i=32, res=1
+i=33, res=1
+i=34, res=1
+i=35, res=1
+i=36, res=1
+i=37, res=1
+i=38, res=1
+i=39, res=1
+i=40, res=1
+i=41, res=1
+i=42, res=1
+i=43, res=1
+i=44, res=1
+i=45, res=1
+i=46, res=1
+i=47, res=1
+i=48, res=1
+i=49, res=1
+i=50, res=1
+i=51, res=1
+i=52, res=1
+i=53, res=1
+i=54, res=1
+i=55, res=1
+i=56, res=1
+i=57, res=1
+i=58, res=1
+i=59, res=1
+i=60, res=1
+i=61, res=1
+i=62, res=1
+i=63, res=1
+i=64, res=1
+i=65, res=1
+i=66, res=1
+i=67, res=1
+i=68, res=1
+i=69, res=1
+i=70, res=1
+i=71, res=1
+i=72, res=1
+i=73, res=1
+i=74, res=1
+i=75, res=1
+i=76, res=1
+i=77, res=1
+i=78, res=1
+i=79, res=1
+i=80, res=1
+i=81, res=1
+i=82, res=1
+i=83, res=1
+i=84, res=1
+i=85, res=1
+i=86, res=1
+i=87, res=1
+i=88, res=1
+i=89, res=1
+i=90, res=1
+i=91, res=1
+i=92, res=1
+i=93, res=1
+i=94, res=1
+i=95, res=1
+i=96, res=1
+i=97, res=1
+i=98, res=1
+i=99, res=1
+i=100, res=1
+i=101, res=1
+i=102, res=1
+i=103, res=1
+i=104, res=1
+i=105, res=1
+i=106, res=1
+i=107, res=1
+i=108, res=1
+i=109, res=1
+i=110, res=1
+i=111, res=1
+i=112, res=1
+i=113, res=1
+i=114, res=1
+i=115, res=1
+i=116, res=1
+i=117, res=1
+i=118, res=1
+i=119, res=1
+i=120, res=1
+i=121, res=1
+i=122, res=1
+i=123, res=1
+i=124, res=1
+i=125, res=1
+i=126, res=1
+i=127, res=1
+i=128, res=Error: invalid call args
+i=129, res=Error: invalid call args
+i=130, res=Error: invalid call args
+i=131, res=Error: invalid call args
+i=132, res=Error: invalid call args
+i=133, res=Error: invalid call args
+i=134, res=Error: invalid call args
+i=135, res=Error: invalid call args
+i=136, res=Error: invalid call args
+i=137, res=Error: invalid call args
+i=138, res=Error: invalid call args
+i=139, res=Error: invalid call args
+i=140, res=Error: invalid call args
+i=141, res=Error: invalid call args
+i=142, res=Error: invalid call args
+i=143, res=Error: invalid call args
+i=144, res=Error: invalid call args
+i=145, res=Error: invalid call args
+i=146, res=Error: invalid call args
+i=147, res=Error: invalid call args
+i=148, res=Error: invalid call args
+i=149, res=Error: invalid call args
+i=150, res=Error: invalid call args
+i=151, res=Error: invalid call args
+i=152, res=Error: invalid call args
+i=153, res=Error: invalid call args
+i=154, res=Error: invalid call args
+i=155, res=Error: invalid call args
+i=156, res=Error: invalid call args
+i=157, res=Error: invalid call args
+i=158, res=Error: invalid call args
+i=159, res=Error: invalid call args
+i=160, res=Error: invalid call args
+i=161, res=Error: invalid call args
+i=162, res=Error: invalid call args
+i=163, res=Error: invalid call args
+i=164, res=Error: invalid call args
+i=165, res=Error: invalid call args
+i=166, res=Error: invalid call args
+i=167, res=Error: invalid call args
+i=168, res=Error: invalid call args
+i=169, res=Error: invalid call args
+i=170, res=Error: invalid call args
+i=171, res=Error: invalid call args
+i=172, res=Error: invalid call args
+i=173, res=Error: invalid call args
+i=174, res=Error: invalid call args
+i=175, res=Error: invalid call args
+i=176, res=Error: invalid call args
+i=177, res=Error: invalid call args
+i=178, res=Error: invalid call args
+i=179, res=Error: invalid call args
+i=180, res=Error: invalid call args
+i=181, res=Error: invalid call args
+i=182, res=Error: invalid call args
+i=183, res=Error: invalid call args
+i=184, res=Error: invalid call args
+i=185, res=Error: invalid call args
+i=186, res=Error: invalid call args
+i=187, res=Error: invalid call args
+i=188, res=Error: invalid call args
+i=189, res=Error: invalid call args
+i=190, res=Error: invalid call args
+i=191, res=Error: invalid call args
+i=192, res=Error: invalid call args
+i=193, res=Error: invalid call args
+i=194, res=Error: invalid call args
+i=195, res=Error: invalid call args
+i=196, res=Error: invalid call args
+i=197, res=Error: invalid call args
+i=198, res=Error: invalid call args
+i=199, res=Error: invalid call args
+i=200, res=Error: invalid call args
+i=201, res=Error: invalid call args
+i=202, res=Error: invalid call args
+i=203, res=Error: invalid call args
+i=204, res=Error: invalid call args
+i=205, res=Error: invalid call args
+i=206, res=Error: invalid call args
+i=207, res=Error: invalid call args
+i=208, res=Error: invalid call args
+i=209, res=Error: invalid call args
+i=210, res=Error: invalid call args
+i=211, res=Error: invalid call args
+i=212, res=Error: invalid call args
+i=213, res=Error: invalid call args
+i=214, res=Error: invalid call args
+i=215, res=Error: invalid call args
+i=216, res=Error: invalid call args
+i=217, res=Error: invalid call args
+i=218, res=Error: invalid call args
+i=219, res=Error: invalid call args
+i=220, res=Error: invalid call args
+i=221, res=Error: invalid call args
+i=222, res=Error: invalid call args
+i=223, res=Error: invalid call args
+i=224, res=Error: invalid call args
+i=225, res=Error: invalid call args
+i=226, res=Error: invalid call args
+i=227, res=Error: invalid call args
+i=228, res=Error: invalid call args
+i=229, res=Error: invalid call args
+i=230, res=Error: invalid call args
+i=231, res=Error: invalid call args
+i=232, res=Error: invalid call args
+i=233, res=Error: invalid call args
+i=234, res=Error: invalid call args
+i=235, res=Error: invalid call args
+i=236, res=Error: invalid call args
+i=237, res=Error: invalid call args
+i=238, res=Error: invalid call args
+i=239, res=Error: invalid call args
+i=240, res=Error: invalid call args
+i=241, res=Error: invalid call args
+i=242, res=Error: invalid call args
+i=243, res=Error: invalid call args
+i=244, res=Error: invalid call args
+i=245, res=Error: invalid call args
+i=246, res=Error: invalid call args
+i=247, res=Error: invalid call args
+i=248, res=Error: invalid call args
+i=249, res=Error: invalid call args
+i=250, res=Error: invalid call args
+i=251, res=Error: invalid call args
+i=252, res=Error: invalid call args
+i=253, res=Error: invalid call args
+i=254, res=Error: invalid call args
+i=255, res=Error: invalid call args
+i=256, res=Error: invalid call args
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_magic_raw(duk_context *ctx) {
+ int i = duk_require_int(ctx, -1);
+ duk_idx_t ret;
+
+ ret = duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, i /*magic*/);
+ duk_push_int(ctx, (duk_int_t) ret);
+ return 1;
+}
static duk_ret_t test_magic(duk_context *ctx) {
- /* FIXME */
- /* magic limits, C api test, check what happens if you exceed */
+ int i;
+
+ for (i = -256; i <= 256; i++) {
+ duk_push_int(ctx, i);
+ duk_safe_call(ctx, test_magic_raw, 1, 1);
+ printf("i=%ld, res=%s\n", (long) i, duk_safe_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+ return 0;
+}
+
+/*===
+*** test_length_values (duk_safe_call)
+i=-16, res=Error: invalid call args
+i=-15, res=Error: invalid call args
+i=-14, res=Error: invalid call args
+i=-13, res=Error: invalid call args
+i=-12, res=Error: invalid call args
+i=-11, res=Error: invalid call args
+i=-10, res=Error: invalid call args
+i=-9, res=Error: invalid call args
+i=-8, res=Error: invalid call args
+i=-7, res=Error: invalid call args
+i=-6, res=Error: invalid call args
+i=-5, res=Error: invalid call args
+i=-4, res=Error: invalid call args
+i=-3, res=Error: invalid call args
+i=-2, res=Error: invalid call args
+i=-1, res=Error: invalid call args
+i=0, res=1
+i=1, res=1
+i=2, res=1
+i=3, res=1
+i=4, res=1
+i=5, res=1
+i=6, res=1
+i=7, res=1
+i=8, res=1
+i=9, res=1
+i=10, res=1
+i=11, res=1
+i=12, res=1
+i=13, res=1
+i=14, res=1
+i=15, res=1
+i=16, res=Error: invalid call args
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_length_raw(duk_context *ctx) {
+ int i = duk_require_int(ctx, -1);
+ duk_idx_t ret;
+
+ ret = duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, i /*length*/, 0x42 /*magic*/);
+ duk_push_int(ctx, (duk_int_t) ret);
+ return 1;
+}
+
+static duk_ret_t test_length_values(duk_context *ctx) {
+ int i;
+
+ for (i = -16; i <= 16; i++) {
+ duk_push_int(ctx, i);
+ duk_safe_call(ctx, test_length_raw, 1, 1);
+ printf("i=%ld, res=%s\n", (long) i, duk_safe_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+
return 0;
}
+/*===
+*** test_nargs_values (duk_safe_call)
+i=-16, nargs=-16, res=Error: invalid call args
+i=-15, nargs=-15, res=Error: invalid call args
+i=-14, nargs=-14, res=Error: invalid call args
+i=-13, nargs=-13, res=Error: invalid call args
+i=-12, nargs=-12, res=Error: invalid call args
+i=-11, nargs=-11, res=Error: invalid call args
+i=-10, nargs=-10, res=Error: invalid call args
+i=-9, nargs=-9, res=Error: invalid call args
+i=-8, nargs=-8, res=Error: invalid call args
+i=-7, nargs=-7, res=Error: invalid call args
+i=-6, nargs=-6, res=Error: invalid call args
+i=-5, nargs=-5, res=Error: invalid call args
+i=-4, nargs=-4, res=Error: invalid call args
+i=-3, nargs=-3, res=Error: invalid call args
+i=-2, nargs=-2, res=Error: invalid call args
+i=-1, nargs=-1 (varargs), res=1
+i=0, nargs=0, res=1
+i=1, nargs=1, res=1
+i=2, nargs=2, res=1
+i=3, nargs=3, res=1
+i=4, nargs=4, res=1
+i=5, nargs=5, res=1
+i=6, nargs=6, res=1
+i=7, nargs=7, res=1
+i=8, nargs=8, res=1
+i=9, nargs=9, res=1
+i=10, nargs=10, res=1
+i=11, nargs=11, res=1
+i=12, nargs=12, res=1
+i=13, nargs=13, res=1
+i=14, nargs=14, res=1
+i=15, nargs=15, res=Error: invalid call args
+i=16, nargs=16, res=Error: invalid call args
+i=17, nargs=-1 (varargs), res=1
+i=18, nargs=18, res=Error: invalid call args
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_nargs_raw(duk_context *ctx) {
+ int i = duk_require_int(ctx, -1);
+ duk_idx_t ret;
+
+ ret = duk_push_c_lightfunc(ctx, my_addtwo_lfunc, i /*nargs*/, 2 /*length*/, 0x42 /*magic*/);
+ duk_push_int(ctx, (duk_int_t) ret);
+ return 1;
+}
+
+static duk_ret_t test_nargs_values(duk_context *ctx) {
+ int i;
+ int nargs;
+ int is_vararg;
+
+ for (i = -16; i <= 18; i++) {
+ if (i == 17) {
+ duk_push_int(ctx, DUK_VARARGS);
+ } else {
+ duk_push_int(ctx, i);
+ }
+ nargs = duk_get_int(ctx, -1);
+ is_vararg = (nargs == DUK_VARARGS);
+ duk_safe_call(ctx, test_nargs_raw, 1, 1);
+ printf("i=%ld, nargs=%ld%s, res=%s\n",
+ (long) i, (long) nargs, (is_vararg ? " (varargs)" : ""),
+ duk_safe_to_string(ctx, -1));
+ duk_pop(ctx);
+ }
+
+ return 0;
+}
+
+/*===
+*** test_enum (duk_safe_call)
+enum defaults
+top: 1
+enum nonenumerable
+key: length
+key: name
+key: constructor
+key: toString
+key: apply
+key: call
+key: bind
+key: __proto__
+key: toLocaleString
+key: valueOf
+key: hasOwnProperty
+key: isPrototypeOf
+key: propertyIsEnumerable
+top: 1
+enum own
+top: 1
+enum own non-enumerable
+key: length
+key: name
+top: 1
+==> rc=0, result='undefined'
+===*/
+
static duk_ret_t test_enum(duk_context *ctx) {
- /* FIXME: push lightfunc here instead of relying on a built-in */
- duk_eval_string(ctx, "Math.max");
+ (void) duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, 0x42 /*magic*/);
printf("enum defaults\n");
duk_enum(ctx, -1, 0);
@@ -64,12 +838,163 @@ static duk_ret_t test_enum(duk_context *ctx) {
return 0;
}
+/*===
+*** test_get_length (duk_safe_call)
+lightFunc len: 3
+ecmaFunc.len: 3
+final top: 2
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_get_length(duk_context *ctx) {
+ duk_size_t len;
+
+ /*
+ * Lightfunc length is its virtual 'length' property, same as for
+ * ordinary functions.
+ */
+
+ (void) duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, 0x42 /*magic*/);
+
+ len = duk_get_length(ctx, -1);
+ printf("lightFunc len: %ld\n", (long) len);
+
+ duk_eval_string(ctx, "(function (a,b,c) {})");
+ len = duk_get_length(ctx, -1);
+ printf("ecmaFunc.len: %ld\n", (long) len);
+
+ printf("final top: %ld\n", (long) duk_get_top(ctx));
+ return 0;
+}
+
+/*===
+*** test_to_object (duk_safe_call)
+tag before: 9
+tag after: 6
+addtwo entry top: 2
+addtwo 'length' property: 3
+addtwo duk_get_length: 3
+addtwo magic: 66
+current magic: 66
+addtwo final top: 3
+result: 357
+final top: 1
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_to_object(duk_context *ctx) {
+ (void) duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, 0x42 /*magic*/);
+
+ printf("tag before: %ld\n", (long) duk_get_type(ctx, -1));
+
+ duk_to_object(ctx, -1);
+
+ printf("tag after: %ld\n", (long) duk_get_type(ctx, -1));
+
+ /* The coerced function works as before */
+
+ duk_push_int(ctx, 123);
+ duk_push_int(ctx, 234);
+ duk_push_int(ctx, 345);
+ duk_call(ctx, 3);
+
+ printf("result: %s\n", duk_safe_to_string(ctx, -1));
+
+ printf("final top: %ld\n", (long) duk_get_top(ctx));
+ return 0;
+}
+
+/*===
+*** test_to_buffer (duk_safe_call)
+function light_PTR_4232() {(* light *)}
+final top: 1
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_to_buffer(duk_context *ctx) {
+ duk_size_t sz;
+ unsigned char *p;
+
+ (void) duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, 0x42 /*magic*/);
+
+ /*
+ * Lightfunc-to-buffer coercion currently produces a string: the
+ * lightfunc gets coerced to a string like a normal function would.
+ * The buffer is then filled with the bytes from this coercion.
+ *
+ * The output must be sanitized because it is platform specific.
+ */
+
+ p = (unsigned char *) duk_to_buffer(ctx, -1, &sz);
+ if (!p) {
+ printf("ptr is NULL\n");
+ } else {
+ /* Don't print length because it depends on pointer length
+ * and thus architecture.
+ */
+#if 0
+ printf("%ld: ", (long) sz);
+#endif
+
+ /* Sanitize with Ecmascript because it's easier... */
+ duk_eval_string(ctx, "(function (x) { return String(x)"
+ ".replace(/\\/\\*/g, '(*').replace(/\\*\\//g, '*)')"
+ ".replace(/light_[0-9a-fA-F]+_/g, 'light_PTR_'); })");
+ duk_dup(ctx, -2);
+ duk_call(ctx, 1);
+
+ printf("%s\n", duk_safe_to_string(ctx, -1));
+ duk_pop(ctx); /* pop temp */
+ }
+
+ printf("final top: %ld\n", (long) duk_get_top(ctx));
+ return 0;
+}
+
+/*===
+*** test_to_pointer (duk_safe_call)
+ptr is NULL: 1
+final top: 1
+==> rc=0, result='undefined'
+===*/
+
+static duk_ret_t test_to_pointer(duk_context *ctx) {
+ void *p;
+
+ (void) duk_push_c_lightfunc(ctx, my_addtwo_lfunc, 2 /*nargs*/, 3 /*length*/, 0x42 /*magic*/);
+
+ /*
+ * Lightfunc-to-pointer coercion currently produces a NULL: there is
+ * no portable way to cast a function pointer to a data pointer, as
+ * there may be segmentation etc involved. This could be improved to
+ * work on specific platforms.
+ */
+
+ p = duk_to_pointer(ctx, -1);
+ printf("ptr is NULL: %d\n", (int) (p == NULL ? 1 : 0));
+
+ printf("final top: %ld\n", (long) duk_get_top(ctx));
+ return 0;
+}
+
+/*===
+still here
+===*/
+
void test(duk_context *ctx) {
/* nargs / length limits, C api test, check what happens if you exceed */
/* Example of using lightfunc as a constructor, separate testcase, doc ref */
+ TEST_SAFE_CALL(test_is_lightfunc);
+ TEST_SAFE_CALL(test_simple_push);
TEST_SAFE_CALL(test_magic);
+ TEST_SAFE_CALL(test_length_values);
+ TEST_SAFE_CALL(test_nargs_values);
TEST_SAFE_CALL(test_enum);
+ TEST_SAFE_CALL(test_get_length);
+ TEST_SAFE_CALL(test_to_object);
+ TEST_SAFE_CALL(test_to_buffer);
+ TEST_SAFE_CALL(test_to_pointer);
printf("still here\n");
fflush(stdout);
diff --git a/api-testcases/test-to-defaultvalue.c b/api-testcases/test-to-defaultvalue.c
index 3091e66354..326257666f 100644
--- a/api-testcases/test-to-defaultvalue.c
+++ b/api-testcases/test-to-defaultvalue.c
@@ -6,7 +6,7 @@ index 1, type 6 -> 4, result: 123
index 2, type 6 -> 3, result: true
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
-==> rc=1, result='TypeError: not object'
+==> rc=1, result='TypeError: unexpected type'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index'
*** test_4 (duk_safe_call)
diff --git a/ecmascript-testcases/test-bi-duktape-act.js b/ecmascript-testcases/test-bi-duktape-act.js
index 326c3c2980..d52dae4341 100644
--- a/ecmascript-testcases/test-bi-duktape-act.js
+++ b/ecmascript-testcases/test-bi-duktape-act.js
@@ -1,7 +1,7 @@
/*===
-1 0 act
-2 10 basicTest
--3 21 global
+-3 26 global
===*/
function basicTest() {
@@ -13,7 +13,12 @@ function basicTest() {
// Property set may change between versions, but at least
// these should be present for now (there is also 'pc' but
// that isn't so useful.
- print(i, t.lineNumber, t.function.name);
+ //
+ // NOTE: normally Duktape.act.name is 'act' but when using
+ // DUK_OPT_LIGHTFUNC_BUILTINS Duktape.act() will be a lightfunc
+ // and have a generic name (e.g. lightfunc_deadbeef_1234). To
+ // make the test case generic, avoid printing Duktape.act name.
+ print(i, t.lineNumber, t.function === Duktape.act ? 'act' : t.function.name);
}
}
@@ -24,7 +29,7 @@ try {
}
/*===
-running on line: 37
+running on line: 42
===*/
/* Simulate Duktape.line(). */
diff --git a/ecmascript-testcases/test-bi-duktape.js b/ecmascript-testcases/test-bi-duktape.js
index d33b9b72f8..3c682e03c3 100644
--- a/ecmascript-testcases/test-bi-duktape.js
+++ b/ecmascript-testcases/test-bi-duktape.js
@@ -154,7 +154,7 @@ function propsTest() {
try {
propsTest();
} catch (e) {
- print(e);
+ print(e.stack || e);
}
/*===
@@ -211,7 +211,7 @@ print('encdec');
try {
encDecTest();
} catch (e) {
- print(e);
+ print(e.stack || e);
}
/*===
@@ -243,5 +243,5 @@ print('finalizer');
try {
finalizerTest();
} catch (e) {
- print(e);
+ print(e.stack || e);
}
diff --git a/ecmascript-testcases/test-dev-lightfunc-accessor.js b/ecmascript-testcases/test-dev-lightfunc-accessor.js
new file mode 100644
index 0000000000..9aa3575497
--- /dev/null
+++ b/ecmascript-testcases/test-dev-lightfunc-accessor.js
@@ -0,0 +1,162 @@
+/*
+ * A lighweight function can be used as a setter/getter but it will be
+ * coerced to an ordinary function by Object.defineProperty() and
+ * Object.defineProperties().
+ *
+ * For the most part this is transparent, but when the property descriptor
+ * is read back, the 'set' and 'get' properties will be ordinary functions
+ * and will not match the original values.
+ */
+
+/*---
+{
+ "custom": true,
+ "specialoptions": "DUK_OPT_LIGHTFUNC_BUILTINS"
+}
+---*/
+
+function isLightFunc(x) {
+ return Duktape.info(x)[0] == 9; // tag
+}
+
+/*===
+Object.defineProperty
+pd_in
+typeof get: function
+isLightFunc get: true
+typeof set: function
+isLightFunc set: true
+get(=Math.min)(9,-3,11,4): -3
+set(=Math.max)(9,-3,11,4): 11
+pd_out
+typeof get: function
+isLightFunc get: false
+typeof set: function
+isLightFunc set: false
+get(=Math.min)(9,-3,11,4): -3
+set(=Math.max)(9,-3,11,4): 11
+pd_in.get == pd_out.get: false
+pd_in.get === pd_out.get: false
+pd_in.set == pd_out.set: false
+pd_in.set === pd_out.set: false
+Object.defineProperties
+pd_in
+typeof get: function
+isLightFunc get: true
+typeof set: function
+isLightFunc set: true
+get(=Math.min)(9,-3,11,4): -3
+set(=Math.max)(9,-3,11,4): 11
+pd_out
+typeof get: function
+isLightFunc get: false
+typeof set: function
+isLightFunc set: false
+get(=Math.min)(9,-3,11,4): -3
+set(=Math.max)(9,-3,11,4): 11
+pd_in.get == pd_out.get: false
+pd_in.get === pd_out.get: false
+pd_in.set == pd_out.set: false
+pd_in.set === pd_out.set: false
+===*/
+
+function lightfuncAsAccessorTest() {
+ /*
+ * Accessor (setter/getter) properties are stored in the internal
+ * property table as a pair of duk_hobject pointers. On 32-bit
+ * platforms the property value slot is 8 bytes which fits either
+ * one duk_tval or two duk_hobject pointers. There is no space for
+ * lightfunc flags in the property slot, and increasing the slot
+ * size for accessors would be a bad trade-off.
+ *
+ * The current solution is to coerce a lightfunc into a full function
+ * when a user tries to use the lightfunc as a getter/setter. This
+ * works transparently for the most part. However, when the property
+ * descriptor is read back, the setter/getter is not a lightfunc and
+ * doesn't match the original argument.
+ */
+
+ var obj;
+ var pd_in, pd_out;
+
+ obj = {};
+ pd_in = {
+ get: Math.min,
+ set: Math.max,
+ enumerable: true,
+ configurable: true
+ };
+
+ print('Object.defineProperty');
+
+ print('pd_in');
+ print('typeof get:', typeof pd_in.get);
+ print('isLightFunc get:', isLightFunc(pd_in.get));
+ print('typeof set:', typeof pd_in.set);
+ print('isLightFunc set:', isLightFunc(pd_in.set));
+ print('get(=Math.min)(9,-3,11,4):', pd_in.get(9, -3, 11, 4));
+ print('set(=Math.max)(9,-3,11,4):', pd_in.set(9, -3, 11, 4));
+
+ Object.defineProperty(obj, 'prop', pd_in);
+ pd_out = Object.getOwnPropertyDescriptor(obj, 'prop');
+
+ print('pd_out');
+ print('typeof get:', typeof pd_out.get);
+ print('isLightFunc get:', isLightFunc(pd_out.get));
+ print('typeof set:', typeof pd_out.set);
+ print('isLightFunc set:', isLightFunc(pd_out.set));
+ print('get(=Math.min)(9,-3,11,4):', pd_out.get(9, -3, 11, 4));
+ print('set(=Math.max)(9,-3,11,4):', pd_out.set(9, -3, 11, 4));
+
+ // Never compares true: lightweight and normal functions never compare
+ // as equal.
+ print('pd_in.get == pd_out.get:', pd_in.get == pd_out.get);
+ print('pd_in.get === pd_out.get:', pd_in.get === pd_out.get);
+ print('pd_in.set == pd_out.set:', pd_in.set == pd_out.set);
+ print('pd_in.set === pd_out.set:', pd_in.set === pd_out.set);
+
+ /*
+ * Same test for Object.defineProperties() which has a different
+ * internal code path.
+ */
+
+ obj = {};
+ pd_in = {
+ get: Math.min,
+ set: Math.max,
+ enumerable: true,
+ configurable: true
+ };
+
+ print('Object.defineProperties');
+
+ print('pd_in');
+ print('typeof get:', typeof pd_in.get);
+ print('isLightFunc get:', isLightFunc(pd_in.get));
+ print('typeof set:', typeof pd_in.set);
+ print('isLightFunc set:', isLightFunc(pd_in.set));
+ print('get(=Math.min)(9,-3,11,4):', pd_in.get(9, -3, 11, 4));
+ print('set(=Math.max)(9,-3,11,4):', pd_in.set(9, -3, 11, 4));
+
+ Object.defineProperties(obj, { prop: pd_in });
+ pd_out = Object.getOwnPropertyDescriptor(obj, 'prop');
+
+ print('pd_out');
+ print('typeof get:', typeof pd_out.get);
+ print('isLightFunc get:', isLightFunc(pd_out.get));
+ print('typeof set:', typeof pd_out.set);
+ print('isLightFunc set:', isLightFunc(pd_out.set));
+ print('get(=Math.min)(9,-3,11,4):', pd_out.get(9, -3, 11, 4));
+ print('set(=Math.max)(9,-3,11,4):', pd_out.set(9, -3, 11, 4));
+
+ print('pd_in.get == pd_out.get:', pd_in.get == pd_out.get);
+ print('pd_in.get === pd_out.get:', pd_in.get === pd_out.get);
+ print('pd_in.set == pd_out.set:', pd_in.set == pd_out.set);
+ print('pd_in.set === pd_out.set:', pd_in.set === pd_out.set);
+}
+
+try {
+ lightfuncAsAccessorTest();
+} catch (e) {
+ print(e.stack || e);
+}
diff --git a/ecmascript-testcases/test-dev-lightfunc-finalizer.js b/ecmascript-testcases/test-dev-lightfunc-finalizer.js
new file mode 100644
index 0000000000..dafbd82ab7
--- /dev/null
+++ b/ecmascript-testcases/test-dev-lightfunc-finalizer.js
@@ -0,0 +1,59 @@
+/*
+ * A lightfunc cannot have a finalizer for two reasons:
+ *
+ * (1) Lightfuncs are primitive values and have no refcount, so they are
+ * not finalized like objects.
+ *
+ * (2) Even if they were finalized, they cannot store a finalizer reference.
+ * This could be worked around by storing a finalizer reference in
+ * Function.prototype and have that deal with lightfuncs.
+ *
+ * This testcase demonstrates that lightfunc finalizers don't work.
+ */
+
+/*---
+{
+ "custom": true,
+ "specialoptions": "DUK_OPT_LIGHTFUNC_BUILTINS"
+}
+---*/
+
+function isLightFunc(x) {
+ return Duktape.info(x)[0] == 9; // tag
+}
+
+/*===
+true
+TypeError
+===*/
+
+function lightfuncFinalizerTest() {
+ var lfunc = Math.max;
+
+ // Verify built-ins are lightfuncs
+ print(isLightFunc(lfunc));
+
+ // Attempt to set a finalizer on the lightfunc directly fails.
+ try {
+ Duktape.fin(lfunc, function (v) { print('lfunc finalizer'); });
+ } catch (e) {
+ print(e.name);
+ //print(e.stack);
+ }
+
+ // Finalizer can be set to Function.prototype but it won't get called
+ // because lightfuncs are primitive values without a refcount field.
+ Duktape.fin(Function.prototype, function (v) {
+ if (isLightFunc(v)) {
+ print('inherited finalizer for lightfunc');
+ }
+ });
+
+ lfunc = null;
+}
+
+try {
+ lightfuncFinalizerTest();
+} catch (e) {
+ print(e.stack || e);
+}
diff --git a/ecmascript-testcases/test-dev-lightfunc.js b/ecmascript-testcases/test-dev-lightfunc.js
index fb33304efa..55abd3e08b 100644
--- a/ecmascript-testcases/test-dev-lightfunc.js
+++ b/ecmascript-testcases/test-dev-lightfunc.js
@@ -2,6 +2,16 @@
* Behavior of lightweight functions in various situations.
*
* Also documents the detailed behavior and limitations of lightfuncs.
+ * Specific limitations that matter have their own testcases (e.g.
+ * setter/getter limitations).
+ *
+ * Exhaustive built-in method tests don't all make practical sense (e.g.
+ * using a lightfunc as a 'this' binding for Array methods or giving a
+ * lightfunc as an argument to new Date()), but they document what
+ * currently happens and ensure there are no assertion failures or such.
+ * These checks are not 100%, e.g. if 'this' and arguments may be lightfuncs,
+ * giving two lightfuncs may cause a TypeError for 'this' validation and
+ * never ensure that the other argument is handled correctly.
*/
/*---
@@ -44,13 +54,14 @@ function getNormalFunc() {
return Number;
}
-function sanitizeFuncToString(x) {
+function sanitizeLfunc(x) {
/* Escape Ecmascript comments which appear e.g. when coercing function
* values to string. Hide lightfunc pointer which is variable data.
*/
x = String(x);
- x = x.replace('/*', '(*').replace('*/', '*)');
- x = x.replace(/light_[0-9a-fA-F]+_/, 'light_PTR_', x);
+ x = x.replace(/\/\*/g, '(*').replace(/\*\//g, '*)');
+ x = x.replace(/light_[0-9a-fA-F]+_/g, 'light_PTR_', x);
+ x = x.replace(/LIGHT_[0-9a-fA-F]+_/g, 'LIGHT_PTR_', x);
return x;
}
@@ -58,6 +69,31 @@ function isLightFunc(x) {
return Duktape.info(x)[0] == 9; // tag
}
+function printTypedJx(v, name) {
+ print(name + ':', typeof v, Duktape.enc('jx', v));
+}
+
+function testTypedJx(func, name) {
+ var res;
+ name = name || func.name || '???';
+ try {
+ res = func();
+ print(name + ':', typeof res, Duktape.enc('jx', res));
+ } catch (e) {
+ print(name + ':', e.name);
+ //print('*** ' + e);
+ }
+}
+
+function sanitizeTraceback(x) {
+ x = x.replace(/\/tmp\/.*?:/g, 'TESTCASE:');
+ x = x.replace(/:\d+/g, ':NNN')
+ x = x.replace(/\/\*/g, '(*').replace(/\*\//g, '*)');
+ x = x.replace(/light_[0-9a-fA-F]+_/g, 'light_PTR_', x);
+ x = x.replace(/LIGHT_[0-9a-fA-F]+_/g, 'LIGHT_PTR_', x);
+ return x;
+}
+
/*===
light func support test
info.length: 1
@@ -79,6 +115,13 @@ function lightFuncSupportTest() {
print(Duktape.enc('jx', Duktape.info(fun)));
}
+try {
+ print('light func support test');
+ lightFuncSupportTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
typeof test
function
@@ -94,8 +137,319 @@ function typeofTest() {
print(typeof fun);
}
+try {
+ print('typeof test');
+ typeofTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+property assignment test
+strPlainNonStrict: success
+strPlainStrict: TypeError
+strObjectNonStrict: success
+strObjectStrict: success
+strObjectNonextNonStrict: success
+strObjectNonextStrict: TypeError
+lfuncNonStrict1: success
+lfuncNonStrict2: success
+lfuncStrict1: TypeError
+lfuncStrict2: TypeError
+===*/
+
+function propertyAssignmentTest() {
+ /*
+ * Trying to assign a property value for a plain string value is a
+ * silent error in non-strict mode and TypeError in strict mode.
+ */
+
+ var strPlainNonStrict = function () {
+ var str = 'foo';
+ str.foo = 'bar';
+ };
+ var strPlainStrict = function () {
+ 'use strict';
+ var str = 'foo';
+ str.foo = 'bar';
+ };
+ try {
+ strPlainNonStrict();
+ print('strPlainNonStrict:', 'success');
+ } catch (e) {
+ print('strPlainNonStrict:', e.name);
+ }
+
+ try {
+ strPlainStrict();
+ print('strPlainStrict:', 'success');
+ } catch (e) {
+ print('strPlainStrict:', e.name);
+ }
+
+ /*
+ * Plain strings are not a good analogy for lightfunc behavior because
+ * lightfuncs try to behave like full objects. A closer analogy would
+ * be how a String object behaves.
+ *
+ * Here the property assignment succeeds because new String() returns
+ * an extensible object.
+ */
+
+ var strObjectNonStrict = function () {
+ var str = new String('foo');
+ str.foo = 'bar';
+ };
+ var strObjectStrict = function () {
+ 'use strict';
+ var str = new String('foo');
+ str.foo = 'bar';
+ };
+ try {
+ strObjectNonStrict();
+ print('strObjectNonStrict:', 'success');
+ } catch (e) {
+ print('strObjectNonStrict:', e.name);
+ }
+
+ try {
+ strObjectStrict();
+ print('strObjectStrict:', 'success');
+ } catch (e) {
+ print('strObjectStrict:', e.name);
+ }
+
+ /*
+ * Even closer analogy would be a new String() object which was made
+ * non-extensible.
+ */
+
+ var strObjectNonextNonStrict = function () {
+ var str = new String('foo');
+ Object.preventExtensions(str);
+ str.foo = 'bar';
+ };
+ var strObjectNonextStrict = function () {
+ 'use strict';
+ var str = new String('foo');
+ Object.preventExtensions(str);
+ str.foo = 'bar';
+ };
+ try {
+ strObjectNonextNonStrict();
+ print('strObjectNonextNonStrict:', 'success');
+ } catch (e) {
+ print('strObjectNonextNonStrict:', e.name);
+ }
+
+ try {
+ strObjectNonextStrict();
+ print('strObjectNonextStrict:', 'success');
+ } catch (e) {
+ print('strObjectNonextStrict:', e.name);
+ }
+
+ /*
+ * Because a lightfunc is not extensible and all own properties are
+ * non-configurable, the best behavior is silent success in non-strict
+ * mode (same happens e.g. when writing over 'length' of a String)
+ * and a TypeError in strict mode.
+ *
+ * This is now the behavior but error messages are not ideal. When
+ * the virtual property exists, the error is "not writable"; when it
+ * doesn't exist, the error is "invalid base value" ("not extensible"
+ * would be better).
+ *
+ * Another quite justifiable behavior would be to pretend as if the
+ * lightfunc was coerced to a full Function object before assignment.
+ * The coerced Function would now be extensible, so it would be possible
+ * to write new properties with no error. The current assignment behavior
+ * is not fully consistent with Object.defineProperty() behavior.
+ */
+
+ var lfuncNonStrict1 = function () {
+ var lfunc = Math.max;
+ lfunc.name = 'foo';
+ }
+ var lfuncNonStrict2 = function () {
+ var lfunc = Math.max;
+ lfunc.nonexistent = 123;
+ }
+ var lfuncStrict1 = function () {
+ 'use strict';
+ var lfunc = Math.max;
+ lfunc.name = 'foo';
+ }
+ var lfuncStrict2 = function () {
+ 'use strict';
+ var lfunc = Math.max;
+ lfunc.nonexistent = 123;
+ }
+
+ try {
+ lfuncNonStrict1();
+ print('lfuncNonStrict1:', 'success');
+ } catch (e) {
+ print('lfuncNonStrict1:', e.name);
+ }
+ try {
+ lfuncNonStrict2();
+ print('lfuncNonStrict2:', 'success');
+ } catch (e) {
+ print('lfuncNonStrict2:', e.name);
+ }
+ try {
+ lfuncStrict1();
+ print('lfuncStrict1:', 'success');
+ } catch (e) {
+ print('lfuncStrict1:', e.name);
+ //print(e.stack);
+ }
+ try {
+ lfuncStrict2();
+ print('lfuncStrict2:', 'success');
+ } catch (e) {
+ print('lfuncStrict2:', e.name);
+ //print(e.stack);
+ }
+}
+
+try {
+ print('property assignment test');
+ propertyAssignmentTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+instanceof test
+{} instanceof lightfunc: TypeError
+false
+{} instanceof func-wo-prototype: TypeError
+lightFunc instanceof Function: true
+lightFunc instanceof Number: false
+lightFunc instanceof Object: true
+===*/
+
+function instanceofTest() {
+ var obj = {};
+ var lightFunc = getLightFunc();
+ var func;
+
+ /*
+ * When a lightweight function is on the right hand side, step 3
+ * of the algorithm in E5.1 Section 15.3.5.3 step will always
+ * throw a TypeError: lightweight functions don't have a virtual
+ * 'prototype' property.
+ */
+
+ try {
+ print('{} instanceof lightfunc:', obj instanceof lightFunc);
+ } catch (e) {
+ print('{} instanceof lightfunc:', e.name);
+ }
+
+ /*
+ * The same happens for an ordinary function which doesn't have a
+ * 'prototype' property. To demonstrate this we need a function
+ * without a 'prototype' property: normal Ecmascript functions
+ * always have the property and it's not configurable so we can't
+ * delete it. Luckily many non-constructor built-ins don't have
+ * the property so we can use one of them.
+ */
+
+ func = Math.cos;
+ print('prototype' in func);
+ try {
+ print('{} instanceof func-wo-prototype:', obj instanceof func);
+ } catch (e) {
+ print('{} instanceof func-wo-prototype:', e.name);
+ }
+
+ /*
+ * When a lightfunc appears on the left-hand side, the prototype
+ * walk begins with Function.prototype. Note that 'instanceof'
+ * never compares the original lhs value, it begins its walk from
+ * lhs's internal prototype.
+ */
+
+ try {
+ print('lightFunc instanceof Function:', lightFunc instanceof Function);
+ } catch (e) {
+ print('lightFunc instanceof Function:', e.name);
+ }
+
+ try {
+ print('lightFunc instanceof Number:', lightFunc instanceof Number);
+ } catch (e) {
+ print('lightFunc instanceof Number:', e.name);
+ }
+
+ try {
+ print('lightFunc instanceof Object:', lightFunc instanceof Object);
+ } catch (e) {
+ print('lightFunc instanceof Object:', e.name);
+ }
+}
+
+try {
+ print('instanceof test');
+ instanceofTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
comparison test
+0 0 true true
+0 1 false false
+0 2 false false
+0 3 false false
+0 4 false false
+0 5 false false
+0 6 false false
+1 0 false false
+1 1 true true
+1 2 false false
+1 3 false false
+1 4 false false
+1 5 false false
+1 6 false false
+2 0 false false
+2 1 false false
+2 2 true true
+2 3 false false
+2 4 false false
+2 5 false false
+2 6 false false
+3 0 false false
+3 1 false false
+3 2 false false
+3 3 true true
+3 4 false false
+3 5 false false
+3 6 false false
+4 0 false false
+4 1 false false
+4 2 false false
+4 3 false false
+4 4 true true
+4 5 false false
+4 6 false false
+5 0 false false
+5 1 false false
+5 2 false false
+5 3 false false
+5 4 false false
+5 5 true true
+5 6 false false
+6 0 false false
+6 1 false false
+6 2 false false
+6 3 false false
+6 4 false false
+6 5 false false
+6 6 true true
===*/
function comparisonTest() {
@@ -118,11 +472,13 @@ function comparisonTest() {
* different functions, it's important to include at least magic
* in the comparison.
*
+ * - Lightfunc never compares equal to an ordinary Function, even
+ * when the Function was created as ToObject(lightFunc). This
+ * mimics the general behavior for Function objects, which always
+ * compare strictly by reference.
*/
- // FIXME: coerced lightfunc vs. plain lightfunc? Ordinary functions
- // only compare equal only if the reference is exactly the same, so
- // perhaps the desired result for lightFunc == Object(lightFunc) is false?
+ // XXX: samevalue
for (i = 0; i < vals.length; i++) {
for (j = 0; j < vals.length; j++) {
@@ -131,11 +487,42 @@ function comparisonTest() {
}
}
+try {
+ print('comparison test');
+ comparisonTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+arithmetic test
+string: testfunction light_PTR_0511() {(* light *)}function light_PTR_0a11() {(* light *)}
+string: function light_PTR_0511() {(* light *)}function light_PTR_0a11() {(* light *)}
+string: function foo() {(* ecmascript *)}function bar() {(* ecmascript *)}
+===*/
+
+function arithmeticTest() {
+ function p(x) {
+ print(typeof x + ': ' + sanitizeLfunc(x));
+ }
+
+ p('test' + Math.cos + Math.sin);
+ p(Math.cos + Math.sin);
+ p((function foo(){}) + (function bar(){}));
+}
+
+try {
+ print('arithmetic test');
+ arithmeticTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
toString() test
-function lightfunc() {(* lightfunc *)}
-function lightfunc() {(* lightfunc *)}
-function lightfunc() {(* lightfunc *)}
+function light_PTR_002f() {(* light *)}
+function light_PTR_002f() {(* light *)}
+function light_PTR_002f() {(* light *)}
true
true
===*/
@@ -144,15 +531,15 @@ function toStringTest() {
/* String coercion of functions is not strictly defined - so here the
* coercion output can identify the function as a lightweight function.
*
- * Because the string coercion output includes Ecmascript comment chars,
- * we need some escaping here.
+ * Because the string coercion output includes Ecmascript comment chars
+ * and a variable pointer, we need sanitization before printing.
*/
var fun = getLightFunc();
- print(sanitizeFuncToString(String(fun)));
- print(sanitizeFuncToString(fun.toString()));
- print(sanitizeFuncToString(Function.prototype.toString.call(fun)));
+ print(sanitizeLfunc(String(fun)));
+ print(sanitizeLfunc(fun.toString()));
+ print(sanitizeLfunc(Function.prototype.toString.call(fun)));
/* ToString(fun) and Function.prototype.toString(fun) should match for
* lightfuncs.
@@ -161,16 +548,24 @@ function toStringTest() {
print(String(fun) === Function.prototype.toString.call(fun));
}
+try {
+ print('toString() test');
+ toStringTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
toObject() test
-no caching: false
+caching: false
length: 2 2
-name: lightfunc lightfunc
+name: light_PTR_002f light_PTR_002f
typeof: function function
internal prototype is Function.prototype: true true
external prototype is not set: true
internal prototypes match: true
external prototypes match (do not exist): true
+isExtensible: false true
Math.max test: 9 9
length: 1 1
===*/
@@ -190,10 +585,10 @@ function toObjectTest() {
var normalFunc = Object(lightFunc);
// Object coercion always results in a new object, there is no "caching"
- print('no caching:', Object(lightFunc) === Object(lightFunc));
+ print('caching:', Object(lightFunc) === Object(lightFunc));
print('length:', lightFunc.length, normalFunc.length);
- print('name:', lightFunc.name, normalFunc.name);
+ print('name:', sanitizeLfunc(lightFunc.name), sanitizeLfunc(normalFunc.name));
print('typeof:', typeof lightFunc, typeof normalFunc);
print('internal prototype is Function.prototype:',
Object.getPrototypeOf(lightFunc) === Function.prototype,
@@ -221,7 +616,141 @@ function toObjectTest() {
normalFunc = Object(lightFunc);
print('length:', lightFunc.length, normalFunc.length);
- // FIXME: other properties
+ // XXX: other properties
+}
+
+try {
+ print('toObject() test');
+ toObjectTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+toBoolean() test
+true
+true
+===*/
+
+function toBooleanTest() {
+ var lfunc = Math.cos;
+ var nfunc = function foo() {};
+
+ print(Boolean(lfunc));
+ print(Boolean(nfunc));
+}
+
+try {
+ print('toBoolean() test');
+ toBooleanTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+toBuffer() test
+buffer 44: function light_PTR_0511() {(* light *)}
+buffer 44: function light_PTR_0a11() {(* light *)}
+===*/
+
+function toBufferTest() {
+ /* Duktape.Buffer(v) does -not- implement the same semantics as the
+ * ToBuffer() coercion provided by duk_to_buffer() API call. The API
+ * ToBuffer() is not directly available, but Duktape.enc('base64', ...)
+ * will (currently) first call a duk_to_buffer() on the argument so we
+ * can use that to get at ToBuffer().
+ */
+
+ function tobuf(x) {
+ return Duktape.dec('base64', Duktape.enc('base64', x));
+ }
+ function printbuf(x) {
+ var tmp = [];
+ var i;
+ tmp.push(typeof x + ' ' + x.length + ': ');
+ for (i = 0; i < x.length; i++) {
+ if (x[i] >= 0x20 && x[i] <= 0x7e) {
+ tmp.push(String.fromCharCode(x[i]));
+ } else {
+ tmp.push('<' + Number(x[i]).toString(16) + '>');
+ }
+ }
+ print(sanitizeLfunc(tmp.join('')));
+ }
+
+ printbuf(tobuf(Math.cos));
+ printbuf(tobuf(Math.sin));
+}
+
+try {
+ print('toBuffer() test');
+ toBufferTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+toPointer() test
+pointer null
+object null
+===*/
+
+function toPointerTest() {
+ var lfunc = Math.cos;
+ var t;
+
+ t = Duktape.Pointer(lfunc);
+ print(typeof t, t);
+
+ t = new Duktape.Pointer(lfunc);
+ print(typeof t, t);
+}
+
+try {
+ print('toPointer() test');
+ toPointerTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+number coercion test
+NaN
+NaN
+0
+0
+0
+0
+0
+0
+===*/
+
+function numberCoercionTest() {
+ var lfunc = Math.cos;
+ var nfunc = function foo(){};
+
+ // ToNumber(): NaN
+ print(Number(lfunc));
+ print(Number(nfunc));
+
+ // ToInteger(): positive zero
+ // XXX: no test, where to get ToInteger() result most easily?
+
+ // ToInt32(), ToUint32(), ToUint16(): positive zero
+ print(lfunc >>> 0); // ToUint32
+ print(nfunc >>> 0);
+ print(lfunc >> 0); // ToInt32
+ print(nfunc >> 0);
+ print(String.fromCharCode(lfunc).charCodeAt(0)); // ToUint16
+ print(String.fromCharCode(nfunc).charCodeAt(0));
+
+}
+
+try {
+ print('number coercion test');
+ numberCoercionTest();
+} catch (e) {
+ print(e.stack || e);
}
/*===
@@ -247,6 +776,51 @@ function callApplyTest() {
print(fun.apply('myThis', [ 123, 321, 987, 345 ]));
}
+try {
+ print('call and apply test');
+ callApplyTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+this coercion test
+function true
+function true
+===*/
+
+function thisCoercionTest() {
+ var lfunc = Math.cos;
+
+ // Strict functions get 'this' as is as normal, including lightfuncs.
+
+ function myStrict() {
+ 'use strict';
+ print(typeof this, isLightFunc(this));
+ }
+ myStrict.call(lfunc);
+
+ // The 'this' binding of a non-strict function is not further coerced if
+ // it is an object. There are two logical behaviors here for lightfuncs:
+ // either we (1) treat them like objects and don't coerce them; or (2)
+ // coerce them forcibly to a fully fledged object.
+ //
+ // Current behavior is (1) so the 'this' binding should also be lightfunc
+ // in myNonStrict.
+
+ function myNonStrict() {
+ print(typeof this, isLightFunc(this));
+ }
+ myNonStrict.call(lfunc);
+}
+
+try {
+ print('this coercion test');
+ thisCoercionTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
inherit from Function.prototype test
testValue
@@ -259,6 +833,13 @@ function inheritFromFunctionPrototypeTest() {
print(fun.inheritTestProperty);
}
+try {
+ print('inherit from Function.prototype test');
+ inheritFromFunctionPrototypeTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
Object.prototype.toString() test
[object Function]
@@ -275,6 +856,13 @@ function objectPrototypeToStringTest() {
print(Object.prototype.toString.call(fun));
}
+try {
+ print('Object.prototype.toString() test');
+ objectPrototypeToStringTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
JSON/JX/JC test
json
@@ -296,50 +884,162 @@ jc
{"_func":true}
{"_func":true}
json
-{"array":[1,null,2,null,3]}
+{"array":[100,null,200,null,300]}
{
"array": [
- 1,
+ 100,
null,
- 2,
+ 200,
null,
- 3
+ 300
]
}
jx
-{lf:{_func:true},nf:{_func:true},array:[1,{_func:true},2,{_func:true},3]}
+{lf:{_func:true},nf:{_func:true},array:[100,{_func:true},200,{_func:true},300]}
{
lf: {_func:true},
nf: {_func:true},
array: [
- 1,
+ 100,
{_func:true},
- 2,
+ 200,
{_func:true},
- 3
+ 300
]
}
jc
-{"lf":{"_func":true},"nf":{"_func":true},"array":[1,{"_func":true},2,{"_func":true},3]}
+{"lf":{"_func":true},"nf":{"_func":true},"array":[100,{"_func":true},200,{"_func":true},300]}
{
"lf": {"_func":true},
"nf": {"_func":true},
"array": [
- 1,
+ 100,
{"_func":true},
- 2,
+ 200,
{"_func":true},
- 3
+ 300
]
}
-===*/
-
-function jsonJxJcTest() {
- /* There's a separate test for this too, but lightweight functions look
- * like functions in JSON/JX/JC output.
- */
+json
+"toJsonRetval"
+"toJsonRetval"
+jx
+"toJsonRetval"
+"toJsonRetval"
+jc
+"toJsonRetval"
+"toJsonRetval"
+json
+"toJsonRetval"
+"toJsonRetval"
+jx
+"toJsonRetval"
+"toJsonRetval"
+jc
+"toJsonRetval"
+"toJsonRetval"
+json
+{"lf":"toJsonRetval","nf":"toJsonRetval","array":[100,"toJsonRetval",200,"toJsonRetval",300]}
+{
+ "lf": "toJsonRetval",
+ "nf": "toJsonRetval",
+ "array": [
+ 100,
+ "toJsonRetval",
+ 200,
+ "toJsonRetval",
+ 300
+ ]
+}
+jx
+{lf:"toJsonRetval",nf:"toJsonRetval",array:[100,"toJsonRetval",200,"toJsonRetval",300]}
+{
+ lf: "toJsonRetval",
+ nf: "toJsonRetval",
+ array: [
+ 100,
+ "toJsonRetval",
+ 200,
+ "toJsonRetval",
+ 300
+ ]
+}
+jc
+{"lf":"toJsonRetval","nf":"toJsonRetval","array":[100,"toJsonRetval",200,"toJsonRetval",300]}
+{
+ "lf": "toJsonRetval",
+ "nf": "toJsonRetval",
+ "array": [
+ 100,
+ "toJsonRetval",
+ 200,
+ "toJsonRetval",
+ 300
+ ]
+}
+json
+0
+0
+jx
+0
+0
+jc
+0
+0
+json
+0
+0
+jx
+0
+0
+jc
+0
+0
+json
+{"lf":null,"nf":null,"array":[100,1,200,3,300]}
+{
+ "lf": null,
+ "nf": null,
+ "array": [
+ 100,
+ 1,
+ 200,
+ 3,
+ 300
+ ]
+}
+jx
+{lf:NaN,nf:NaN,array:[100,1,200,3,300]}
+{
+ lf: NaN,
+ nf: NaN,
+ array: [
+ 100,
+ 1,
+ 200,
+ 3,
+ 300
+ ]
+}
+jc
+{"lf":{"_nan":true},"nf":{"_nan":true},"array":[100,1,200,3,300]}
+{
+ "lf": {"_nan":true},
+ "nf": {"_nan":true},
+ "array": [
+ 100,
+ 1,
+ 200,
+ 3,
+ 300
+ ]
+}
+===*/
- // FIXME: should JX/JC distinguish?
+function jsonJxJcTest() {
+ /* There's a separate test for this too, but lightweight functions look
+ * like functions in JSON/JX/JC output.
+ */
var lightFunc = getLightFunc();
var normalFunc = getNormalFunc();
@@ -349,7 +1049,7 @@ function jsonJxJcTest() {
var testValue3 = {
lf: lightFunc,
nf: normalFunc,
- array: [ 1, lightFunc, 2, normalFunc, 3 ]
+ array: [ 100, lightFunc, 200, normalFunc, 300 ]
};
[ testValue1, testValue2, testValue3 ].forEach(function (v) {
@@ -363,19 +1063,77 @@ function jsonJxJcTest() {
print(Duktape.enc('jc', v));
print(Duktape.enc('jc', v, null, 4));
});
+
+ /* toJSON() should work, and is inherited from Function.prototype.
+ * XXX: right now the 'this' binding will be a lightfunc coerced
+ * to a normal function, so 'toJsonRetval' is returned.
+ */
+
+ Function.prototype.toJSON = function (key) {
+ //print('toJSON, this-is-lightfunc:', isLightFunc(this), 'key:', key);
+ if (isLightFunc(this)) {
+ return 'toJsonLightfuncRetval';
+ }
+ return 'toJsonRetval';
+ };
+
+ [ testValue1, testValue2, testValue3 ].forEach(function (v) {
+ print('json');
+ print(JSON.stringify(v));
+ print(JSON.stringify(v, null, 4));
+ print('jx');
+ print(Duktape.enc('jx', v));
+ print(Duktape.enc('jx', v, null, 4));
+ print('jc');
+ print(Duktape.enc('jc', v));
+ print(Duktape.enc('jc', v, null, 4));
+ });
+
+ delete Function.prototype.toJSON;
+
+ /* The toJSON function itself can also be a lightfunc. Below we use
+ * Math.min() as the toJSON() function: it will return:
+ * - NaN when the key is a non-empty string
+ * - 0 when the key is an empty string (the top level 'holder' key
+ * is an empty string)
+ * - key as is, if the key is a number (array case)
+ */
+
+ Function.prototype.toJSON = Math.min;
+
+ [ testValue1, testValue2, testValue3 ].forEach(function (v) {
+ print('json');
+ print(JSON.stringify(v));
+ print(JSON.stringify(v, null, 4));
+ print('jx');
+ print(Duktape.enc('jx', v));
+ print(Duktape.enc('jx', v, null, 4));
+ print('jc');
+ print(Duktape.enc('jc', v));
+ print(Duktape.enc('jc', v, null, 4));
+ });
+
+ delete Function.prototype.toJSON;
+}
+
+try {
+ print('JSON/JX/JC test');
+ jsonJxJcTest();
+} catch (e) {
+ print(e.stack || e);
}
/*===
bound function test
-F: function lightfunc() {(* lightfunc *)}
+F: function light_PTR_002f() {(* light *)}
F type tag: 9
-G: function lightfunc() {(* bound *)}
+G: function light_PTR_002f() {(* bound *)}
G type tag: 6
G.length: 1
-H: function light_0805b822_002f() {(* bound *)}
+H: function light_PTR_002f() {(* bound *)}
H type tag: 6
H.length: 0
-I: function light_0805b822_002f() {(* bound *)}
+I: function light_PTR_002f() {(* bound *)}
I type tag: 6
I.length: 0
G(123): 234
@@ -388,21 +1146,24 @@ function boundFunctionTest() {
*/
var F = Math.max; // length 2, varargs
- print('F:', sanitizeFuncToString(String(F)));
+ print('F:', sanitizeLfunc(String(F)));
print('F type tag:', Duktape.info(F)[0]);
+ // 'G' will be a full Function object but will still have a lightfunc
+ // 'name' as a result of ToObject coercion, which is intentional but
+ // somewhat confusing. This would be fixable in the ToObject() coercion.
var G = F.bind('myThis', 234); // bound once
- print('G:', sanitizeFuncToString(String(G)));
+ print('G:', sanitizeLfunc(String(G)));
print('G type tag:', Duktape.info(G)[0]);
print('G.length:', G.length); // length 1, one argument was bound
var H = G.bind('foo', 345); // bound twice
- print('H:', sanitizeFuncToString(String(H)));
+ print('H:', sanitizeLfunc(String(H)));
print('H type tag:', Duktape.info(H)[0]);
print('H.length:', H.length); // length 0, two arguments are bound
var I = H.bind('foo', 345); // bound three times
- print('I:', sanitizeFuncToString(String(I)));
+ print('I:', sanitizeLfunc(String(I)));
print('I type tag:', Duktape.info(I)[0]);
print('I.length:', I.length); // length 0, doesn't become negative
@@ -411,6 +1172,13 @@ function boundFunctionTest() {
print('G(123,987):', G(123, 987));
}
+try {
+ print('bound function test');
+ boundFunctionTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
property get test
length directly: 2
@@ -420,14 +1188,14 @@ toString coerced object (return "length")
length through object coercion: 2
read from length -> 2
read from prototype -> undefined
-read from name -> lightfunc
+read from name -> light_PTR_002f
toString coerced object (return "length")
toString coerced object (return "length")
read from length -> 2
read from testWritable -> 123
read from testNonWritable -> 234
-read from call -> function lightfunc() {(* lightfunc *)}
-read from apply -> function lightfunc() {(* lightfunc *)}
+read from call -> function light_PTR_001f() {(* light *)}
+read from apply -> function light_PTR_0022() {(* light *)}
read from nonexistent -> undefined
===*/
@@ -459,13 +1227,18 @@ function propertyGetTest() {
testKeys.forEach(function (k) {
try {
- print('read from', k, '->', sanitizeFuncToString(lightFunc[k]));
+ print('read from', k, '->', sanitizeLfunc(lightFunc[k]));
} catch (e) {
print('read from', k, '->', e.name);
}
});
+}
- // FIXME: property getter test
+try {
+ print('property get test');
+ propertyGetTest();
+} catch (e) {
+ print(e.stack || e);
}
/*===
@@ -541,6 +1314,13 @@ function propertyPutTest() {
});
}
+try {
+ print('property put test');
+ propertyPutTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
property has test
existence: length -> true
@@ -578,6 +1358,13 @@ function propertyHasTest() {
});
}
+try {
+ print('property has test');
+ propertyHasTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
property delete test
delete: length -> false
@@ -662,38 +1449,45 @@ function propertyDeleteTest() {
}
}
+try {
+ print('property delete test');
+ propertyDeleteTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
property accessor this binding test
getter, strict
strict getter "this" binding test
typeof this: function
-this == lightFunc: false
+this == lightFunc: true
this === lightFunc: true
-this.name: lightfunc
+this.name: light_PTR_002f
type tag: 9
getter retval
setter, strict
strict setter "this" binding test
typeof this: function
-this == lightFunc: false
+this == lightFunc: true
this === lightFunc: true
-this.name: lightfunc
+this.name: light_PTR_002f
type tag: 9
getter, non-strict
non-strict getter "this" binding test
typeof this: function
-this == lightFunc: false
-this === lightFunc: false
-this.name: lightfunc
-type tag: 6
+this == lightFunc: true
+this === lightFunc: true
+this.name: light_PTR_002f
+type tag: 9
getter retval
setter, non-strict
non-strict setter "this" binding test
typeof this: function
-this == lightFunc: false
-this === lightFunc: false
-this.name: lightfunc
-type tag: 6
+this == lightFunc: true
+this === lightFunc: true
+this.name: light_PTR_002f
+type tag: 9
===*/
function propertyAccessorThisBindingTest() {
@@ -704,19 +1498,8 @@ function propertyAccessorThisBindingTest() {
* property, the getter/setter 'this' binding is set to the lightfunc
* (and not, e.g., Function.prototype).
*
- * However, if the setter/getter is non-strict, the this binding is
- * then object coerced. This is similar to what happens to a string:
- *
- * > Object.defineProperty(String.prototype, 'foo', { get: function () { print(typeof this); } });
- * {}
- * > Object.defineProperty(String.prototype, 'bar', { get: function () { 'use strict'; print(typeof this); } });
- * {}
- * > "foo".foo
- * object
- * undefined
- * > "foo".bar
- * string
- * undefined
+ * Because a lightfunc behaves like a full Function object, it is
+ * not coerced with ToObject() even when the accessor is non-strict.
*/
Object.defineProperty(Function.prototype, 'testAccessorStrict', {
@@ -726,7 +1509,7 @@ function propertyAccessorThisBindingTest() {
print('typeof this:', typeof this);
print('this == lightFunc:', this == lightFunc);
print('this === lightFunc:', this === lightFunc);
- print('this.name:', sanitizeFuncToString(this.name));
+ print('this.name:', sanitizeLfunc(this.name));
print('type tag:', Duktape.info(this)[0]);
return 'getter retval';
},
@@ -736,7 +1519,7 @@ function propertyAccessorThisBindingTest() {
print('typeof this:', typeof this);
print('this == lightFunc:', this == lightFunc);
print('this === lightFunc:', this === lightFunc);
- print('this.name:', sanitizeFuncToString(this.name));
+ print('this.name:', sanitizeLfunc(this.name));
print('type tag:', Duktape.info(this)[0]);
},
enumerable: false,
@@ -749,7 +1532,7 @@ function propertyAccessorThisBindingTest() {
print('typeof this:', typeof this);
print('this == lightFunc:', this == lightFunc);
print('this === lightFunc:', this === lightFunc);
- print('this.name:', sanitizeFuncToString(this.name));
+ print('this.name:', sanitizeLfunc(this.name));
print('type tag:', Duktape.info(this)[0]);
return 'getter retval';
},
@@ -758,7 +1541,7 @@ function propertyAccessorThisBindingTest() {
print('typeof this:', typeof this);
print('this == lightFunc:', this == lightFunc);
print('this === lightFunc:', this === lightFunc);
- print('this.name:', sanitizeFuncToString(this.name));
+ print('this.name:', sanitizeLfunc(this.name));
print('type tag:', Duktape.info(this)[0]);
},
enumerable: false,
@@ -776,8 +1559,23 @@ function propertyAccessorThisBindingTest() {
lightFunc.testAccessorNonStrict = 123;
}
+try {
+ print('property accessor this binding test');
+ propertyAccessorThisBindingTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
/*===
property misc test
+isSealed: true
+isFrozen: true
+isExtensible: false
+for-in: "inheritTestProperty"
+Object.getOwnPropertyNames: "length"
+Object.getOwnPropertyNames: "name"
+lightFunc.name matches regexp: true
+censored lightFunc.name: light_XXX_XXX
===*/
function propertyMiscTest() {
@@ -835,17 +1633,22 @@ function propertyMiscTest() {
t = lightFunc.name.substring(0, 5) + '_' +
lightFunc.name.substring(6).replace(/[0-9a-fA-F]+/g, 'XXX');
print('censored lightFunc.name:', t);
+}
- // .prototype maps to Function.prototype
-/*
- print('call' in lightFunc); // inherited
- print('prototype' in lightFunc); // own property
- print(lightFunc.prototype === Function.prototype);
-*/
+try {
+ print('property misc test');
+ propertyMiscTest();
+} catch (e) {
+ print(e.stack || e);
}
/*===
traceback test
+URIError: invalid input
+ duk_bi_global.c:NNN
+ light_PTR_0011 light strict preventsyield
+ tracebackTest TESTCASE:NNN
+ global TESTCASE:NNN preventsyield
===*/
function tracebackTest() {
@@ -857,37 +1660,90 @@ function tracebackTest() {
err = e;
}
- // FIXME: validate
- print(err.stack);
+ // heavy sanitization
+ print(sanitizeTraceback(err.stack));
+}
+
+try {
+ print('traceback test');
+ tracebackTest();
+} catch (e) {
+ print(e.stack || e);
}
/*===
Duktape.act() test
+Error: for traceback
+ callback TESTCASE:NNN preventsyield
+ light_PTR_0212 light strict preventsyield
+ duktapeActTest TESTCASE:NNN
+ global TESTCASE:NNN preventsyield
+-1 ["lineNumber","pc","function"] light_PTR_0011
+-2 ["lineNumber","pc","function"] callback
+-3 ["lineNumber","pc","function"] light_PTR_0212
+-4 ["lineNumber","pc","function"] duktapeActTest
+-5 ["lineNumber","pc","function"] global
===*/
function duktapeActTest() {
- // FIXME: lightfunc in Duktape.act(); how to test, need intermediate function
- // to call Ecmascript function, perhaps ./duk --test
-
- // FIXME: use forEach for now
+ // This test assumes that Array.prototype.forEach() has been created
+ // as a lightfunc, so that it appears in the middle of the callstack.
[ 'foo' ].forEach(function callback(x) {
var e = new Error('for traceback');
var i;
var a;
- print(e.stack);
+ print(sanitizeTraceback(e.stack));
for (i = -1; ; i--) {
a = Duktape.act(i);
if (!a) { break; }
- print(i, Duktape.enc('jx', Object.getOwnPropertyNames(a)), sanitizeFuncToString(a.function.name));
+ print(i, Duktape.enc('jx', Object.getOwnPropertyNames(a)), sanitizeLfunc(a.function.name));
}
});
}
-function exemptBuiltinsTest() {
+try {
+ print('Duktape.act() test');
+ duktapeActTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+exempt built-ins test
+Math.max (is lightfunc): function true
+eval: function false
+yield: function false
+resume: function false
+require: function false
+Object: function false
+Function: function false
+Array: function false
+String: function false
+Boolean: function false
+Number: function false
+Date: function false
+RegExp: function false
+Error: function false
+EvalError: function false
+RangeError: function false
+ReferenceError: function false
+SyntaxError: function false
+TypeError: function false
+URIError: function false
+Proxy: function false
+Duktape.Buffer: function false
+Duktape.Pointer: function false
+Duktape.Thread: function false
+Duktape.Logger: function false
+Duktape: object false
+Math: object false
+JSON: object false
+===*/
+function exemptBuiltinsTest() {
function f(v) {
return (typeof v) + ' ' + isLightFunc(v);
}
@@ -942,138 +1798,1171 @@ function exemptBuiltinsTest() {
print('JSON:', f(JSON));
}
-/*
-- Duktape.info() documentation for tags
-- Rename lightfunc/LIGHTFUNC to FUNCTION? Cf. POINTER and BUFFER are the
- plain primitive type names, while Pointer and Buffer are Objects
-- Portable funcptr printing -- duk_push_funcptr_string(ctx, ptr, sizeof)?
-- Some way of reading a function's magic from Ecmascript code?
-- Magic rename for release (keep magic API funcs as macros)?
-- Bound function object pointing to lightweight func
+try {
+ print('exempt built-ins test');
+ exemptBuiltinsTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+getOwnPropertyNames() test
+length,name
+length
+name
+===*/
-- interaction with 'caller' property
-- list all function properties and add testcase printing for them
+function getOwnPropertyNamesTest() {
+ var lfunc = Math.cos;
+ var names;
+
+ named = Object.getOwnPropertyNames(lfunc);
+ print(named);
+ if (named) {
+ named.forEach(function (x) { print(x); });
+ }
+}
-- check virtual name of lightfuncs
-- if built-ins get special handling for lightfunc name, test for that here
+try {
+ print('getOwnPropertyNames() test');
+ getOwnPropertyNamesTest();
+} catch (e) {
+ print(e.stack || e);
+}
-- a in b
-- a instanceof b
+/*===
+getOwnPropertyDescriptor() test
+key: name
+value: string light_PTR_0511
+writable: boolean false
+enumerable: boolean false
+configurable: boolean false
+key: length
+value: number 1
+writable: boolean false
+enumerable: boolean false
+configurable: boolean false
+key: nonExistent
+no descriptor
+===*/
-- equality, plain and strict
-*/
+function getOwnPropertyDescriptorTest() {
+ var lfunc = Math.cos;
+
+ function test(key) {
+ print('key:', key);
+ var pd = Object.getOwnPropertyDescriptor(lfunc, key);
+ if (!pd) { print('no descriptor'); return; }
+ print('value:', typeof pd.value, typeof pd.value === 'string' ? sanitizeLfunc(pd.value) : pd.value);
+ print('writable:', typeof pd.writable, pd.writable);
+ print('enumerable:', typeof pd.enumerable, pd.enumerable);
+ print('configurable:', typeof pd.configurable, pd.configurable);
+ }
+
+ test('name');
+ test('length');
+ test('nonExistent');
+}
try {
- print('light func support test');
- lightFuncSupportTest();
+ print('getOwnPropertyDescriptor() test');
+ getOwnPropertyDescriptorTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+hasOwnProperty() test
+true
+true
+false
+false
+===*/
+
+function hasOwnPropertyTest() {
+ var lfunc = Math.cos;
+
+ print(Object.prototype.hasOwnProperty.call(lfunc, 'name'));
+ print(Object.prototype.hasOwnProperty.call(lfunc, 'length'));
+ print(Object.prototype.hasOwnProperty.call(lfunc, 'nonExistent'));
+ print(Object.prototype.hasOwnProperty.call(lfunc, 'call')); // inherited
+}
+
try {
- print('typeof test');
- typeofTest();
+ print('hasOwnProperty() test');
+ hasOwnPropertyTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+propertyIsEnumerable() test
+false
+false
+false
+false
+===*/
+
+function propertyIsEnumerableTest() {
+ var lfunc = Math.cos;
+
+ print(Object.prototype.propertyIsEnumerable(lfunc, 'name'));
+ print(Object.prototype.propertyIsEnumerable(lfunc, 'length'));
+ print(Object.prototype.propertyIsEnumerable(lfunc, 'nonExistent'));
+ print(Object.prototype.propertyIsEnumerable(lfunc, 'call')); // inherited
}
+
try {
- print('comparison test');
- comparisonTest();
+ print('propertyIsEnumerable() test');
+ propertyIsEnumerableTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+defineProperty() test
+nonexistent: success
+name: TypeError
+length: success
+===*/
+
+function definePropertyTest() {
+ /*
+ * Object.defineProperty() specification algorithm throws a TypeError if
+ * the argument is not an object. Because we want lightfuncs to behave
+ * like full objects, Object.defineProperty() should work as if it was
+ * allowed and the lightfunc was an actual object.
+ *
+ * The current implementation just coerces a lightfunc to an object with
+ * ToObject(). The resulting normal Function object has a copy of the
+ * lightfunc's virtual properties. The Function object is also extensible
+ * (which is a bit odd because the lightfunc is not considered extensible).
+ * Because the coerced function is extensible, adding new properties will
+ * appear to succeed. The temporary object is not exposed to calling code
+ * and is later garbage collected.
+ *
+ * Another possible behavior might be an unconditional TypeError. This
+ * would not match the ordinary defineProperty() behavior where it is
+ * possible to write even to a write-protected value if the new value
+ * SameValue() compares equal to the old value.
+ *
+ * The preferred behavior is not clear.
+ *
+ * Drawing some analogies from plain strings: plain string values can be
+ * assigned properties with no error, even in strict mode:
+ *
+ * > function () { 'use strict'; return ('foo').bar=1; }()
+ * 1
+ *
+ * There is no analogy for defineProperty() because strings can't be used
+ * as an Object.defineProperty() argument:
+ *
+ * > Object.defineProperty('foo', 'bar', { value: 1, writable: true, ... })
+ * TypeError: Object.defineProperty called on non-object
+ *
+ * The extensibility of a plain string object also cannot be queried:
+ *
+ * > Object.isExtensible('foo')
+ * TypeError: Object.isExtensible called on non-object
+ *
+ * If a plain string is coerced to a full String object, it becomes
+ * extensible:
+ *
+ * > Object.isExtensible(new String('foo'))
+ * true
+ *
+ * So, the current behavior for a defineProperty() call is basically:
+ *
+ * Object.defineProperty(Object(lightFunc), ...);
+ *
+ * This will succeed in defining new properties (which are not reflected
+ * in the lightFunc value) which are effectively lost.
+ */
+
+ var lf = Math.max;
+
+ // Non-existent property: succeeds because the temporary ToObject()
+ // coerced object is extensible. It might be more intuitive for this
+ // to fail with "not extensible".
+ try {
+ Object.defineProperty(lf, 'nonexistent', {
+ value: 123, writable: true, enumerable: true, configurable: true
+ });
+ print('nonexistent:', 'success');
+ } catch (e) {
+ print('nonexistent:', e.name);
+ }
+
+ // Existing non-configurable property with a different value: rejected
+ // as part of normal defineProperty() handling.
+ try {
+ Object.defineProperty(lf, 'name', {
+ value: 123, writable: true, enumerable: true, configurable: true
+ });
+ print('name:', 'success');
+ } catch (e) {
+ print('name:', e.name);
+ }
+
+ // Existing non-configurable property with same value as before: accepted
+ // as part of normal defineProperty() handling.
+ try {
+ Object.defineProperty(lf, 'length', {
+ value: 2, writable: false, enumerable: false, configurable: false
+ });
+ print('length:', 'success');
+ } catch (e) {
+ print('length:', e.name);
+ }
}
+
try {
- print('toString() test');
- toStringTest();
+ print('defineProperty() test');
+ definePropertyTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+defineProperties() test
+nonexistent: success
+name: TypeError
+length: success
+===*/
+
+function definePropertiesTest() {
+ /*
+ * Same as definePropertyTest() but for Object.defineProperties: it has
+ * a slightly different internal code path so should be tested separately.
+ */
+
+ var lf = Math.max;
+
+ // Non-existent property.
+ try {
+ Object.defineProperties(lf, { nonexistent: {
+ value: 123, writable: true, enumerable: true, configurable: true
+ } });
+ print('nonexistent:', 'success');
+ } catch (e) {
+ print('nonexistent:', e.name);
+ }
+
+ // Existing non-configurable property, different value
+ try {
+ Object.defineProperties(lf, { name: {
+ value: 123, writable: true, enumerable: true, configurable: true
+ } });
+ print('name:', 'success');
+ } catch (e) {
+ print('name:', e.name);
+ }
+
+ // Existing non-configurable property, same value
+ try {
+ Object.defineProperties(lf, { length: {
+ value: 2, writable: false, enumerable: false, configurable: false
+ } });
+ print('length:', 'success');
+ } catch (e) {
+ print('length:', e.name);
+ }
}
+
try {
- print('toObject() test');
- toObjectTest();
+ print('defineProperties() test');
+ definePropertiesTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+getPrototypeOf() test
+true
+true
+===*/
+
+function getPrototypeOfTest() {
+ var lfunc = Math.max;
+
+ print(Object.getPrototypeOf(lfunc) === Function.prototype);
+ print(lfunc.__proto__ === Function.prototype);
}
+
try {
- print('call and apply test');
- callApplyTest();
+ print('getPrototypeOf() test');
+ getPrototypeOfTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+setPrototypeOfTest()
+TypeError
+TypeError
+TypeError
+TypeError
+success
+success
+success
+success
+===*/
+
+function setPrototypeOfTest() {
+ var lfunc = Math.max;
+ var nonext;
+
+ function err(cb) {
+ try {
+ cb();
+ print('never here');
+ } catch (e) {
+ print(e.name);
+ }
+ }
+
+ function succ(cb) {
+ try {
+ cb();
+ print('success');
+ } catch (e) {
+ print(e);
+ }
+ }
+
+ // Trying to change prototype is a TypeError. The same behavior applies
+ // to ordinary non-extensible objects.
+
+ err(function () { var nonext = {}; Object.preventExtensions(nonext); Object.setPrototypeOf(nonext, {}); });
+ err(function () { Object.setPrototypeOf(lfunc, {}); });
+
+ err(function () { var nonext = {}; Object.preventExtensions(nonext); nonext.__proto__ = {}; });
+ err(function () { lfunc.__proto__ = {}; });
+
+ // Setting existing prototype value is a no-op.
+
+ succ(function () { var nonext = {}; Object.preventExtensions(nonext); Object.setPrototypeOf(nonext, Object.prototype); });
+ succ(function () { Object.setPrototypeOf(lfunc, Function.prototype); });
+
+ succ(function () { var nonext = {}; Object.preventExtensions(nonext); nonext.__proto__ = Object.prototype; });
+ succ(function () { lfunc.__proto__ = Function.prototype; });
+}
+
try {
- print('inherit from Function.prototype test');
- inheritFromFunctionPrototypeTest();
+ print('setPrototypeOf() test');
+ setPrototypeOfTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+Array built-in test
+Array: object [{_func:true}]
+new Array: object [{_func:true}]
+isArray: boolean false
+toString: string "[object Function]"
+valueOf: function {_func:true}
+concat: object [{_func:true}]
+pop: TypeError
+push: TypeError
+sort: function {_func:true}
+splice: TypeError
+reverse: function {_func:true}
+shift: TypeError
+unshift: TypeError
+every: TypeError
+some: TypeError
+forEach: TypeError
+map: TypeError
+filter: TypeError
+reduce: TypeError
+reduceRight: TypeError
+===*/
+
+function arrayBuiltinTest() {
+ var lfunc = Math.cos;
+
+ function f(meth) {
+ testTypedJx(function () {
+ return Array.prototype[meth].call(lfunc);
+ }, meth);
+ }
+
+ testTypedJx(function () { return Array(lfunc) }, 'Array');
+ testTypedJx(function () { return new Array(lfunc) }, 'new Array');
+ testTypedJx(function () { return Array.isArray(lfunc) }, 'isArray');
+
+ // these tests are not particularly useful, but might reveal some
+ // assertion errors
+
+ f('toString');
+ f('valueOf');
+ f('concat');
+ f('pop'); // TypeError, length not writable
+ f('push'); // TypeError, length not writable
+ f('sort');
+ f('splice'); // TypeError, length not writable
+ f('reverse');
+ f('shift'); // TypeError, length not writable
+ f('unshift'); // TypeError, length not writable
+ f('every'); // TypeError, callback not a function
+ f('some'); // -''-
+ f('forEach'); // -''-
+ f('map'); // -''-
+ f('filter'); // -''-
+ f('reduce'); // -''-
+ f('reduceRight'); // -''-
+}
+
try {
- print('Object.prototype.toString() test');
- objectPrototypeToStringTest();
+ print('Array built-in test');
+ arrayBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+Boolean built-in test
+Boolean: boolean true
+new Boolean: object true
+toString: TypeError
+valueOf: TypeError
+===*/
+
+function booleanBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Boolean(lfunc) }, 'Boolean');
+ testTypedJx(function () { return new Boolean(lfunc) }, 'new Boolean');
+ testTypedJx(function () { return Boolean.prototype.toString.call(lfunc, lfunc) }, 'toString');
+ testTypedJx(function () { return Boolean.prototype.valueOf.call(lfunc, lfunc) }, 'valueOf');
+}
+
try {
- print('JSON/JX/JC test');
- jsonJxJcTest();
+ print('Boolean built-in test');
+ booleanBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+Duktape.Buffer built-in test
+Duktape.Buffer: TypeError
+new Duktape.buffer: TypeError
+toString: TypeError
+valueOf: TypeError
+===*/
+
+function duktapeBufferBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Duktape.Buffer(lfunc) }, 'Duktape.Buffer');
+ testTypedJx(function () { return new Duktape.Buffer(lfunc) }, 'new Duktape.buffer');
+ testTypedJx(function () { return Duktape.Buffer.prototype.toString.call(lfunc, lfunc) }, 'toString');
+ testTypedJx(function () { return Duktape.Buffer.prototype.valueOf.call(lfunc, lfunc) }, 'valueOf');
}
+
try {
- print('bound function test');
- boundFunctionTest();
+ print('Duktape.Buffer built-in test');
+ duktapeBufferBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+Date built-in test
+Date: string "string"
+new Date: string "object"
+parse: number NaN
+UTC: number NaN
+now: string "number"
+toString: TypeError
+valueOf: TypeError
+toDateString: TypeError
+toTimeString: TypeError
+toLocaleString: TypeError
+toLocaleDateString: TypeError
+toLocaleTimeString: TypeError
+getTime: TypeError
+getFullYear: TypeError
+getUTCFullYear: TypeError
+getMonth: TypeError
+getUTCFullMonth: TypeError
+getDate: TypeError
+getUTCDate: TypeError
+getDay: TypeError
+getUTCDay: TypeError
+getHours: TypeError
+getUTCHours: TypeError
+getMinutes: TypeError
+getUTCMinutes: TypeError
+getSeconds: TypeError
+getUTCSeconds: TypeError
+getMilliseconds: TypeError
+getUTCMilliseconds: TypeError
+getTimezoneOffset: TypeError
+setTime: TypeError
+setMilliseconds: TypeError
+setUTCMilliseconds: TypeError
+setSeconds: TypeError
+setUTCSeconds: TypeError
+setMinutes: TypeError
+setUTCMinutes: TypeError
+setHours: TypeError
+setUTCHours: TypeError
+setDate: TypeError
+setUTCDate: TypeError
+setMonth: TypeError
+setUTCMonth: TypeError
+setFullYear: TypeError
+setUTCFullYear: TypeError
+toUTCString: TypeError
+toISOString: TypeError
+toJSON: TypeError
+setYear: TypeError
+getYear: TypeError
+===*/
+
+function dateBuiltinTest() {
+ var lfunc = Math.cos;
+
+ function f(meth) {
+ testTypedJx(function () {
+ return Date.prototype[meth].call(lfunc, lfunc);
+ }, meth);
+ }
+
+ testTypedJx(function () { return typeof Date(lfunc) }, 'Date');
+ testTypedJx(function () { return typeof new Date(lfunc) }, 'new Date');
+ testTypedJx(function () { return Date.parse(lfunc) }, 'parse');
+ testTypedJx(function () { return Date.UTC(lfunc) }, 'UTC');
+ testTypedJx(function () { return typeof Date.now(lfunc) }, 'now');
+
+ f('toString');
+ f('valueOf');
+ f('toDateString');
+ f('toTimeString');
+ f('toLocaleString');
+ f('toLocaleDateString');
+ f('toLocaleTimeString');
+ f('getTime');
+ f('getFullYear');
+ f('getUTCFullYear');
+ f('getMonth');
+ f('getUTCFullMonth');
+ f('getDate');
+ f('getUTCDate');
+ f('getDay');
+ f('getUTCDay');
+ f('getHours');
+ f('getUTCHours');
+ f('getMinutes');
+ f('getUTCMinutes');
+ f('getSeconds');
+ f('getUTCSeconds');
+ f('getMilliseconds');
+ f('getUTCMilliseconds');
+ f('getTimezoneOffset');
+ f('setTime');
+ f('setMilliseconds');
+ f('setUTCMilliseconds');
+ f('setSeconds');
+ f('setUTCSeconds');
+ f('setMinutes');
+ f('setUTCMinutes');
+ f('setHours');
+ f('setUTCHours');
+ f('setDate');
+ f('setUTCDate');
+ f('setMonth');
+ f('setUTCMonth');
+ f('setFullYear');
+ f('setUTCFullYear');
+ f('toUTCString');
+ f('toISOString');
+ f('toJSON');
+ f('setYear');
+ f('getYear');
+}
+
try {
- print('property get test');
- propertyGetTest();
+ print('Date built-in test');
+ dateBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+Duktape built-in test
+info: object [9]
+act: undefined undefined
+gc: boolean true
+fin-get: TypeError
+fin-set: TypeError
+encdec-hex: string "function light_PTR_0511() {(* light *)}"
+dec-hex: TypeError
+compact: function {_func:true}
+===*/
+
+function duktapeBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Duktape.info(lfunc); }, 'info');
+
+ // doesn't really make sense
+ testTypedJx(function () { return Duktape.act(lfunc); }, 'act');
+
+ // doesn't really make sense (will act like Duktape.gc(0))
+ testTypedJx(function () { return Duktape.gc(lfunc); }, 'gc');
+
+ // attempt to get finalizer
+ testTypedJx(function () { return Duktape.fin(lfunc); }, 'fin-get');
+
+ // attempt to set finalizer
+ testTypedJx(function () { return Duktape.fin(lfunc, function () {}); }, 'fin-set');
+
+ testTypedJx(function () { return sanitizeLfunc(Duktape.dec('hex', Duktape.enc('hex', lfunc))); }, 'encdec-hex');
+ testTypedJx(function () { return Duktape.dec('hex', lfunc); }, 'dec-hex');
+
+ // attempt to compact is a no-op
+ testTypedJx(function () { return Duktape.compact(lfunc); }, 'compact');
}
+
try {
- print('property put test');
- propertyPutTest();
+ print('Duktape built-in test');
+ duktapeBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+Error built-in test
+Error: object {}
+new Error: object {}
+toString: string "light_PTR_0511"
+valueOf: function {_func:true}
+===*/
+
+function errorBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Error(lfunc); }, 'Error');
+ testTypedJx(function () { return new Error(lfunc); }, 'new Error');
+ testTypedJx(function () { return sanitizeLfunc(Error.prototype.toString.call(lfunc, lfunc)); }, 'toString');
+ testTypedJx(function () { return Error.prototype.valueOf.call(lfunc, lfunc); }, 'valueOf');
+}
+
try {
- print('property has test');
- propertyHasTest();
+ print('Error built-in test');
+ errorBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+Function built-in test
+Function: function {_func:true}
+new Function: function {_func:true}
+toString: string "function light_PTR_0511() {(* light *)}"
+valueOf: function {_func:true}
+===*/
+
+function functionBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Function(lfunc); }, 'Function');
+ testTypedJx(function () { return new Function(lfunc); }, 'new Function');
+ testTypedJx(function () { return sanitizeLfunc(Function.prototype.toString.call(lfunc, lfunc)) }, 'toString');
+ testTypedJx(function () { return Function.prototype.valueOf.call(lfunc, lfunc) }, 'valueOf');
+
+ // Already covered by other tests:
+ // - call
+ // - apply
+ // - bind
}
+
try {
- print('property delete test');
- propertyDeleteTest();
+ print('Function built-in test');
+ functionBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+global built-in test
+eval: function {_func:true}
+parseInt: number NaN
+parseFloat: number NaN
+isNaN: boolean true
+isFinite: boolean false
+decodeURI: string "function light_PTR_0511() {(* light *)}"
+decodeURIComponent: string "function light_PTR_0511() {(* light *)}"
+encodeURI: string "string"
+encodeURIComponent: string "string"
+escape: string "string"
+unescape: string "string"
+===*/
+
+function globalBuiltinTest() {
+ var lfunc = Math.cos;
+
+ // This interestingly evaluates to a function because ToString(lfunc) parses
+ // to a valid function declaration.
+ testTypedJx(function () { return eval(lfunc); }, 'eval');
+
+ testTypedJx(function () { return parseInt(lfunc); }, 'parseInt');
+ testTypedJx(function () { return parseFloat(lfunc); }, 'parseFloat');
+ testTypedJx(function () { return isNaN(lfunc); }, 'isNaN');
+ testTypedJx(function () { return isFinite(lfunc); }, 'isFinite');
+
+ // Must sanitize here
+ testTypedJx(function () { return sanitizeLfunc(decodeURI(lfunc)); }, 'decodeURI');
+ testTypedJx(function () { return sanitizeLfunc(decodeURIComponent(lfunc)); }, 'decodeURIComponent');
+
+ // The encoded output would need to be sanitized; just check it's a string
+ testTypedJx(function () { return typeof encodeURI(lfunc); }, 'encodeURI');
+ testTypedJx(function () { return typeof encodeURIComponent(lfunc); }, 'encodeURIComponent');
+ testTypedJx(function () { return typeof escape(lfunc); }, 'escape');
+ testTypedJx(function () { return typeof unescape(lfunc); }, 'unescape');
}
+
try {
- print('property accessor this binding test');
- propertyAccessorThisBindingTest();
+ print('global built-in test');
+ globalBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+JSON built-in test
+parse: SyntaxError
+stringify: undefined undefined
+===*/
+
+function jsonBuiltinTest() {
+ var lfunc = Math.cos;
+
+ // covered elsewhere too
+ testTypedJx(function () { return JSON.parse(lfunc); }, 'parse');
+ testTypedJx(function () { return JSON.stringify(lfunc); }, 'stringify');
}
+
try {
- print('property misc test');
- propertyMiscTest();
+ print('JSON built-in test');
+ jsonBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+Duktape.Logger built-in test
+Duktape.Logger: TypeError
+new Duktape.Logger: object {}
+fmt: TypeError
+raw: TypeError
+TIMESTAMP INF test: My light func is: function light_PTR_0511() {(* light *)}
+===*/
+
+function duktapeLoggerBuiltinTest() {
+ var lfunc = Math.cos;
+ var old_raw;
+ var logger;
+
+ testTypedJx(function () { return Duktape.Logger(lfunc); }, 'Duktape.Logger');
+ testTypedJx(function () { return new Duktape.Logger(lfunc); }, 'new Duktape.Logger');
+ testTypedJx(function () { return Duktape.logger.prototype.fmt(lfunc); }, 'fmt');
+ testTypedJx(function () { return Duktape.logger.prototype.raw(lfunc); }, 'raw');
+
+ // Test that lightfuncs log in a useful way. Because the toString()
+ // coercion contains a pointer we need to abduct the raw() function.
+
+ old_raw = Duktape.Logger.prototype.old_raw;
+ Duktape.Logger.prototype.raw = function (buf) {
+ var msg = sanitizeLfunc(String(buf));
+ msg = msg.replace(/^\S+/, 'TIMESTAMP');
+ print(msg);
+ };
+ logger = new Duktape.Logger('test');
+ logger.info('My light func is:', lfunc);
+ Duktape.Logger.prototype.raw = old_raw;
+}
+
try {
- print('traceback test');
- tracebackTest();
+ print('Duktape.Logger built-in test');
+ duktapeLoggerBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
}
+
+/*===
+Math built-in test
+abs: number NaN
+acos: number NaN
+asin: number NaN
+atan: number NaN
+atan2: number NaN
+ceil: number NaN
+cos: number NaN
+exp: number NaN
+floor: number NaN
+log: number NaN
+max: number NaN
+min: number NaN
+pow: number NaN
+random: string "number"
+round: number NaN
+sin: number NaN
+sqrt: number NaN
+tan: number NaN
+===*/
+
+function mathBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Math.abs(lfunc); }, 'abs');
+ testTypedJx(function () { return Math.acos(lfunc); }, 'acos');
+ testTypedJx(function () { return Math.asin(lfunc); }, 'asin');
+ testTypedJx(function () { return Math.atan(lfunc); }, 'atan');
+ testTypedJx(function () { return Math.atan2(lfunc); }, 'atan2');
+ testTypedJx(function () { return Math.ceil(lfunc); }, 'ceil');
+ testTypedJx(function () { return Math.cos(lfunc); }, 'cos');
+ testTypedJx(function () { return Math.exp(lfunc); }, 'exp');
+ testTypedJx(function () { return Math.floor(lfunc); }, 'floor');
+ testTypedJx(function () { return Math.log(lfunc); }, 'log');
+ testTypedJx(function () { return Math.max(lfunc); }, 'max');
+ testTypedJx(function () { return Math.min(lfunc); }, 'min');
+ testTypedJx(function () { return Math.pow(lfunc); }, 'pow');
+ testTypedJx(function () { return typeof Math.random(lfunc); }, 'random'); // avoid outputting result value
+ testTypedJx(function () { return Math.round(lfunc); }, 'round');
+ testTypedJx(function () { return Math.sin(lfunc); }, 'sin');
+ testTypedJx(function () { return Math.sqrt(lfunc); }, 'sqrt');
+ testTypedJx(function () { return Math.tan(lfunc); }, 'tan');
+}
+
try {
- print('Duktape.act() test');
- duktapeActTest();
+ print('Math built-in test');
+ mathBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+Number built-in test
+Number: number NaN
+new Number: object NaN
+toString: TypeError
+toLocaleString: TypeError
+valueOf: TypeError
+toFixed: TypeError
+toExponential: TypeError
+toPrecision: TypeError
+===*/
+
+function numberBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Number(lfunc); }, 'Number');
+ testTypedJx(function () { return new Number(lfunc); }, 'new Number');
+ testTypedJx(function () { return Number.prototype.toString.call(lfunc, lfunc); }, 'toString');
+ testTypedJx(function () { return Number.prototype.toLocaleString.call(lfunc, lfunc); }, 'toLocaleString');
+ testTypedJx(function () { return Number.prototype.valueOf.call(lfunc, lfunc); }, 'valueOf');
+ testTypedJx(function () { return Number.prototype.toFixed.call(lfunc, lfunc); }, 'toFixed');
+ testTypedJx(function () { return Number.prototype.toExponential.call(lfunc, lfunc); }, 'toExponential');
+ testTypedJx(function () { return Number.prototype.toPrecision.call(lfunc, lfunc); }, 'toPrecision');
}
+
try {
- print('exempt built-ins test');
- exemptBuiltinsTest();
+ print('Number built-in test');
+ numberBuiltinTest();
} catch (e) {
- print(e.stack);
+ print(e.stack || e);
+}
+
+/*===
+Object built-in test
+Object: function {_func:true}
+new Object: function {_func:true}
+getPrototypeOf: function {_func:true}
+setPrototypeOf: TypeError
+seal: function {_func:true}
+freeze: function {_func:true}
+preventExtensions: function {_func:true}
+isSealed: boolean true
+isFrozen: boolean true
+isExtensible: boolean false
+toString: string "[object Function]"
+toLocaleString: string "function light_PTR_0511() {(* native *)}"
+valueOf: function {_func:true}
+isPrototypeOf: boolean false
+===*/
+
+function objectBuiltinTest() {
+ var lfunc = Math.cos;
+
+ // Object coercion: return ToObject, i.e. normal function
+ testTypedJx(function () { return Object(lfunc); }, 'Object');
+
+ // new Object(x) is supposed to return 'x' as is, if it is already a
+ // function. This is tricky for lightfuncs: should the input be returned
+ // as is (= a lightfunc) or ToObject coerced version of input (not a
+ // lightfunc)? At the moment ToObject(lightfunc) is returned.
+
+ testTypedJx(function () { return new Object(lfunc); }, 'new Object');
+ testTypedJx(function () { return Object.getPrototypeOf(lfunc); }, 'getPrototypeOf');
+ testTypedJx(function () { return Object.setPrototypeOf(lfunc, {}); }, 'setPrototypeOf');
+ testTypedJx(function () { return Object.seal(lfunc); }, 'seal');
+ testTypedJx(function () { return Object.freeze(lfunc); }, 'freeze');
+ testTypedJx(function () { return Object.preventExtensions(lfunc); }, 'preventExtensions');
+ testTypedJx(function () { return Object.isSealed(lfunc); }, 'isSealed');
+ testTypedJx(function () { return Object.isFrozen(lfunc); }, 'isFrozen');
+ testTypedJx(function () { return Object.isExtensible(lfunc); }, 'isExtensible');
+
+ // Covered elsewhere:
+ // - getOwnPropertyDescriptor()
+ // - defineProperty()
+ // - defineProperties()
+ // - keys()
+
+ testTypedJx(function () { return Object.prototype.toString.call(lfunc, lfunc); }, 'toString');
+ testTypedJx(function () { return sanitizeLfunc(Object.prototype.toLocaleString.call(lfunc, lfunc)); }, 'toLocaleString');
+ testTypedJx(function () { return Object.prototype.valueOf.call(lfunc, lfunc); }, 'valueOf');
+ testTypedJx(function () { return Object.prototype.isPrototypeOf.call(lfunc, lfunc); }, 'isPrototypeOf');
+}
+
+try {
+ print('Object built-in test');
+ objectBuiltinTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+Duktape.Pointer built-in test
+Duktape.Pointer: pointer (null)
+new Duktape.Pointer: object (null)
+toString: TypeError
+valueOf: TypeError
+===*/
+
+function duktapePointerBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return Duktape.Pointer(lfunc); }, 'Duktape.Pointer');
+ testTypedJx(function () { return new Duktape.Pointer(lfunc); }, 'new Duktape.Pointer');
+ testTypedJx(function () { return Duktape.Pointer.prototype.toString.call(lfunc); }, 'toString');
+ testTypedJx(function () { return Duktape.Pointer.prototype.toString.call(lfunc); }, 'valueOf');
+}
+
+try {
+ print('Duktape.Pointer built-in test');
+ duktapePointerBuiltinTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+Proxy built-in test
+get
+this: object false [object Object]
+target: function false function light_PTR_0511() {(* native *)}
+key: string name
+proxy.name: light_PTR_0511
+get
+this: object false [object Object]
+target: function false function light_PTR_0511() {(* native *)}
+key: string length
+proxy.length: 1
+get
+this: object false [object Object]
+target: function false function light_PTR_0511() {(* native *)}
+key: string nonExistent
+proxy.nonExistent: dummy
+get
+this: function false function light_PTR_0511() {(* native *)}
+target: object false [object Object]
+key: string foo
+proxy.foo: bar
+get
+this: function false function light_PTR_0511() {(* native *)}
+target: object false [object Object]
+key: string nonExistent
+proxy.nonExistent: dummy
+===*/
+
+function proxyBuiltinTest() {
+ var lfunc = Math.cos;
+
+ // Proxy as a target object; ES6 requires that must be an Object and a
+ // lightfunc pretends to be an object. So, it must be possible to use
+ // lightfunc as a target. Currently Proxy will just coerce the lightfunc
+ // to a full Function silently.
+
+ var handler = {}
+ var proxy = new Proxy(lfunc, handler);
+ handler.get = function (target, key) {
+ print('get');
+ print('this:', typeof this, isLightFunc(this), sanitizeLfunc(this));
+ print('target:', typeof target, isLightFunc(target), sanitizeLfunc(target));
+ print('key:', typeof key, key);
+ return target[key] || 'dummy'; // passthrough
+ }
+ print('proxy.name:', sanitizeLfunc(proxy.name));
+ print('proxy.length:', proxy.length);
+ print('proxy.nonExistent:', proxy.nonExistent);
+
+ // Proxy as a handler value; ES6 requires it must be an Object and a
+ // lightfunc pretends to be an object. The traps must be placed in
+ // Function.prototype for it to actually work - so this is not a very
+ // useful thing. Currently Proxy will just coerce the lightfunc to a
+ // full Function silently.
+
+ var proxy = new Proxy({ foo: 'bar' }, lfunc);
+ Function.prototype.get = function (target, key) {
+ print('get');
+ print('this:', typeof this, isLightFunc(this), sanitizeLfunc(this));
+ print('target:', typeof target, isLightFunc(target), target);
+ print('key:', typeof key, key);
+ return target[key] || 'dummy'; // passthrough
+ };
+ print('proxy.foo:', proxy.foo);
+ print('proxy.nonExistent:', proxy.nonExistent);
+ delete Function.prototype.get;
+}
+
+try {
+ print('Proxy built-in test');
+ proxyBuiltinTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+RegExp built-in test
+RegExp: SyntaxError
+new RegExp: SyntaxError
+exec: TypeError
+test: TypeError
+toString: TypeError
+valueOf: function {_func:true}
+===*/
+
+function regexpBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return RegExp(lfunc); }, 'RegExp');
+ testTypedJx(function () { return new RegExp(lfunc); }, 'new RegExp');
+
+ testTypedJx(function () { return RegExp.prototype.exec.call(lfunc, lfunc); }, 'exec');
+ testTypedJx(function () { return RegExp.prototype.test.call(lfunc, lfunc); }, 'test');
+ testTypedJx(function () { return RegExp.prototype.toString.call(lfunc, lfunc); }, 'toString');
+ testTypedJx(function () { return RegExp.prototype.valueOf.call(lfunc, lfunc); }, 'valueOf');
+}
+
+try {
+ print('RegExp built-in test');
+ regexpBuiltinTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+String built-in test
+String: string "function light_PTR_0511() {(* light *)}"
+new String: string "function light_PTR_0511() {(* light *)}"
+new String: string "object"
+fromCharCode: string "\x00"
+toString: TypeError
+valueOf: TypeError
+charAt: string "f"
+charCodeAt: number 102
+concat: string "function light_PTR_0511() {(* light *)}function light_PTR_0511() {(* light *)}"
+indexOf: number 0
+lastIndexOf: number 0
+localeCompare: number 0
+match: SyntaxError
+replace: string "undefined"
+search: SyntaxError
+slice: string "function light_PTR_0511() {(* light *)}"
+split: object ["",""]
+substring: string "function light_PTR_0511() {(* light *)}"
+toLowerCase: string "function light_PTR_0511() {(* light *)}"
+toLocaleLowerCase: string "function light_PTR_0511() {(* light *)}"
+toUpperCase: string "FUNCTION LIGHT_PTR_0511() {(* LIGHT *)}"
+toLocaleUpperCase: string "FUNCTION LIGHT_PTR_0511() {(* LIGHT *)}"
+trim: string "function light_PTR_0511() {(* light *)}"
+substr: string "function light_PTR_0511() {(* light *)}"
+===*/
+
+function stringBuiltinTest() {
+ var lfunc = Math.cos;
+
+ testTypedJx(function () { return sanitizeLfunc(String(lfunc)); }, 'String');
+
+ // new String() returns an object, but sanitizeLfunc() coerces it to a
+ // plain string; check return type separately
+ testTypedJx(function () { return sanitizeLfunc(new String(lfunc)); }, 'new String');
+ testTypedJx(function () { return typeof new String(lfunc); }, 'new String');
+
+ testTypedJx(function () { return String.fromCharCode(lfunc); }, 'fromCharCode');
+
+ testTypedJx(function () { return String.prototype.toString.call(lfunc, lfunc); }, 'toString');
+ testTypedJx(function () { return String.prototype.valueOf.call(lfunc, lfunc); }, 'valueOf');
+ testTypedJx(function () { return String.prototype.charAt.call(lfunc, lfunc); }, 'charAt');
+ testTypedJx(function () { return String.prototype.charCodeAt.call(lfunc, lfunc); }, 'charCodeAt');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.concat.call(lfunc, lfunc)); }, 'concat');
+ testTypedJx(function () { return String.prototype.indexOf.call(lfunc, lfunc); }, 'indexOf');
+ testTypedJx(function () { return String.prototype.lastIndexOf.call(lfunc, lfunc); }, 'lastIndexOf');
+ testTypedJx(function () { return String.prototype.localeCompare.call(lfunc, lfunc); }, 'localeCompare');
+ testTypedJx(function () { return String.prototype.match.call(lfunc, lfunc); }, 'match');
+ testTypedJx(function () { return String.prototype.replace.call(lfunc, lfunc); }, 'replace');
+ testTypedJx(function () { return String.prototype.search.call(lfunc, lfunc); }, 'search');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.slice.call(lfunc, lfunc)); }, 'slice');
+ testTypedJx(function () { return String.prototype.split.call(lfunc, lfunc); }, 'split');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.substring.call(lfunc, lfunc)); }, 'substring');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.toLowerCase.call(lfunc, lfunc)); }, 'toLowerCase');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.toLocaleLowerCase.call(lfunc, lfunc)); }, 'toLocaleLowerCase');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.toUpperCase.call(lfunc, lfunc)); }, 'toUpperCase');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.toLocaleUpperCase.call(lfunc, lfunc)); }, 'toLocaleUpperCase');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.trim.call(lfunc, lfunc)); }, 'trim');
+ testTypedJx(function () { return sanitizeLfunc(String.prototype.substr.call(lfunc, lfunc)); }, 'substr');
+}
+
+try {
+ print('String built-in test');
+ stringBuiltinTest();
+} catch (e) {
+ print(e.stack || e);
+}
+
+/*===
+Duktape.Thread built-in test
+TypeError
+TypeError
+===*/
+
+function duktapeThreadBuiltinTest() {
+ var lfunc = Math.cos;
+ var thr;
+
+ // Lightfunc should be accepted as an initial function for a thread, but
+ // as of Duktape 1.0 only non-bound Ecmascript functions are allowed.
+ try {
+ thr = new Duktape.Thread(lfunc);
+ print(Duktape.Thread.resume(thr, 1.23));
+ } catch (e) {
+ print(e.name);
+ }
+
+ try {
+ print(Duktape.Thread.yield(lfunc, 1.23));
+ } catch (e) {
+ print(e.name);
+ }
+}
+
+try {
+ print('Duktape.Thread built-in test');
+ duktapeThreadBuiltinTest();
+} catch (e) {
+ print(e.stack || e);
}
diff --git a/src/duk_api_call.c b/src/duk_api_call.c
index acf62e662a..623e06f521 100644
--- a/src/duk_api_call.c
+++ b/src/duk_api_call.c
@@ -473,8 +473,6 @@ DUK_EXTERNAL duk_int_t duk_get_magic(duk_context *ctx, duk_idx_t index) {
DUK_ASSERT(ctx != NULL);
- /* FIXME: hardcoded values for flag partitioning */
-
tv = duk_require_tval(ctx, index);
if (DUK_TVAL_IS_OBJECT(tv)) {
h = DUK_TVAL_GET_OBJECT(tv);
@@ -484,10 +482,8 @@ DUK_EXTERNAL duk_int_t duk_get_magic(duk_context *ctx, duk_idx_t index) {
}
return (duk_int_t) ((duk_hnativefunction *) h)->magic;
} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
- /* FIXME: use shared macro */
- duk_int32_t tmp = (duk_int32_t) (DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) >> 8);
- tmp = (tmp << 16) >> 24; /* 0x0000##00 -> 0x##000000 -> sign extend to 0xssssss## */
- return (duk_int_t) tmp;
+ duk_small_uint_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv);
+ return (duk_int_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags);
}
/* fall through */
diff --git a/src/duk_api_internal.h b/src/duk_api_internal.h
index 55bc6f45e4..fb406af9f7 100644
--- a/src/duk_api_internal.h
+++ b/src/duk_api_internal.h
@@ -67,15 +67,18 @@ DUK_INTERNAL_DECL duk_hobject *duk_get_hobject(duk_context *ctx, duk_idx_t index
DUK_INTERNAL_DECL duk_hbuffer *duk_get_hbuffer(duk_context *ctx, duk_idx_t index);
DUK_INTERNAL_DECL duk_hthread *duk_get_hthread(duk_context *ctx, duk_idx_t index);
DUK_INTERNAL_DECL duk_hcompiledfunction *duk_get_hcompiledfunction(duk_context *ctx, duk_idx_t index);
-#if 0 /*unused */
DUK_INTERNAL_DECL duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, duk_idx_t index);
-#endif
#define duk_get_hobject_with_class(ctx,index,classnum) \
((duk_hobject *) duk_get_tagged_heaphdr_raw((ctx), (index), \
DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL | \
DUK_GETTAGGED_FLAG_CHECK_CLASS | ((classnum) << DUK_GETTAGGED_CLASS_SHIFT)))
+#if 0 /* This would be pointless: unexpected type and lightfunc would both return NULL */
+DUK_INTERNAL_DECL duk_hobject *duk_get_hobject_or_lfunc(duk_context *ctx, duk_idx_t index);
+#endif
+DUK_INTERNAL_DECL duk_hobject *duk_get_hobject_or_lfunc_coerce(duk_context *ctx, duk_idx_t index);
+
#if 0 /*unused*/
DUK_INTERNAL_DECL void *duk_get_voidptr(duk_context *ctx, duk_idx_t index);
#endif
diff --git a/src/duk_api_public.h.in b/src/duk_api_public.h.in
index 5f46b447a3..ceb19ae473 100644
--- a/src/duk_api_public.h.in
+++ b/src/duk_api_public.h.in
@@ -362,6 +362,7 @@ DUK_EXTERNAL_DECL void duk_push_thread_stash(duk_context *ctx, duk_context *targ
DUK_EXTERNAL_DECL duk_idx_t duk_push_object(duk_context *ctx);
DUK_EXTERNAL_DECL duk_idx_t duk_push_array(duk_context *ctx);
DUK_EXTERNAL_DECL duk_idx_t duk_push_c_function(duk_context *ctx, duk_c_function func, duk_idx_t nargs);
+DUK_EXTERNAL_DECL duk_idx_t duk_push_c_lightfunc(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_idx_t length, duk_int_t magic);
DUK_EXTERNAL_DECL duk_idx_t duk_push_thread_raw(duk_context *ctx, duk_uint_t flags);
#define duk_push_thread(ctx) \
@@ -419,6 +420,7 @@ DUK_EXTERNAL_DECL duk_bool_t duk_is_string(duk_context *ctx, duk_idx_t index);
DUK_EXTERNAL_DECL duk_bool_t duk_is_object(duk_context *ctx, duk_idx_t index);
DUK_EXTERNAL_DECL duk_bool_t duk_is_buffer(duk_context *ctx, duk_idx_t index);
DUK_EXTERNAL_DECL duk_bool_t duk_is_pointer(duk_context *ctx, duk_idx_t index);
+DUK_EXTERNAL_DECL duk_bool_t duk_is_lightfunc(duk_context *ctx, duk_idx_t index);
DUK_EXTERNAL_DECL duk_bool_t duk_is_array(duk_context *ctx, duk_idx_t index);
DUK_EXTERNAL_DECL duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t index);
@@ -495,6 +497,7 @@ DUK_EXTERNAL_DECL void *duk_require_heapptr(duk_context *ctx, duk_idx_t index);
DUK_TYPE_MASK_OBJECT | \
DUK_TYPE_MASK_BUFFER | \
DUK_TYPE_MASK_POINTER | \
+ DUK_TYPE_MASK_LIGHTFUNC | \
DUK_TYPE_MASK_THROW))
/*
diff --git a/src/duk_api_stack.c b/src/duk_api_stack.c
index da6f5cd6c2..e91085fc45 100644
--- a/src/duk_api_stack.c
+++ b/src/duk_api_stack.c
@@ -16,7 +16,7 @@
* Forward declarations
*/
-static duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_uint_t flags);
+DUK_LOCAL_DECL duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_uint_t flags);
/*
* Global state for working around missing variadic macros
@@ -1335,7 +1335,6 @@ DUK_INTERNAL duk_hcompiledfunction *duk_require_hcompiledfunction(duk_context *c
}
#endif
-#if 0 /*unused */
DUK_INTERNAL duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, duk_idx_t index) {
duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL);
if (h != NULL && !DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
@@ -1343,7 +1342,6 @@ DUK_INTERNAL duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, duk_
}
return (duk_hnativefunction *) h;
}
-#endif
DUK_INTERNAL duk_hnativefunction *duk_require_hnativefunction(duk_context *ctx, duk_idx_t index) {
duk_hthread *thr = (duk_hthread *) ctx;
@@ -1437,6 +1435,36 @@ DUK_EXTERNAL void *duk_require_heapptr(duk_context *ctx, duk_idx_t index) {
return (void *) NULL; /* not reachable */
}
+#if 0
+/* This would be pointless: we'd return NULL for both lightfuncs and
+ * unexpected types.
+ */
+duk_hobject *duk_get_hobject_or_lfunc(duk_context *ctx, duk_idx_t index) {
+}
+#endif
+
+/* Useful for internal call sites where we either expect an object (function)
+ * or a lightfunc. Accepts an object (returned as is) or a lightfunc (coerced
+ * to an object). Return value is NULL if value is neither an object nor a
+ * lightfunc.
+ */
+duk_hobject *duk_get_hobject_or_lfunc_coerce(duk_context *ctx, duk_idx_t index) {
+ duk_tval *tv;
+
+ DUK_ASSERT(ctx != NULL);
+
+ tv = duk_require_tval(ctx, index);
+ DUK_ASSERT(tv != NULL);
+ if (DUK_TVAL_IS_OBJECT(tv)) {
+ return DUK_TVAL_GET_OBJECT(tv);
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ duk_to_object(ctx, index);
+ return duk_require_hobject(ctx, index);
+ }
+
+ return NULL;
+}
+
/* Useful for internal call sites where we either expect an object (function)
* or a lightfunc. Returns NULL for a lightfunc.
*/
@@ -1512,8 +1540,9 @@ DUK_EXTERNAL duk_size_t duk_get_length(duk_context *ctx, duk_idx_t index) {
return (duk_size_t) DUK_HBUFFER_GET_SIZE(h);
}
case DUK_TAG_LIGHTFUNC: {
- /* FIXME: return value for virtual length property */
- return 0;
+ duk_small_uint_t lf_flags;
+ lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv);
+ return (duk_size_t) DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags);
}
default:
/* number */
@@ -1578,15 +1607,10 @@ DUK_EXTERNAL void duk_to_defaultvalue(duk_context *ctx, duk_idx_t index, duk_int
coercers[1] = DUK_STRIDX_TO_STRING;
index = duk_require_normalize_index(ctx, index);
-
- if (!duk_is_object(ctx, index)) {
- DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_OBJECT);
- }
- obj = duk_get_hobject(ctx, index);
- DUK_ASSERT(obj != NULL);
+ obj = duk_require_hobject_or_lfunc(ctx, index);
if (hint == DUK_HINT_NONE) {
- if (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_DATE) {
+ if (obj != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_DATE) {
hint = DUK_HINT_STRING;
} else {
hint = DUK_HINT_NUMBER;
@@ -1641,22 +1665,16 @@ DUK_EXTERNAL void duk_to_null(duk_context *ctx, duk_idx_t index) {
/* E5 Section 9.1 */
DUK_EXTERNAL void duk_to_primitive(duk_context *ctx, duk_idx_t index, duk_int_t hint) {
- duk_tval *tv;
-
DUK_ASSERT(ctx != NULL);
DUK_ASSERT(hint == DUK_HINT_NONE || hint == DUK_HINT_NUMBER || hint == DUK_HINT_STRING);
index = duk_require_normalize_index(ctx, index);
- tv = duk_require_tval(ctx, index);
- DUK_ASSERT(tv != NULL);
-
- if (DUK_TVAL_GET_TAG(tv) != DUK_TAG_OBJECT) {
+ if (!duk_check_type_mask(ctx, index, DUK_TYPE_MASK_OBJECT |
+ DUK_TYPE_MASK_LIGHTFUNC)) {
/* everything except object stay as is */
return;
}
- DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
-
duk_to_defaultvalue(ctx, index, hint);
}
@@ -2081,8 +2099,9 @@ DUK_EXTERNAL void *duk_to_pointer(duk_context *ctx, duk_idx_t index) {
res = (void *) DUK_TVAL_GET_HEAPHDR(tv);
break;
case DUK_TAG_LIGHTFUNC:
- /* FIXME: function pointers may not cast correctly to void *,
- * but try anyway?
+ /* Function pointers do not always cast correctly to void *
+ * (depends on memory and segmentation model for instance),
+ * so they coerce to NULL.
*/
res = NULL;
break;
@@ -2165,7 +2184,7 @@ DUK_EXTERNAL void duk_to_object(duk_context *ctx, duk_idx_t index) {
DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags);
nargs = DUK_LFUNC_FLAGS_GET_NARGS(lf_flags);
- if (nargs == 0x0f) {
+ if (nargs == DUK_LFUNC_NARGS_VARARGS) {
nargs = DUK_VARARGS;
}
flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
@@ -2410,6 +2429,11 @@ DUK_EXTERNAL duk_bool_t duk_is_pointer(duk_context *ctx, duk_idx_t index) {
return duk__tag_check(ctx, index, DUK_TAG_POINTER);
}
+DUK_EXTERNAL duk_bool_t duk_is_lightfunc(duk_context *ctx, duk_idx_t index) {
+ DUK_ASSERT(ctx != NULL);
+ return duk__tag_check(ctx, index, DUK_TAG_LIGHTFUNC);
+}
+
DUK_EXTERNAL duk_bool_t duk_is_array(duk_context *ctx, duk_idx_t index) {
duk_hobject *obj;
@@ -2823,27 +2847,15 @@ DUK_INTERNAL duk_hstring *duk_push_this_coercible_to_string(duk_context *ctx) {
DUK_EXTERNAL void duk_push_current_function(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_activation *act;
- duk_hobject *func;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(ctx != NULL);
DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
- /* FIXME: lightfunc handling: perhaps we'll need the lightfunc
- * ptr after all... with all the duk_tval fields.
- */
-
act = duk_hthread_get_current_activation(thr);
if (act) {
- /* FIXME: change if tv_func gets always initialized */
- func = DUK_ACT_GET_FUNC(act);
- if (func) {
- duk_push_hobject(ctx, func);
- } else {
- DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&act->tv_func));
- duk_push_tval(ctx, &act->tv_func);
- }
+ duk_push_tval(ctx, &act->tv_func);
} else {
duk_push_undefined(ctx);
}
@@ -3013,7 +3025,7 @@ DUK_INTERNAL duk_idx_t duk_push_object_helper(duk_context *ctx, duk_uint_t hobje
DUK_ASSERT(prototype_bidx == -1 ||
(prototype_bidx >= 0 && prototype_bidx < DUK_NUM_BUILTINS));
- /* check stack before interning (avoid hanging temp) */
+ /* check stack first */
if (thr->valstack_top >= thr->valstack_end) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
}
@@ -3100,7 +3112,7 @@ DUK_EXTERNAL duk_idx_t duk_push_thread_raw(duk_context *ctx, duk_uint_t flags) {
DUK_ASSERT(ctx != NULL);
- /* check stack before interning (avoid hanging temp) */
+ /* check stack first */
if (thr->valstack_top >= thr->valstack_end) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
}
@@ -3155,7 +3167,7 @@ DUK_INTERNAL duk_idx_t duk_push_compiledfunction(duk_context *ctx) {
DUK_ASSERT(ctx != NULL);
- /* check stack before interning (avoid hanging temp) */
+ /* check stack first */
if (thr->valstack_top >= thr->valstack_end) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
}
@@ -3196,7 +3208,7 @@ DUK_LOCAL duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function fu
DUK_ASSERT(ctx != NULL);
- /* check stack before interning (avoid hanging temp) */
+ /* check stack first */
if (thr->valstack_top >= thr->valstack_end) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
}
@@ -3280,6 +3292,43 @@ DUK_INTERNAL void duk_push_c_function_noconstruct_noexotic(duk_context *ctx, duk
(void) duk__push_c_function_raw(ctx, func, nargs, flags);
}
+DUK_EXTERNAL duk_idx_t duk_push_c_lightfunc(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_idx_t length, duk_int_t magic) {
+ duk_hthread *thr = (duk_hthread *) ctx;
+ duk_tval tv_tmp;
+ duk_small_uint_t lf_flags;
+
+ DUK_ASSERT(ctx != NULL);
+
+ /* check stack first */
+ if (thr->valstack_top >= thr->valstack_end) {
+ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+ }
+
+ if (nargs >= DUK_LFUNC_NARGS_MIN && nargs <= DUK_LFUNC_NARGS_MAX) {
+ /* as is */
+ } else if (nargs == DUK_VARARGS) {
+ nargs = DUK_LFUNC_NARGS_VARARGS;
+ } else {
+ goto api_error;
+ }
+ if (!(length >= DUK_LFUNC_LENGTH_MIN && length <= DUK_LFUNC_LENGTH_MAX)) {
+ goto api_error;
+ }
+ if (!(magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX)) {
+ goto api_error;
+ }
+
+ lf_flags = DUK_LFUNC_FLAGS_PACK(magic, length, nargs);
+ DUK_TVAL_SET_LIGHTFUNC(&tv_tmp, func, lf_flags);
+ duk_push_tval(ctx, &tv_tmp); /* XXX: direct valstack write */
+ DUK_ASSERT(thr->valstack_top != thr->valstack_bottom);
+ return ((duk_idx_t) (thr->valstack_top - thr->valstack_bottom)) - 1;
+
+ api_error:
+ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+ return 0; /* not reached */
+}
+
DUK_LOCAL duk_idx_t duk__push_error_object_vsprintf(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_idx_t ret;
@@ -3368,7 +3417,7 @@ DUK_EXTERNAL void *duk_push_buffer(duk_context *ctx, duk_size_t size, duk_bool_t
DUK_ASSERT(ctx != NULL);
- /* check stack before interning (avoid hanging temp) */
+ /* check stack first */
if (thr->valstack_top >= thr->valstack_end) {
DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
}
@@ -3667,8 +3716,7 @@ DUK_EXTERNAL duk_bool_t duk_strict_equals(duk_context *ctx, duk_idx_t index1, du
* Lightfunc
*/
-void duk_push_lightfunc_name(duk_context *ctx, duk_tval *tv) {
- /* FIXME: add pointer, and perhaps flags here? */
+DUK_INTERNAL void duk_push_lightfunc_name(duk_context *ctx, duk_tval *tv) {
duk_c_function func;
DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv));
@@ -3684,18 +3732,15 @@ void duk_push_lightfunc_name(duk_context *ctx, duk_tval *tv) {
* pressure in low memory environments (but only when function name
* is accessed).
*/
-#if 1
+
func = DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv);
duk_push_sprintf(ctx, "light_");
duk_push_string_funcptr(ctx, (duk_uint8_t *) &func, sizeof(func));
duk_push_sprintf(ctx, "_%04x", (unsigned int) DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv));
duk_concat(ctx, 3);
-#else
- duk_push_string(ctx, "light");
-#endif
}
-void duk_push_lightfunc_tostring(duk_context *ctx, duk_tval *tv) {
+DUK_INTERNAL void duk_push_lightfunc_tostring(duk_context *ctx, duk_tval *tv) {
DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv));
duk_push_string(ctx, "function ");
@@ -3711,7 +3756,7 @@ void duk_push_lightfunc_tostring(duk_context *ctx, duk_tval *tv) {
* bytes from memory.
*/
-void duk_push_string_funcptr(duk_context *ctx, duk_uint8_t *ptr, duk_size_t sz) {
+DUK_INTERNAL void duk_push_string_funcptr(duk_context *ctx, duk_uint8_t *ptr, duk_size_t sz) {
duk_uint8_t buf[32 * 2];
duk_uint8_t *p, *q;
duk_small_uint_t i;
diff --git a/src/duk_bi_buffer.c b/src/duk_bi_buffer.c
index 5e5e57fadd..8d255a9ce6 100644
--- a/src/duk_bi_buffer.c
+++ b/src/duk_bi_buffer.c
@@ -21,6 +21,8 @@ DUK_INTERNAL duk_ret_t duk_bi_buffer_constructor(duk_context *ctx) {
*
* http://nodejs.org/api/buffer.html
*
+ * Note that the ToBuffer() coercion (duk_to_buffer()) does NOT match
+ * the constructor behavior.
*/
buf_dynamic = duk_get_boolean(ctx, 1); /* default to false */
diff --git a/src/duk_bi_date.c b/src/duk_bi_date.c
index 69b3e12a58..1b8a1d51dc 100644
--- a/src/duk_bi_date.c
+++ b/src/duk_bi_date.c
@@ -1880,7 +1880,7 @@ static duk_uint16_t duk__date_magics[] = {
DUK__FLAG_NAN_TO_ZERO + DUK__FLAG_YEAR_FIXUP + (3 << DUK__FLAG_VALUE_SHIFT),
};
-duk_small_uint_t duk__date_get_indirect_magic(duk_context *ctx) {
+DUK_LOCAL duk_small_uint_t duk__date_get_indirect_magic(duk_context *ctx) {
duk_small_int_t magicidx = (duk_small_uint_t) duk_get_current_magic(ctx);
DUK_ASSERT(magicidx >= 0 && magicidx < (duk_small_int_t) (sizeof(duk__date_magics) / sizeof(duk_uint16_t)));
return (duk_small_uint_t) duk__date_magics[magicidx];
diff --git a/src/duk_bi_duktape.c b/src/duk_bi_duktape.c
index 46904cf491..eb8b237505 100644
--- a/src/duk_bi_duktape.c
+++ b/src/duk_bi_duktape.c
@@ -122,7 +122,6 @@ DUK_INTERNAL duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) {
DUK_INTERNAL duk_ret_t duk_bi_duktape_object_act(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_activation *act;
- duk_hobject *h_func;
duk_uint_fast32_t pc;
duk_uint_fast32_t line;
duk_int_t level;
@@ -139,16 +138,7 @@ DUK_INTERNAL duk_ret_t duk_bi_duktape_object_act(duk_context *ctx) {
duk_push_object(ctx);
- /* FIXME: push act->tv_func for lightfuncs, or perhaps tv_func just
- * directly if it is changed to be always initialized.
- */
- h_func = DUK_ACT_GET_FUNC(act);
- if (!h_func) {
- DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&act->tv_func));
- duk_push_tval(ctx, &act->tv_func);
- } else {
- duk_push_hobject(ctx, h_func);
- }
+ duk_push_tval(ctx, &act->tv_func);
pc = (duk_uint_fast32_t) act->pc;
duk_push_uint(ctx, (duk_uint_t) pc);
diff --git a/src/duk_bi_error.c b/src/duk_bi_error.c
index 3e4d464e27..2b5a241fa1 100644
--- a/src/duk_bi_error.c
+++ b/src/duk_bi_error.c
@@ -49,9 +49,7 @@ DUK_INTERNAL duk_ret_t duk_bi_error_prototype_to_string(duk_context *ctx) {
/* XXX: optimize with more direct internal access */
duk_push_this(ctx);
- if (!duk_is_object(ctx, -1)) {
- goto type_error;
- }
+ (void) duk_require_hobject_or_lfunc_coerce(ctx, -1);
/* [ ... this ] */
@@ -93,9 +91,6 @@ DUK_INTERNAL duk_ret_t duk_bi_error_prototype_to_string(duk_context *ctx) {
duk_concat(ctx, 3);
return 1;
-
- type_error:
- return DUK_RET_TYPE_ERROR;
}
#ifdef DUK_USE_TRACEBACKS
diff --git a/src/duk_bi_function.c b/src/duk_bi_function.c
index 0bdc568af4..44406019c6 100644
--- a/src/duk_bi_function.c
+++ b/src/duk_bi_function.c
@@ -330,8 +330,7 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) {
* is a bit ambiguous on this point but it would make sense.
*/
if (h_target == NULL) {
- /* lightfunc */
- /* FIXME: assume strict? */
+ /* Lightfuncs are always strict. */
DUK_HOBJECT_SET_STRICT(h_bound);
} else if (DUK_HOBJECT_HAS_STRICT(h_target)) {
DUK_HOBJECT_SET_STRICT(h_bound);
diff --git a/src/duk_bi_json.c b/src/duk_bi_json.c
index 7521e3f27d..287e2b74f8 100644
--- a/src/duk_bi_json.c
+++ b/src/duk_bi_json.c
@@ -1337,12 +1337,11 @@ DUK_LOCAL duk_bool_t duk__enc_value1(duk_json_enc_ctx *js_ctx, duk_idx_t idx_hol
DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
- h = duk_get_hobject(ctx, -1);
+ h = duk_get_hobject_or_lfunc_coerce(ctx, -1);
if (h != NULL) {
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_JSON);
- h = duk_get_hobject(ctx, -1);
+ h = duk_get_hobject_or_lfunc_coerce(ctx, -1); /* toJSON() can also be a lightfunc */
- /* FIXME: lightfunc support */
if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it"));
duk_dup(ctx, -2); /* -> [ ... key val toJSON val ] */
diff --git a/src/duk_bi_object.c b/src/duk_bi_object.c
index 37289aca9c..4efbf13e8d 100644
--- a/src/duk_bi_object.c
+++ b/src/duk_bi_object.c
@@ -17,13 +17,15 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor(duk_context *ctx) {
/* Pointer and buffer primitive values are treated like other
* primitives values which have a fully fledged object counterpart:
- * promote to an object value.
+ * promote to an object value. Lightfuncs are coerced with
+ * ToObject() even they could also be returned as is.
*/
if (duk_check_type_mask(ctx, 0, DUK_TYPE_MASK_STRING |
DUK_TYPE_MASK_BOOLEAN |
DUK_TYPE_MASK_NUMBER |
DUK_TYPE_MASK_POINTER |
- DUK_TYPE_MASK_BUFFER)) {
+ DUK_TYPE_MASK_BUFFER |
+ DUK_TYPE_MASK_LIGHTFUNC)) {
duk_to_object(ctx, 0);
return 1;
}
@@ -49,17 +51,8 @@ DUK_INTERNAL duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx) {
duk_insert(ctx, 0);
}
- /* FIXME: lightfunc hack, rework */
- {
- duk_tval *tv = duk_get_tval(ctx, 0);
- if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
- duk_push_hobject_bidx(ctx, DUK_BIDX_FUNCTION_PROTOTYPE);
- return 1;
- }
- }
-
- h = duk_require_hobject(ctx, 0);
- DUK_ASSERT(h != NULL);
+ h = duk_require_hobject_or_lfunc(ctx, 0);
+ /* h is NULL for lightfunc */
/* XXX: should the API call handle this directly, i.e. attempt
* to duk_push_hobject(ctx, null) would push a null instead?
@@ -67,7 +60,9 @@ DUK_INTERNAL duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx) {
* not wanted here.)
*/
- if (h->prototype) {
+ if (h == NULL) {
+ duk_push_hobject_bidx(ctx, DUK_BIDX_FUNCTION_PROTOTYPE);
+ } else if (h->prototype) {
duk_push_hobject(ctx, h->prototype);
} else {
duk_push_null(ctx);
@@ -106,13 +101,20 @@ DUK_INTERNAL duk_ret_t duk_bi_object_setprototype_shared(duk_context *ctx) {
duk_require_object_coercible(ctx, 0);
duk_require_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT);
}
+
+ h_new_proto = duk_get_hobject(ctx, 1);
+ /* h_new_proto may be NULL */
+ if (duk_is_lightfunc(ctx, 0)) {
+ if (h_new_proto == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]) {
+ goto skip;
+ }
+ goto fail_nonextensible;
+ }
h_obj = duk_get_hobject(ctx, 0);
if (!h_obj) {
goto skip;
}
- h_new_proto = duk_get_hobject(ctx, 1);
DUK_ASSERT(h_obj != NULL);
- /* h_new_proto may be NULL */
/* [[SetPrototypeOf]] standard behavior, E6 9.1.2 */
/* NOTE: steps 7-8 seem to be a cut-paste bug in the E6 draft */
@@ -203,10 +205,11 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context
duk_hobject *h;
duk_bool_t is_freeze;
- /* FIXME: lightfunc */
-
- h = duk_require_hobject(ctx, 0);
- DUK_ASSERT(h != NULL);
+ h = duk_require_hobject_or_lfunc(ctx, 0);
+ if (!h) {
+ /* Lightfunc, always success. */
+ return 1;
+ }
is_freeze = (duk_bool_t) duk_get_current_magic(ctx);
duk_hobject_object_seal_freeze_helper(thr, h, is_freeze);
@@ -223,9 +226,11 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *h;
- /* FIXME: lightfunc */
-
- h = duk_require_hobject(ctx, 0);
+ h = duk_require_hobject_or_lfunc(ctx, 0);
+ if (!h) {
+ /* Lightfunc, always success. */
+ return 1;
+ }
DUK_ASSERT(h != NULL);
DUK_HOBJECT_CLEAR_EXTENSIBLE(h);
@@ -243,8 +248,6 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_con
duk_bool_t is_frozen;
duk_bool_t rc;
- /* FIXME: lightfunc */
-
h = duk_require_hobject_or_lfunc(ctx, 0);
if (!h) {
duk_push_true(ctx); /* frozen and sealed */
@@ -259,8 +262,6 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_con
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx) {
duk_hobject *h;
- /* FIXME: lightfunc */
-
h = duk_require_hobject_or_lfunc(ctx, 0);
if (!h) {
duk_push_false(ctx);
diff --git a/src/duk_bi_proxy.c b/src/duk_bi_proxy.c
index 06715f1d7e..72d6f46cb3 100644
--- a/src/duk_bi_proxy.c
+++ b/src/duk_bi_proxy.c
@@ -16,7 +16,7 @@ DUK_INTERNAL duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) {
/* Reject a proxy object as the target because it would need
* special handler in property lookups. (ES6 has no such restriction)
*/
- h_target = duk_require_hobject(ctx, 0);
+ h_target = duk_require_hobject_or_lfunc_coerce(ctx, 0);
DUK_ASSERT(h_target != NULL);
if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h_target)) {
return DUK_RET_TYPE_ERROR;
@@ -25,7 +25,7 @@ DUK_INTERNAL duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) {
/* Reject a proxy object as the handler because it would cause
* potentially unbounded recursion. (ES6 has no such restriction)
*/
- h_handler = duk_require_hobject(ctx, 1);
+ h_handler = duk_require_hobject_or_lfunc_coerce(ctx, 1);
DUK_ASSERT(h_handler != NULL);
if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h_handler)) {
return DUK_RET_TYPE_ERROR;
diff --git a/src/duk_bi_thread.c b/src/duk_bi_thread.c
index f7c48c39ef..28ef291d68 100644
--- a/src/duk_bi_thread.c
+++ b/src/duk_bi_thread.c
@@ -12,10 +12,11 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_constructor(duk_context *ctx) {
duk_hthread *new_thr;
duk_hobject *func;
+ /* XXX: need a duk_require_func_or_lfunc_coerce() */
if (!duk_is_callable(ctx, 0)) {
return DUK_RET_TYPE_ERROR;
}
- func = duk_get_hobject(ctx, 0);
+ func = duk_require_hobject_or_lfunc_coerce(ctx, 0);
DUK_ASSERT(func != NULL);
duk_push_thread(ctx);
diff --git a/src/duk_debug_vsnprintf.c b/src/duk_debug_vsnprintf.c
index b6496a9b22..a2aa62ca87 100644
--- a/src/duk_debug_vsnprintf.c
+++ b/src/duk_debug_vsnprintf.c
@@ -725,8 +725,13 @@ DUK_LOCAL void duk__print_tval(duk__dprint_state *st, duk_tval *tv) {
break;
}
case DUK_TAG_LIGHTFUNC: {
- /* FIXME: func ptr and flags */
- duk_fb_sprintf(fb, "lightfunc");
+ duk_c_function func;
+ duk_small_uint_t lf_flags;
+
+ DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags);
+ duk_fb_sprintf(fb, "lightfunc:");
+ duk_fb_put_funcptr(fb, (duk_uint8_t *) &func, sizeof(func));
+ duk_fb_sprintf(fb, ":%04lx", (long) lf_flags);
break;
}
default: {
diff --git a/src/duk_error_augment.c b/src/duk_error_augment.c
index 88676ad82e..a8b14f5d54 100644
--- a/src/duk_error_augment.c
+++ b/src/duk_error_augment.c
@@ -224,7 +224,6 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */
for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) {
duk_uint32_t pc;
- duk_hobject *func;
/*
* Note: each API operation potentially resizes the callstack,
@@ -236,20 +235,10 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
/* [... arr] */
- /* FIXME: proper lightfunc support */
-#if 0
- DUK_ASSERT(thr_callstack->callstack[i].func != NULL);
DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */
-#endif
/* Add function object. */
- func = DUK_ACT_GET_FUNC(thr_callstack->callstack + i);
- if (func) {
- duk_push_hobject(ctx, func); /* -> [... arr func] */
- } else {
- duk_tval *tv = &(thr_callstack->callstack + i)->tv_func;
- duk_push_tval(ctx, tv);
- }
+ duk_push_tval(ctx, &(thr_callstack->callstack + i)->tv_func);
duk_def_prop_index_wec(ctx, -2, arr_idx);
arr_idx++;
diff --git a/src/duk_hobject_props.c b/src/duk_hobject_props.c
index 1f7f2519a0..ef7e55e52a 100644
--- a/src/duk_hobject_props.c
+++ b/src/duk_hobject_props.c
@@ -135,7 +135,7 @@ DUK_LOCAL duk_uint32_t duk__push_tval_to_hstring_arr_idx(duk_context *ctx, duk_t
}
/* String is an own (virtual) property of a lightfunc. */
-static duk_bool_t duk__key_is_lightfunc_ownprop(duk_hthread *thr, duk_hstring *key) {
+DUK_LOCAL duk_bool_t duk__key_is_lightfunc_ownprop(duk_hthread *thr, duk_hstring *key) {
return (key == DUK_HTHREAD_STRING_LENGTH(thr) ||
key == DUK_HTHREAD_STRING_NAME(thr));
}
@@ -2230,8 +2230,6 @@ DUK_INTERNAL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj,
case DUK_TAG_LIGHTFUNC: {
duk_int_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_obj);
- /* FIXME: remaining virtual properties */
-
/* Must coerce key: if key is an object, it may coerce to e.g. 'length'. */
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
@@ -2438,8 +2436,11 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj,
* here.
*/
- /* FIXME: refactor to avoid double call for key coercion */
- /* FIXME: remaining virtual properties */
+ /* XXX: Refactor key coercion so that it's only called once. It can't
+ * be trivially lifted here because the object must be type checked
+ * first.
+ */
+
if (DUK_TVAL_IS_OBJECT(tv_obj)) {
obj = DUK_TVAL_GET_OBJECT(tv_obj);
DUK_ASSERT(obj != NULL);
@@ -2447,7 +2448,6 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj,
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
} else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj)) {
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
-
if (duk__key_is_lightfunc_ownprop(thr, key)) {
/* FOUND */
rc = 1;
@@ -2522,6 +2522,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj,
/* XXX: inline into a prototype walking loop? */
rc = duk__get_property_desc(thr, obj, key, &desc, 0 /*flags*/); /* don't push value */
+ /* fall through */
pop_and_return:
duk_pop(ctx); /* [ key ] -> [] */
@@ -3133,8 +3134,6 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj,
* by an inherited setter which means we can't stop the lookup here.
*/
- /* FIXME: remaining virtual properties */
-
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
if (duk__key_is_lightfunc_ownprop(thr, key)) {
@@ -4301,7 +4300,7 @@ DUK_INTERNAL duk_ret_t duk_hobject_object_get_own_property_descriptor(duk_contex
DUK_ASSERT(thr != NULL);
DUK_ASSERT(thr->heap != NULL);
- obj = duk_require_hobject(ctx, 0);
+ obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
(void) duk_to_string(ctx, 1);
key = duk_require_hstring(ctx, 1);
@@ -4401,6 +4400,13 @@ DUK_LOCAL void duk__normalize_property_descriptor(duk_context *ctx) {
if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_GET)) {
duk_tval *tv = duk_require_tval(ctx, -1);
is_acc_desc = 1;
+ if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ /* NOTE: lightfuncs are coerced to full functions because
+ * lightfuncs don't fit into a property value slot. This
+ * has some side effects, see test-dev-lightfunc-accessor.js.
+ */
+ duk_to_object(ctx, -1);
+ }
if (DUK_TVAL_IS_UNDEFINED(tv) ||
(DUK_TVAL_IS_OBJECT(tv) &&
DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv)))) {
@@ -4413,6 +4419,13 @@ DUK_LOCAL void duk__normalize_property_descriptor(duk_context *ctx) {
if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_SET)) {
duk_tval *tv = duk_require_tval(ctx, -1);
is_acc_desc = 1;
+ if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+ /* NOTE: lightfuncs are coerced to full functions because
+ * lightfuncs don't fit into a property value slot. This
+ * has some side effects, see test-dev-lightfunc-accessor.js.
+ */
+ duk_to_object(ctx, -1);
+ }
if (DUK_TVAL_IS_UNDEFINED(tv) ||
(DUK_TVAL_IS_OBJECT(tv) &&
DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv)))) {
@@ -4464,16 +4477,14 @@ DUK_LOCAL void duk__normalize_property_descriptor(duk_context *ctx) {
* entirely.
*
* Note: this is only called for actual objects, not primitive values.
- * Thist must support virtual properties for full objects (e.g. Strings)
- * but not for plain values (e.g. lightfuncs).
+ * This must support virtual properties for full objects (e.g. Strings)
+ * but not for plain values (e.g. strings). Lightfuncs, even though
+ * primitive in a sense, are treated like objects and accepted as target
+ * values.
*
* This is a Duktape/C function.
*/
-/* FIXME: lightfunc support: because this operation can be done on objects,
- * lightfuncs must also be supported here...
- */
-
/* XXX: this is a major target for size optimization */
DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
@@ -4515,7 +4526,12 @@ DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
- obj = duk_require_hobject(ctx, 0);
+ /* Lightfuncs are currently supported by coercing to a temporary
+ * Function object; changes will be allowed (the coerced value is
+ * extensible) but will be lost.
+ */
+ obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
+
(void) duk_to_string(ctx, 1);
key = duk_require_hstring(ctx, 1);
desc = duk_require_hobject(ctx, 2);
@@ -4555,6 +4571,10 @@ DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
arrlen_old_len = 0;
arrlen_new_len = 0;
+ /* XXX: just normalize the descriptor here too? It would allow
+ * lightfunc coercion to be limited to the normalization function.
+ */
+
/*
* Extract property descriptor values as required in ToPropertyDescriptor().
* However, don't create an explicit property descriptor object: we don't
@@ -4586,7 +4606,11 @@ DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
get = NULL;
if (has_get && !duk_is_undefined(ctx, -1)) {
/* XXX: get = duk_require_callable_hobject(ctx, -1)? */
- get = duk_require_hobject(ctx, -1);
+ /* NOTE: lightfuncs are coerced to full functions because
+ * lightfuncs don't fit into a property value slot. This
+ * has some side effects, see test-dev-lightfunc-accessor.js.
+ */
+ get = duk_require_hobject_or_lfunc_coerce(ctx, -1);
DUK_ASSERT(get != NULL);
if (!DUK_HOBJECT_IS_CALLABLE(get)) {
goto fail_invalid_desc;
@@ -4597,7 +4621,11 @@ DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
has_set = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_SET);
set = NULL;
if (has_set && !duk_is_undefined(ctx, -1)) {
- set = duk_require_hobject(ctx, -1);
+ /* NOTE: lightfuncs are coerced to full functions because
+ * lightfuncs don't fit into a property value slot. This
+ * has some side effects, see test-dev-lightfunc-accessor.js.
+ */
+ set = duk_require_hobject_or_lfunc_coerce(ctx, -1);
DUK_ASSERT(set != NULL);
if (!DUK_HOBJECT_IS_CALLABLE(set)) {
goto fail_invalid_desc;
@@ -5376,7 +5404,9 @@ DUK_INTERNAL duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
*/
DUK_INTERNAL duk_ret_t duk_hobject_object_define_properties(duk_context *ctx) {
- duk_require_hobject(ctx, 0); /* target */
+ /* Lightfunc handling by ToObject() coercion. */
+ duk_require_hobject_or_lfunc_coerce(ctx, 0); /* target */
+
duk_to_object(ctx, 1); /* properties object */
DUK_DDD(DUK_DDDPRINT("target=%!iT, properties=%!iT",
diff --git a/src/duk_hthread.h b/src/duk_hthread.h
index 32143100df..abc6ab750b 100644
--- a/src/duk_hthread.h
+++ b/src/duk_hthread.h
@@ -135,7 +135,7 @@
* Struct defines
*/
-/* FIXME: for a memory-code tradeoff, remove 'func' and make it's access either a function
+/* XXX: for a memory-code tradeoff, remove 'func' and make it's access either a function
* or a macro. This would make the activation 32 bytes long on 32-bit platforms again.
*/
diff --git a/src/duk_hthread_builtins.c b/src/duk_hthread_builtins.c
index 1ffa4c3bee..8aa0b80e73 100644
--- a/src/duk_hthread_builtins.c
+++ b/src/duk_hthread_builtins.c
@@ -6,10 +6,6 @@
#include "duk_internal.h"
-/* FIXME: lightfunc check for other function values - constructors like
- * Number etc?
- */
-
/*
* Encoding constants, must match genbuiltins.py
*/
@@ -47,17 +43,6 @@
* by genbuiltins.py.
*/
-#ifdef DUK_USE_LIGHTFUNC_BUILTINS
-/* FIXME: test function */
-static int duk__lightfunc_test(duk_context *ctx) {
- int v1 = duk_get_int(ctx, 0);
- int v2 = duk_get_int(ctx, 1);
- fprintf(stderr, "Lightfunc called, top=%d, args: %d %d\n", duk_get_top(ctx), v1, v2);
- duk_push_int(ctx, v1 + v2);
- return 1;
-}
-#endif
-
DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
duk_context *ctx = (duk_context *) thr;
duk_bitdecoder_ctx bd_ctx;
@@ -111,7 +96,6 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
}
/* XXX: set magic directly here? (it could share the c_nargs arg) */
- DUK_D(DUK_DPRINT("FIXME: lightweight potential?"));
duk_push_c_function_noexotic(ctx, c_func, c_nargs);
h = duk_require_hobject(ctx, -1);
@@ -354,7 +338,6 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
c_func_getter = duk_bi_native_functions[natidx_getter];
c_func_setter = duk_bi_native_functions[natidx_setter];
- DUK_D(DUK_DPRINT("FIXME: lightweight potential?"));
duk_push_c_function_noconstruct_noexotic(ctx, c_func_getter, 0); /* always 0 args */
duk_push_c_function_noconstruct_noexotic(ctx, c_func_setter, 1); /* always 1 arg */
@@ -417,12 +400,10 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0);
#if defined(DUK_USE_LIGHTFUNC_BUILTINS)
- /* FIXME: hardcoded values, use constants */
- /* FIXME: fix awkward control flow */
lightfunc_eligible =
- ((c_nargs >= 0 && c_nargs <= 0x0e) || (c_nargs == DUK_VARARGS)) &&
- (c_length <= 0x0f) &&
- (magic >= -0x80 && magic <= 0x7f);
+ ((c_nargs >= DUK_LFUNC_NARGS_MIN && c_nargs <= DUK_LFUNC_NARGS_MAX) || (c_nargs == DUK_VARARGS)) &&
+ (c_length <= DUK_LFUNC_LENGTH_MAX) &&
+ (magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX);
if (stridx == DUK_STRIDX_EVAL ||
stridx == DUK_STRIDX_YIELD ||
stridx == DUK_STRIDX_RESUME ||
@@ -437,13 +418,11 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
if (lightfunc_eligible) {
duk_tval tv_lfunc;
- duk_small_uint_t lf_flags =
- ((magic << 8) & 0xff00UL) |
- (c_length << 4) |
- (c_nargs == DUK_VARARGS ? 0x0f : c_nargs);
+ duk_small_uint_t lf_nargs = (c_nargs == DUK_VARARGS ? DUK_LFUNC_NARGS_VARARGS : c_nargs);
+ duk_small_uint_t lf_flags = DUK_LFUNC_FLAGS_PACK(magic, c_length, lf_nargs);
DUK_TVAL_SET_LIGHTFUNC(&tv_lfunc, c_func, lf_flags);
duk_push_tval(ctx, &tv_lfunc);
- DUK_D(DUK_DPRINT("built-in function eligible as light function: i=%d, j=%d c_length=%ld, c_nargs=%ld, magic=%ld", (int) i, (int) j, (long) c_length, (long) c_nargs, (long) magic));
+ DUK_D(DUK_DPRINT("built-in function eligible as light function: i=%d, j=%d c_length=%ld, c_nargs=%ld, magic=%ld -> %!iT", (int) i, (int) j, (long) c_length, (long) c_nargs, (long) magic, duk_get_tval(ctx, -1)));
goto lightfunc_skip;
}
#endif /* DUK_USE_LIGHTFUNC_BUILTINS */
@@ -636,16 +615,6 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
}
#endif
-#ifdef DUK_USE_LIGHTFUNC_BUILTINS /* FIXME: lightfunc testing hack */
- {
- duk_tval tv_lfunc;
- DUK_TVAL_SET_LIGHTFUNC(&tv_lfunc, duk__lightfunc_test, DUK_LFUNC_FLAGS_PACK(0, 2, 2));
- duk_push_string(ctx, "fixme_lightfunc_test");
- duk_push_tval(ctx, &tv_lfunc);
- duk_def_prop(ctx, DUK_BIDX_DUKTAPE, DUK_PROPDESC_FLAGS_WC);
- }
-#endif
-
/*
* Pop built-ins from stack: they are now INCREF'd and
* reachable from the builtins[] array.
diff --git a/src/duk_hthread_stacks.c b/src/duk_hthread_stacks.c
index b2eb8642c7..e21db16ab3 100644
--- a/src/duk_hthread_stacks.c
+++ b/src/duk_hthread_stacks.c
@@ -199,14 +199,13 @@ DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_
DUK_DDD(DUK_DDDPRINT("skip closing environments, envs not owned by this activation"));
goto skip_env_close;
}
-
- /* FIXME: lightfunc handling, explicit check or comment */
+ /* func is NULL for lightfunc */
DUK_ASSERT(act->lex_env == act->var_env);
if (act->var_env != NULL) {
DUK_DDD(DUK_DDDPRINT("closing var_env record %p -> %!O",
(void *) act->var_env, (duk_heaphdr *) act->var_env));
- duk_js_close_environment_record(thr, act->var_env, DUK_ACT_GET_FUNC(act), act->idx_bottom);
+ duk_js_close_environment_record(thr, act->var_env, func, act->idx_bottom);
act = thr->callstack + idx; /* avoid side effect issues */
}
diff --git a/src/duk_js_call.c b/src/duk_js_call.c
index 9316aa47c0..e9dc893b49 100644
--- a/src/duk_js_call.c
+++ b/src/duk_js_call.c
@@ -318,7 +318,6 @@ void duk__handle_createargs_for_call(duk_hthread *thr,
* function. This would make call time handling much easier.
*/
-/* FIXME: lightfunc handling */
DUK_LOCAL
void duk__handle_bound_chain_for_call(duk_hthread *thr,
duk_idx_t idx_func,
@@ -369,7 +368,7 @@ void duk__handle_bound_chain_for_call(duk_hthread *thr,
*/
DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%ld: %!T",
- (void *) DUK_TVAL_GET_OBJECT(tv), (long) num_stack_args, tv_func));
+ (void *) DUK_TVAL_GET_OBJECT(tv_func), (long) num_stack_args, tv_func));
/* [ ... func this arg1 ... argN ] */
@@ -588,7 +587,8 @@ void duk__coerce_effective_this_binding(duk_hthread *thr,
if (func) {
strict = DUK_HOBJECT_HAS_STRICT(func);
} else {
- strict = 1; /* FIXME: lightweight, bit? */
+ /* Lightfuncs are always considered strict. */
+ strict = 1;
}
if (strict) {
@@ -599,6 +599,9 @@ void duk__coerce_effective_this_binding(duk_hthread *thr,
if (DUK_TVAL_IS_OBJECT(tv_this)) {
DUK_DDD(DUK_DDDPRINT("this binding: non-strict, object -> use directly"));
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv_this)) {
+ /* Lightfuncs are treated like objects and not coerced. */
+ DUK_DDD(DUK_DDDPRINT("this binding: non-strict, lightfunc -> use directly"));
} else if (DUK_TVAL_IS_UNDEFINED(tv_this) || DUK_TVAL_IS_NULL(tv_this)) {
DUK_DDD(DUK_DDDPRINT("this binding: non-strict, undefined/null -> use global object"));
obj_global = thr->builtins[DUK_BIDX_GLOBAL];
@@ -620,6 +623,67 @@ void duk__coerce_effective_this_binding(duk_hthread *thr,
}
}
+/*
+ * Shared helper for non-bound func lookup.
+ *
+ * Returns duk_hobject * to the final non-bound function (NULL for lightfunc).
+ */
+
+DUK_LOCAL
+duk_hobject *duk__nonbound_func_lookup(duk_context *ctx,
+ duk_idx_t idx_func,
+ duk_idx_t *out_num_stack_args,
+ duk_tval **out_tv_func,
+ duk_small_uint_t call_flags) {
+ duk_hthread *thr = (duk_hthread *) ctx;
+ duk_tval *tv_func;
+ duk_hobject *func;
+
+ for (;;) {
+ /* Use loop to minimize code size of relookup after bound function case */
+ tv_func = duk_get_tval(ctx, idx_func);
+ DUK_ASSERT(tv_func != NULL);
+
+ if (DUK_TVAL_IS_OBJECT(tv_func)) {
+ func = DUK_TVAL_GET_OBJECT(tv_func);
+ if (!DUK_HOBJECT_IS_CALLABLE(func)) {
+ goto not_callable_error;
+ }
+ if (DUK_HOBJECT_HAS_BOUND(func)) {
+ duk__handle_bound_chain_for_call(thr, idx_func, out_num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+
+ /* The final object may be a normal function or a lightfunc.
+ * We need to re-lookup tv_func because it may have changed
+ * (also value stack may have been resized). Loop again to
+ * do that; we're guaranteed not to come here again.
+ */
+ DUK_ASSERT(DUK_TVAL_IS_OBJECT(duk_require_tval(ctx, idx_func)) ||
+ DUK_TVAL_IS_LIGHTFUNC(duk_require_tval(ctx, idx_func)));
+ continue;
+ }
+ } else if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
+ func = NULL;
+ } else {
+ goto not_callable_error;
+ }
+ break;
+ }
+
+ DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) ||
+ DUK_TVAL_IS_LIGHTFUNC(tv_func));
+ DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func));
+ DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPILEDFUNCTION(func) ||
+ DUK_HOBJECT_IS_NATIVEFUNCTION(func)));
+
+ *out_tv_func = tv_func;
+ return func;
+
+ not_callable_error:
+ DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
+ DUK_UNREACHABLE();
+ return NULL; /* never executed */
+}
+
/*
* Helper for making various kinds of calls.
*
@@ -682,7 +746,8 @@ duk_int_t duk_handle_call(duk_hthread *thr,
duk_idx_t nregs; /* # total registers target function wants on entry (< 0 => "as is") */
duk_size_t vs_min_size;
duk_hobject *func; /* 'func' on stack (borrowed reference) */
- duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) */
+ duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) or tv_func_copy */
+ duk_tval tv_func_copy; /* to avoid relookups */
duk_activation *act;
duk_hobject *env;
duk_jmpbuf our_jmpbuf;
@@ -747,6 +812,12 @@ duk_int_t duk_handle_call(duk_hthread *thr,
(void *) entry_curr_thread,
(long) entry_thread_state));
+ /* XXX: Multiple tv_func lookups are now avoided by making a local
+ * copy of tv_func. Another approach would be to compute an offset
+ * for tv_func from valstack bottom and recomputing the tv_func
+ * pointer quickly as valstack + offset instead of calling duk_get_tval().
+ */
+
#if 0
DUK_D(DUK_DPRINT("callstack before call setup:"));
DUK_DEBUG_DUMP_CALLSTACK(thr);
@@ -953,39 +1024,13 @@ duk_int_t duk_handle_call(duk_hthread *thr,
* the bound function chain first.
*/
- if (!duk_is_callable(thr, idx_func)) {
- DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
- }
- tv_func = duk_get_tval(ctx, idx_func);
- DUK_ASSERT(tv_func != NULL);
- /* FIXME: avoid calling duk_is_callable() in the first place? */
-
- if (DUK_TVAL_IS_OBJECT(tv_func)) {
- func = DUK_TVAL_GET_OBJECT(tv_func);
- if (DUK_HOBJECT_HAS_BOUND(func)) {
- duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
- }
- }
- tv_func = NULL; /* invalidated */
-
- tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
- DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) ||
- DUK_TVAL_IS_LIGHTFUNC(tv_func));
-
- if (DUK_TVAL_IS_OBJECT(tv_func)) {
- func = DUK_TVAL_GET_OBJECT(tv_func);
- } else {
- func = NULL;
- }
-
- DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func));
- DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPILEDFUNCTION(func) ||
- DUK_HOBJECT_IS_NATIVEFUNCTION(func)));
+ func = duk__nonbound_func_lookup(ctx, idx_func, &num_stack_args, &tv_func, call_flags);
+ DUK_TVAL_SET_TVAL(&tv_func_copy, tv_func);
+ tv_func = &tv_func_copy; /* local copy to avoid relookups */
duk__coerce_effective_this_binding(thr, func, idx_func + 1);
DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T",
(duk_tval *) duk_get_tval(ctx, idx_func + 1)));
- tv_func = NULL; /* invalidated */
/* These base values are never used, but if the compiler doesn't know
* that DUK_ERROR() won't return, these are needed to silence warnings.
@@ -999,16 +1044,13 @@ duk_int_t duk_handle_call(duk_hthread *thr,
duk_small_uint_t lf_flags;
DUK_DDD(DUK_DDDPRINT("lightfunc call handling"));
- tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_func));
lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_func);
- func = NULL;
- nargs = lf_flags & 0x0f; /* FIXME: constants */
- if (nargs == 0x0f) {
+ nargs = DUK_LFUNC_FLAGS_GET_NARGS(lf_flags);
+ if (nargs == DUK_LFUNC_NARGS_VARARGS) {
nargs = -1; /* vararg */
}
nregs = nargs;
- /* FIXME: extract magic here from lf_flags directly */
} else if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
nargs = ((duk_hcompiledfunction *) func)->nargs;
nregs = ((duk_hcompiledfunction *) func)->nregs;
@@ -1045,7 +1087,6 @@ duk_int_t duk_handle_call(duk_hthread *thr,
idx_args; /* bottom of new func */
vs_min_size += (nregs >= 0 ? nregs : num_stack_args); /* num entries of new func at entry */
if (func == NULL || DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
- /* FIXME: lightweight funcs must be handled above */
vs_min_size += DUK_VALSTACK_API_ENTRY_MINIMUM; /* Duktape/C API guaranteed entries (on top of args) */
}
vs_min_size += DUK_VALSTACK_INTERNAL_EXTRA, /* + spare */
@@ -1109,7 +1150,7 @@ duk_int_t duk_handle_call(duk_hthread *thr,
*/
act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;
- act->func = func; /* FIXME: this is an issue */
+ act->func = func; /* NULL for lightfunc */
act->var_env = NULL;
act->lex_env = NULL;
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
@@ -1120,19 +1161,17 @@ duk_int_t duk_handle_call(duk_hthread *thr,
#if 0 /* topmost activation idx_retval is considered garbage, no need to init */
act->idx_retval = 0;
#endif
- if (func == NULL) {
- /* FIXME: use macro, avoid block */
- /* FIXME: setting tv_func in other cases */
- tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
- DUK_TVAL_SET_TVAL(&act->tv_func, tv_func); /* borrowed, no refcount */
- }
+ DUK_TVAL_SET_TVAL(&act->tv_func, tv_func); /* borrowed, no refcount */
if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
/* duk_hthread_callstack_unwind() will decrease this on unwind */
thr->callstack_preventcount++;
}
- /* FIXME: lightfunc? */
+ /* XXX: Is this INCREF necessary? 'func' is always a borrowed
+ * reference reachable through the value stack? If changed, stack
+ * unwind code also needs to be fixed to match.
+ */
DUK_HOBJECT_INCREF(thr, func); /* act->func */
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
@@ -1214,6 +1253,7 @@ duk_int_t duk_handle_call(duk_hthread *thr,
/* XXX: replace with a single operation */
if (nregs >= 0) {
+ DUK_ASSERT(nargs >= 0);
duk_set_top(ctx, idx_args + nargs); /* clamp anything above nargs */
duk_set_top(ctx, idx_args + nregs); /* extend with undefined */
} else {
@@ -1267,7 +1307,6 @@ duk_int_t duk_handle_call(duk_hthread *thr,
if (func) {
rc = ((duk_hnativefunction *) func)->func((duk_context *) thr);
} else {
- /* FIXME: lightfunc */
duk_c_function funcptr = DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv_func);
rc = funcptr((duk_context *) thr);
}
@@ -1892,8 +1931,8 @@ duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
duk_idx_t idx_args; /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */
duk_idx_t nargs; /* # argument registers target function wants (< 0 => never for ecma calls) */
duk_idx_t nregs; /* # total registers target function wants on entry (< 0 => never for ecma calls) */
- duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) */
duk_hobject *func; /* 'func' on stack (borrowed reference) */
+ duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) */
duk_activation *act;
duk_hobject *env;
duk_bool_t use_tailcall;
@@ -1984,36 +2023,12 @@ duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
* function call.
*/
- if (!duk_is_callable(thr, idx_func)) {
- DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
- }
- tv_func = duk_get_tval(ctx, idx_func);
- DUK_ASSERT(tv_func != NULL);
- /* FIXME: avoid calling duk_is_callable() in the first place? */
-
- if (DUK_TVAL_IS_OBJECT(tv_func)) {
- func = DUK_TVAL_GET_OBJECT(tv_func);
- if (DUK_HOBJECT_HAS_BOUND(func)) {
- duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
- }
- }
- tv_func = NULL; /* invalidated */
-
- tv_func = duk_get_tval(ctx, idx_func); /* relookup, valstack resize possible */
- DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) ||
- DUK_TVAL_IS_LIGHTFUNC(tv_func));
-
- if (DUK_TVAL_IS_OBJECT(tv_func)) {
- func = DUK_TVAL_GET_OBJECT(tv_func);
- if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
- DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(func));
- DUK_DDD(DUK_DDDPRINT("final target is a native function, cannot do ecma-to-ecma call"));
- return 0;
- }
- } else {
- DUK_DDD(DUK_DDDPRINT("final target is a lightfunc, cannot do ecma-to-ecma call"));
+ func = duk__nonbound_func_lookup(ctx, idx_func, &num_stack_args, &tv_func, call_flags);
+ if (func == NULL || !DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+ DUK_DDD(DUK_DDDPRINT("final target is a lightfunc/nativefunc, cannot do ecma-to-ecma call"));
return 0;
}
+ /* XXX: tv_func is not actually needed */
DUK_ASSERT(func != NULL);
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
@@ -2022,7 +2037,6 @@ duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
duk__coerce_effective_this_binding(thr, func, idx_func + 1);
DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T",
duk_get_tval(ctx, idx_func + 1)));
- tv_func = NULL; /* invalidated */
nargs = ((duk_hcompiledfunction *) func)->nargs;
nregs = ((duk_hcompiledfunction *) func)->nregs;
@@ -2126,6 +2140,7 @@ duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
act->prev_caller = NULL;
#endif
act->pc = 0; /* don't want an intermediate exposed state with invalid pc */
+ DUK_TVAL_SET_OBJECT(&act->tv_func, func); /* borrowed, no refcount */
#ifdef DUK_USE_REFERENCE_COUNTING
DUK_HOBJECT_INCREF(thr, func);
act = thr->callstack + thr->callstack_top - 1; /* side effects (currently none though) */
@@ -2253,6 +2268,7 @@ duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
#if 0 /* topmost activation idx_retval is considered garbage, no need to init */
act->idx_retval = 0;
#endif
+ DUK_TVAL_SET_OBJECT(&act->tv_func, func); /* borrowed, no refcount */
DUK_HOBJECT_INCREF(thr, func); /* act->func */
diff --git a/src/duk_js_executor.c b/src/duk_js_executor.c
index 4c4a049897..8826ccdff8 100644
--- a/src/duk_js_executor.c
+++ b/src/duk_js_executor.c
@@ -827,7 +827,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
1, /* num_stack_args */
call_flags); /* call_flags */
if (setup_rc == 0) {
- /* FIXME: cannot happen? if not, document */
+ /* Shouldn't happen but check anyway. */
DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
}
@@ -2739,9 +2739,10 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
* target at DUK__REGP(idx) with the final, non-bound function (which
* may be a lightfunc), and fudge arguments if necessary.
*
- * FIXME: the call setup won't do "effective this binding" resolution
- * if an ecma-to-ecma call is not possible. This is quite confusing,
- * so perhaps add a helper for doing bound function and effective this
+ * XXX: If an ecma-to-ecma call is not possible, this initial call
+ * setup will do bound function chain resolution but won't do the
+ * "effective this binding" resolution which is quite confusing.
+ * Perhaps add a helper for doing bound function and effective this
* binding resolution - and call that explicitly? Ecma-to-ecma call
* setup and normal function handling can then assume this prestep has
* been done by the caller.
@@ -2786,18 +2787,16 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *entry_thread) {
if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
call_flags = 0; /* not protected, respect reclimit, not constructor */
- /* FIXME: eval needs special handling if it can also be a lightfunc
- * (already excepted from lightfunc status, explain here).
- */
- /* FIXME: the call target bound chain is already resolved by the ecma
- * call setup attempt, document here... Perhaps the bound chain resolution
- * could be a shared helper instead so that we could skip it here.
+ /* There is no eval() special handling here: eval() is never
+ * automatically converted to a lightfunc.
*/
+ DUK_ASSERT(DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv_func) != duk_bi_global_object_eval);
+
duk_handle_call(thr,
num_stack_args,
call_flags);
- /* FIXME: who should restore? */
+ /* XXX: who should restore? */
duk_require_stack_top(ctx, fun->nregs); /* may have shrunk by inner calls, must recheck */
duk_set_top(ctx, fun->nregs);
diff --git a/src/duk_js_ops.c b/src/duk_js_ops.c
index b2c70429cc..5ae97803ee 100644
--- a/src/duk_js_ops.c
+++ b/src/duk_js_ops.c
@@ -216,10 +216,7 @@ DUK_INTERNAL duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv) {
return duk__tonumber_string_raw(thr);
}
case DUK_TAG_POINTER: {
- /* Coerce like boolean. This allows code to do something like:
- *
- * if (ptr) { ... }
- */
+ /* Coerce like boolean */
void *p = DUK_TVAL_GET_POINTER(tv);
return (p != NULL ? 1.0 : 0.0);
}
@@ -959,11 +956,16 @@ DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_
/*
* 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(ctx, tv_x);
duk_push_tval(ctx, tv_y);
- func = duk_require_hobject(ctx, -1); /* FIXME: lightfunc */
+ func = duk_require_hobject(ctx, -1);
/*
* For bound objects, [[HasInstance]] just calls the target function
@@ -1020,7 +1022,9 @@ DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_
/* [ ... lval rval(func) ] */
- val = duk_get_hobject(ctx, -2);
+ /* Handle lightfuncs through object coercion for now. */
+ /* XXX: direct implementation */
+ val = duk_get_hobject_or_lfunc_coerce(ctx, -2);
if (!val) {
goto pop_and_false;
}
diff --git a/src/duk_js_var.c b/src/duk_js_var.c
index ce9fd7d791..95d67d49d0 100644
--- a/src/duk_js_var.c
+++ b/src/duk_js_var.c
@@ -568,7 +568,7 @@ DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject
DUK_ASSERT(thr != NULL);
DUK_ASSERT(env != NULL);
- /* FIXME: DUK_ASSERT(func != NULL); */
+ /* func is NULL for lightfuncs */
if (!DUK_HOBJECT_IS_DECENV(env) || DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
DUK_DDD(DUK_DDDPRINT("environment record not a declarative record, "
diff --git a/src/duk_strings.c b/src/duk_strings.c
index bdd89c8d6b..509343f509 100644
--- a/src/duk_strings.c
+++ b/src/duk_strings.c
@@ -27,7 +27,6 @@ DUK_INTERNAL const char *duk_str_not_number = "not number";
DUK_INTERNAL const char *duk_str_not_string = "not string";
DUK_INTERNAL const char *duk_str_not_pointer = "not pointer";
DUK_INTERNAL const char *duk_str_not_buffer = "not buffer";
-DUK_INTERNAL const char *duk_str_not_object = "not object";
DUK_INTERNAL const char *duk_str_unexpected_type = "unexpected type";
DUK_INTERNAL const char *duk_str_not_thread = "not thread";
#if 0 /*unused*/
diff --git a/src/duk_strings.h b/src/duk_strings.h
index fd1d359e7b..1f1c83b6c4 100644
--- a/src/duk_strings.h
+++ b/src/duk_strings.h
@@ -51,7 +51,6 @@ DUK_INTERNAL_DECL const char *duk_str_not_configurable;
#define DUK_STR_NOT_STRING duk_str_not_string
#define DUK_STR_NOT_POINTER duk_str_not_pointer
#define DUK_STR_NOT_BUFFER duk_str_not_buffer
-#define DUK_STR_NOT_OBJECT duk_str_not_object
#define DUK_STR_UNEXPECTED_TYPE duk_str_unexpected_type
#define DUK_STR_NOT_THREAD duk_str_not_thread
#if 0 /*unused*/
@@ -91,7 +90,6 @@ DUK_INTERNAL_DECL const char *duk_str_not_number;
DUK_INTERNAL_DECL const char *duk_str_not_string;
DUK_INTERNAL_DECL const char *duk_str_not_pointer;
DUK_INTERNAL_DECL const char *duk_str_not_buffer;
-DUK_INTERNAL_DECL const char *duk_str_not_object;
DUK_INTERNAL_DECL const char *duk_str_unexpected_type;
DUK_INTERNAL_DECL const char *duk_str_not_thread;
#if 0 /*unused*/
diff --git a/src/duk_tval.h b/src/duk_tval.h
index c73f4207dc..74cb631dde 100644
--- a/src/duk_tval.h
+++ b/src/duk_tval.h
@@ -196,7 +196,7 @@ typedef struct duk_tval_struct duk_tval;
struct duk_tval_struct {
duk_small_uint_t t;
- duk_small_uint_t v_flags;
+ duk_small_uint_t v_extra;
union {
duk_double_t d;
duk_small_int_t i;
@@ -260,7 +260,7 @@ struct duk_tval_struct {
#define DUK_TVAL_SET_LIGHTFUNC(tv,fp,flags) do { \
(tv)->t = DUK_TAG_LIGHTFUNC; \
- (tv)->v_flags = (flags); \
+ (tv)->v_extra = (flags); \
(tv)->v.lightfunc = (duk_c_function) (fp); \
} while (0)
@@ -292,11 +292,11 @@ struct duk_tval_struct {
#define DUK_TVAL_GET_NUMBER(tv) ((tv)->v.d)
#define DUK_TVAL_GET_POINTER(tv) ((tv)->v.voidptr)
#define DUK_TVAL_GET_LIGHTFUNC(tv,out_fp,out_flags) do { \
- (out_flags) = (duk_uint32_t) (tv)->v_flags; \
+ (out_flags) = (duk_uint32_t) (tv)->v_extra; \
(out_fp) = (tv)->v.lightfunc; \
} while (0)
#define DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv) ((tv)->v.lightfunc)
-#define DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) ((duk_uint32_t) ((tv)->v_flags))
+#define DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) ((duk_uint32_t) ((tv)->v_extra))
#define DUK_TVAL_GET_STRING(tv) ((tv)->v.hstring)
#define DUK_TVAL_GET_OBJECT(tv) ((tv)->v.hobject)
#define DUK_TVAL_GET_BUFFER(tv) ((tv)->v.hbuffer)
@@ -338,6 +338,14 @@ struct duk_tval_struct {
#define DUK_LFUNC_FLAGS_GET_NARGS(lf_flags) \
((lf_flags) & 0x0f)
#define DUK_LFUNC_FLAGS_PACK(magic,length,nargs) \
- ((magic) << 8) | ((length) << 4) | (nargs)
+ (((magic) & 0xff) << 8) | ((length) << 4) | (nargs)
+
+#define DUK_LFUNC_NARGS_VARARGS 0x0f /* varargs marker */
+#define DUK_LFUNC_NARGS_MIN 0x00
+#define DUK_LFUNC_NARGS_MAX 0x0e /* max, excl. varargs marker */
+#define DUK_LFUNC_LENGTH_MIN 0x00
+#define DUK_LFUNC_LENGTH_MAX 0x0f
+#define DUK_LFUNC_MAGIC_MIN (-0x80)
+#define DUK_LFUNC_MAGIC_MAX 0x7f
#endif /* DUK_TVAL_H_INCLUDED */
diff --git a/website/api/concepts.html b/website/api/concepts.html
index e0f704dae4..e91a1c7ef6 100644
--- a/website/api/concepts.html
+++ b/website/api/concepts.html
@@ -75,6 +75,7 @@ Stack type
object | DUK_TYPE_OBJECT | DUK_TYPE_MASK_OBJECT | object with properties | yes |
buffer | DUK_TYPE_BUFFER | DUK_TYPE_MASK_BUFFER | mutable byte buffer, fixed/dynamic | yes |
pointer | DUK_TYPE_POINTER | DUK_TYPE_MASK_POINTER | opaque pointer (void *) | no |
+lightfunc | DUK_TYPE_LIGHTFUNC | DUK_TYPE_MASK_LIGHTFUNC | plain Duktape/C pointer (non-object) | no |
@@ -123,8 +124,8 @@ Array index
Duktape/C function
A C function with a Duktape/C API signature can be associated with an
-Ecmascript function object, and gets called when the Ecmascript function
-object is called. Example:
+Ecmascript function object or a lightfunc value, and gets called when
+the associated value is called from Ecmascript code. Example:
int my_func(duk_context *ctx) {
duk_push_int(ctx, 123);
diff --git a/website/api/duk_get_magic.txt b/website/api/duk_get_magic.txt
index 2d2cc444e3..5c4acd7161 100644
--- a/website/api/duk_get_magic.txt
+++ b/website/api/duk_get_magic.txt
@@ -9,6 +9,11 @@ duk_int_t duk_get_magic(duk_context *ctx, duk_idx_t index);
at index
. If the value is not a Duktape/C function, an error is
thrown.
+
+Lightweight functions have space for only 8 magic value bits which is
+interpreted as a signed integer (-128 to 127).
+
+
=example
duk_int_t my_flags = duk_get_magic(ctx, -3);
diff --git a/website/api/duk_is_lightfunc.txt b/website/api/duk_is_lightfunc.txt
new file mode 100644
index 0000000000..c4429fd335
--- /dev/null
+++ b/website/api/duk_is_lightfunc.txt
@@ -0,0 +1,17 @@
+=proto
+duk_bool_t duk_is_lightfunc(duk_context *ctx, duk_idx_t index);
+
+=stack
+[ ... val! ... ]
+
+=summary
+Returns 1 if value at index
is a lightfunc, otherwise
+returns 0. If index
is invalid, also returns 0.
+
+=example
+if (duk_is_lightfunc(ctx, -3)) {
+ /* ... */
+}
+
+=tags
+stack
diff --git a/website/api/duk_push_c_function.txt b/website/api/duk_push_c_function.txt
index 067570da8e..e8d702e548 100644
--- a/website/api/duk_push_c_function.txt
+++ b/website/api/duk_push_c_function.txt
@@ -63,6 +63,9 @@ void test(void) {
duk_pop(ctx);
}
+=seealso
+duk_push_c_lightfunc
+
=tags
stack
function
diff --git a/website/api/duk_push_c_lightfunc.txt b/website/api/duk_push_c_lightfunc.txt
new file mode 100644
index 0000000000..a212b32c7e
--- /dev/null
+++ b/website/api/duk_push_c_lightfunc.txt
@@ -0,0 +1,58 @@
+=proto
+duk_idx_t duk_push_c_lightfunc(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_idx_t length, duk_int_t magic);
+
+=stack
+[ ... ] -> [ ... func! ]
+
+=summary
+Push a new lightfunc value, associated with a C function, to the stack.
+Returns non-negative index (relative to stack bottom) of the
+pushed lightfunc.
+
+A lightfunc is a tagged value which contains a Duktape/C function
+pointer and a small set of internal control flags with no related heap
+allocations. The internal control flags encode the nargs
,
+length
, and magic
values, which therefore have
+significant restrictions:
+
+nargs
must be [0,14] or DUK_VARARGS
.
+length
must be [0,15] and maps to the virtual length
+ property of the lightfunc.
+magic
must be [-128,127].
+
+
+A lightfunc cannot hold any own properties, it only has virtual
+name
and length
properties, and inherits
+further properties from Function.prototype
.
+
+The nargs
argument controls how the value stack looks like when
+func
is entered, and behaves like for ordinary Duktape/C functions, see
+duk_push_c_function()
.
+
+The function created will be callable both as a normal function (func()
)
+and as a constructor (new func()
). You can differentiate between the two
+call styles using duk_is_constructor_call()
.
+Although the function can be used as a constructor, it cannot have a prototype
+property like normal Function objects.
+
+
+If you intend to use the pushed lightfunc as a constructor, and want to use
+a custom prototype object (instead of Object.prototype
), the
+lightfunc must return an object value. The object will then replace the default
+instance (bound to this
) automatically created for the constructor,
+and will be the value of a new MyLightFunc()
expression.
+
+
+=example
+duk_idx_t func_idx;
+
+func_idx = duk_push_c_lightfunc(ctx, my_addtwo, 2 /*nargs*/, 2 /*length*/, 0 /*magic*/);
+
+=seealso
+duk_push_c_function
+
+=tags
+stack
+function
+lightfunc
+experimental
diff --git a/website/api/duk_to_boolean.txt b/website/api/duk_to_boolean.txt
index f017fbb67d..a741236a7c 100644
--- a/website/api/duk_to_boolean.txt
+++ b/website/api/duk_to_boolean.txt
@@ -10,11 +10,7 @@ duk_bool_t duk_to_boolean(duk_context *ctx, duk_idx_t index);
coerced value. Returns 1 if the result of the coercion true
, 0 otherwise.
If index
is invalid, throws an error.
-Custom type coercion:
-
-- Buffer:
false
for zero-size buffer, true
otherwise
-- Pointer:
false
for NULL
pointer, true
otherwise
-
+
=example
if (duk_to_boolean(ctx, -3)) {
diff --git a/website/api/duk_to_defaultvalue.txt b/website/api/duk_to_defaultvalue.txt
index 598044fdf2..78c742c219 100644
--- a/website/api/duk_to_defaultvalue.txt
+++ b/website/api/duk_to_defaultvalue.txt
@@ -14,8 +14,7 @@ the input value is a Date
instance, in which case a string is prefe
(the exact coercion behavior is described in the Ecmascript specification).
If the value is not an object or index
is invalid, throws an error.
-Custom type coercion: both buffer and pointer are non-object values, so they
-cause an error to be thrown.
+
[[DefaultValue]]
is a rather technical coercion, not usually needed by
diff --git a/website/api/duk_to_int.txt b/website/api/duk_to_int.txt
index 424ee07db4..992fcca8d6 100644
--- a/website/api/duk_to_int.txt
+++ b/website/api/duk_to_int.txt
@@ -12,11 +12,7 @@ result using the algorithm described in
duk_get_int()
(this second coercion is not
reflected in the new stack value). If
index
is invalid, throws an error.
-
Custom type coercion:
-
-- Buffer:
0
for zero-size buffer, 1
otherwise
-- Pointer:
0
for NULL
pointer, 1
otherwise
-
+
If you want the double
version of the ToInteger()
coerced value,
use:
diff --git a/website/api/duk_to_int32.txt b/website/api/duk_to_int32.txt
index 1f04228807..e2fd217fb2 100644
--- a/website/api/duk_to_int32.txt
+++ b/website/api/duk_to_int32.txt
@@ -10,11 +10,7 @@ duk_int32_t duk_to_int32(duk_context *ctx, duk_idx_t index);
coerced value. Returns the coerced value. If
index
is invalid, throws
an error.
-
Custom type coercion:
-
-- Buffer:
0
for zero-size buffer, 1
otherwise
-- Pointer:
0
for NULL
pointer, 1
otherwise
-
+
=example
printf("ToInt32(): %ld\n", (long) duk_to_int32(ctx, -3));
diff --git a/website/api/duk_to_lstring.txt b/website/api/duk_to_lstring.txt
index ca35be3897..bfcec2d446 100644
--- a/website/api/duk_to_lstring.txt
+++ b/website/api/duk_to_lstring.txt
@@ -12,12 +12,7 @@ NUL-terminated string data, and writes the string byte length to
*out_len<
(if out_len
is non-NULL
). If index
is invalid, throws
an error.
-Custom type coercion:
-
-- Buffer: coerces byte-for-byte into a string
-- Pointer: coerces to a string formatted with
sprintf()
format
- %p
-
+
=example
const char *ptr;
diff --git a/website/api/duk_to_number.txt b/website/api/duk_to_number.txt
index 11cee657b4..ce5d05149b 100644
--- a/website/api/duk_to_number.txt
+++ b/website/api/duk_to_number.txt
@@ -10,11 +10,7 @@ duk_double_t duk_to_number(duk_context *ctx, duk_idx_t index);
coerced value. Returns the coerced value. If index
is invalid, throws
an error.
-Custom type coercion:
-
-- Buffer:
0
for zero-size buffer, 1
otherwise
-- Pointer:
0
for NULL
pointer, 1
otherwise
-
+
=example
printf("coerced number: %lf\n", (double) duk_to_number(ctx, -3));
diff --git a/website/api/duk_to_primitive.txt b/website/api/duk_to_primitive.txt
index 022ac622fa..41a1421cd8 100644
--- a/website/api/duk_to_primitive.txt
+++ b/website/api/duk_to_primitive.txt
@@ -23,6 +23,8 @@ is invalid, throws an error.
Pointer object: coerces to the underlying plain pointer value
+
+
=example
duk_to_primitive(ctx, -3, DUK_HINT_NUMBER);
diff --git a/website/api/duk_to_string.txt b/website/api/duk_to_string.txt
index 33d8543d7c..7f9a4f4ff1 100644
--- a/website/api/duk_to_string.txt
+++ b/website/api/duk_to_string.txt
@@ -10,12 +10,7 @@ const char *duk_to_string(duk_context *ctx, duk_idx_t index);
coerced value. Returns a non-NULL
pointer to the read-only,
NUL-terminated string data. If index
is invalid, throws an error.
-Custom type coercion:
-
-- Buffer: coerces byte-for-byte into a string
-- Pointer: coerces to a string formatted with
sprintf()
format
- %p
-
+
=example
printf("coerced string: %s\n", duk_to_string(ctx, -3));
diff --git a/website/api/duk_to_uint16.txt b/website/api/duk_to_uint16.txt
index 9e770745c9..226aae4c78 100644
--- a/website/api/duk_to_uint16.txt
+++ b/website/api/duk_to_uint16.txt
@@ -10,11 +10,7 @@ duk_uint16_t duk_to_uint16(duk_context *ctx, duk_idx_t index);
coerced value. Returns the coerced value. If index
is invalid, throws
an error.
-Custom type coercion:
-
-- Buffer:
0
for zero-size buffer, 1
otherwise
-- Pointer:
0
for NULL
pointer, 1
otherwise
-
+
=example
printf("ToUint16(): %u\n", (unsigned int) duk_to_uint16(ctx, -3));
diff --git a/website/api/duk_to_uint32.txt b/website/api/duk_to_uint32.txt
index 34c93e4eea..e63e359beb 100644
--- a/website/api/duk_to_uint32.txt
+++ b/website/api/duk_to_uint32.txt
@@ -10,11 +10,7 @@ duk_uint32_t duk_to_uint32(duk_context *ctx, duk_idx_t index);
coerced value. Returns the coerced value. If index
is invalid, throws
an error.
-Custom type coercion:
-
-- Buffer:
0
for zero-size buffer, 1
otherwise
-- Pointer:
0
for NULL
pointer, 1
otherwise
-
+
=example
printf("ToUint32(): %lu\n", (unsigned long) duk_to_uint32(ctx, -3));
diff --git a/website/api/ref-custom-type-coercion.html b/website/api/ref-custom-type-coercion.html
new file mode 100644
index 0000000000..1afafb17c2
--- /dev/null
+++ b/website/api/ref-custom-type-coercion.html
@@ -0,0 +1,4 @@
+
diff --git a/website/guide/custombehavior.html b/website/guide/custombehavior.html
index 130b877e2f..4dc9d51695 100644
--- a/website/guide/custombehavior.html
+++ b/website/guide/custombehavior.html
@@ -6,8 +6,8 @@ Custom behavior
Duktape built-in and custom types
The Duktape
built-in is (of course) non-standard and provides
-access to Duktape specific features. Also the buffer and pointer types are
-custom.
+access to Duktape specific features. Also the buffer, pointer, and lightfunc
+types are custom.
Internal properties
diff --git a/website/guide/customjson.html b/website/guide/customjson.html
index 34c782f659..216fe89bd1 100644
--- a/website/guide/customjson.html
+++ b/website/guide/customjson.html
@@ -166,7 +166,7 @@ JSON format examples
Standard JSON: always encoded as null |
-köhä |
+"köhä" |
"köhä" |
"k\xf6h\xe4" |
"k\u00f6h\u00e4" |
@@ -235,6 +235,13 @@ JSON format examples
{"_func":true} |
Standard JSON: encoded as null inside arrays, otherwise omitted |
+
+lightfunc |
+n/a |
+{_func:true} |
+{"_func":true} |
+Formats like ordinary functions |
+
diff --git a/website/guide/duktapebuiltins.html b/website/guide/duktapebuiltins.html
index 724794f95d..ef33e7116d 100644
--- a/website/guide/duktapebuiltins.html
+++ b/website/guide/duktapebuiltins.html
@@ -411,6 +411,19 @@ info()
- |
- |
+
+lightfunc |
+type tag |
+- |
+- |
+- |
+- |
+- |
+- |
+- |
+- |
+- |
+
diff --git a/website/guide/functionobjects.html b/website/guide/functionobjects.html
index c8943d78a7..feb6304090 100644
--- a/website/guide/functionobjects.html
+++ b/website/guide/functionobjects.html
@@ -1,6 +1,6 @@
Function objects
-Property summary
+Properties of Ecmascript functions
Duktape Function objects add a few properties to standard Ecmascript
properties. The table below summarizes properties assigned to newly
@@ -12,7 +12,7 @@
Property summary
Property name | Compatibility | Description |
-length | standard | Function argument count (if relevant). Present for all Function objects, including bound functions. |
+length | standard | Function (nominal) argument count (if relevant). Present for all Function objects, including bound functions. |
prototype | standard | Prototype used for new objects when called as a constructor. Present for most constructable Function objects, not copied to bound functions. |
caller | standard | Accessor which throws an error. Present for strict functions and bound functions. Not copied to bound functions. (If DUK_OPT_NONSTD_FUNC_CALLER_PROPERTY is given, non-strict functions will get a non-standard caller property.) |
arguments | standard | Accessor which throws an error. Present for strict functions and bound functions. Not copied to bound functions. |
@@ -40,6 +40,13 @@ Property summary
}
+
+Several Ecmascript built-in functions have properties different from user
+created Functions.
+
+
+Properties of Duktape/C functions
+
User-created Duktape/C functions (duk_push_c_function()
) have
a different set of properties to reduce Function object memory footprint:
@@ -58,3 +65,36 @@ Property summary
and arguments
properties are missing by default. This is not strictly
compliant but is important to reduce function footprint. User code can of course
assign these but is not required to do so.
+
+Properties of lightweight Duktape/C functions
+
+Lightweight functions have a set of non-configurable, non-writable virtual
+properties listed below. Like normal functions, they inherit further properties
+from Function.prototype
.
+
+
+
+Property name | Compatibility | Description |
+
+
+length | standard | Function (nominal) argument count. |
+name | Duktape | Function name: "light_<PTR>_<FLAGS>" . |
+
+
+
+The name
property is an automatically generated virtual
+function name. <PTR> is a platform dependent dump of the Duktape/C
+function pointer, and <FLAGS> is a raw hex dump of the 16-bit internal
+control fields (the format is Duktape internal). You shouldn't rely on a
+specific format. For example:
+
+duk> print(myLightFunc.name);
+light_0805b94c_0511
+
+
+As for ordinary functions, a lightfunc coerces to an implementation
+dependent string. You shouldn't rely on a specific format. For example:
+
+duk> print(myLightFunc);
+function light_0805b94c_0511() {/* light */}
+
diff --git a/website/guide/stacktypes.html b/website/guide/stacktypes.html
index 95142fdbff..531047c4d8 100644
--- a/website/guide/stacktypes.html
+++ b/website/guide/stacktypes.html
@@ -1,19 +1,20 @@
Stack types
-Duktape stack types are:
+Overview
-(none) | DUK_TYPE_NONE | DUK_TYPE_MASK_NONE | no type (missing value, invalid index, etc) |
-undefined | DUK_TYPE_UNDEFINED | DUK_TYPE_MASK_UNDEFINED | undefined |
-null | DUK_TYPE_NULL | DUK_TYPE_MASK_NULL | null |
-boolean | DUK_TYPE_BOOLEAN | DUK_TYPE_MASK_BOOLEAN | true and false |
-number | DUK_TYPE_NUMBER | DUK_TYPE_MASK_NUMBER | IEEE double |
-string | DUK_TYPE_STRING | DUK_TYPE_MASK_STRING | immutable string |
-object | DUK_TYPE_OBJECT | DUK_TYPE_MASK_OBJECT | object with properties |
-buffer | DUK_TYPE_BUFFER | DUK_TYPE_MASK_BUFFER | mutable byte buffer, fixed/dynamic |
-pointer | DUK_TYPE_POINTER | DUK_TYPE_MASK_POINTER | opaque pointer (void *) |
+(none) | DUK_TYPE_NONE | DUK_TYPE_MASK_NONE | no type (missing value, invalid index, etc) |
+undefined | DUK_TYPE_UNDEFINED | DUK_TYPE_MASK_UNDEFINED | undefined |
+null | DUK_TYPE_NULL | DUK_TYPE_MASK_NULL | null |
+boolean | DUK_TYPE_BOOLEAN | DUK_TYPE_MASK_BOOLEAN | true and false |
+number | DUK_TYPE_NUMBER | DUK_TYPE_MASK_NUMBER | IEEE double |
+string | DUK_TYPE_STRING | DUK_TYPE_MASK_STRING | immutable string |
+object | DUK_TYPE_OBJECT | DUK_TYPE_MASK_OBJECT | object with properties |
+buffer | DUK_TYPE_BUFFER | DUK_TYPE_MASK_BUFFER | mutable byte buffer, fixed/dynamic |
+pointer | DUK_TYPE_POINTER | DUK_TYPE_MASK_POINTER | opaque pointer (void *) |
+lightfunc | DUK_TYPE_LIGHTFUNC | DUK_TYPE_MASK_LIGHTFUNC | plain Duktape/C pointer (non-object) |
@@ -54,16 +55,16 @@ Pointer stability
and fixed buffers is stable; it is safe to keep a C pointer referring to the
data even after a Duktape/C function returns as long the string or fixed
buffer remains reachable from a garbage collection point of view at all times.
-Note that this is not usually not the case for Duktape/C value stack
-arguments, for instance, unless specific arrangements are made.
+Note that this is not the case for Duktape/C value stack arguments, for
+instance, unless specific arrangements are made.
The data area of a dynamic buffer does not have a stable pointer.
The buffer itself has a heap header with a stable address but the current
buffer is allocated separately and potentially relocated when the buffer
is resized. It is thus unsafe to hold a pointer to a dynamic buffer's data
area across a buffer resize, and it's probably best not to hold a pointer
-after a Duktape/C function returns (how would you reliably know when the
-buffer is resized?).
+after a Duktape/C function returns (as there would be no easy way of being
+sure that the buffer hadn't been resized).
Type masks
@@ -305,3 +306,39 @@ Pointer
pointers inside an object and use the object finalizer to free the
native resources related to the pointer(s).
+Lightfunc
+
+The lightfunc type is a plain Duktape/C function pointer and a small
+set of control flags packed into a single tagged value which requires no further
+heap allocations. The control flags (16 bits currently) encode:
+(1) number of stack arguments expected by the Duktape/C function (0 to 14 or
+varargs), (2) virtual length
property value (0 to 15), and
+(3) a magic value (-128 to 127). Because a lightfunc is a plain tagged
+value, it cannot hold any actual own property values; it has a few virtual
+properties and inherits other properties through Function.prototype
.
+
+Lightfuncs are a separate tagged type in the Duktape C API, but behave mostly
+like Function objects for Ecmascript code. They have significant limitations
+compared to ordinary Function objects, the most important being:
+
+- Lightfuncs cannot hold own properties and have a fixed virtual
name
+ which appears in tracebacks, etc.
+- Lightfuncs can be used as constructor functions, but cannot have a
+
prototype
property. If you need to construct objects which
+ don't inherit from Object.prototype
(the default), you need to
+ construct and return an instance explicitly in the constructor.
+- Lightfuncs can be used as accessor properties (getters/setters), but they
+ get converted to actual functions; see
+ test-dev-lightfunc-accessor.js.
+- Lightfuncs cannot have a finalizer as they are a primitive type and
+ don't have a reference count field or otherwise participate in garbage
+ collection; see
+ test-dev-lightfunc-finalizer.js.
+
+
+Lightfuncs are useful for very low memory environments where the memory
+impact of ordinary Function objects matters. For more discussion, see
+Properties of lightweight Duktape/C functions,
+Type algorithms,
+and
+lightweight-functions.rst.
diff --git a/website/guide/typealgorithms.html b/website/guide/typealgorithms.html
index f00bdeb4bb..1f9c0047c6 100644
--- a/website/guide/typealgorithms.html
+++ b/website/guide/typealgorithms.html
@@ -21,6 +21,8 @@ Notation
n | number compare: NaN values compare false, zeroes compare true regardless of sign (e.g. +0 == -0) |
N | number compare in SameValue: NaN values compare true, zeroes compare with sign (e.g. SameValue(+0,-0) is false) |
p | heap pointer compare |
+L | lightfunc compare: to be considered equal, Duktape/C function pointers and
+ internal control flags (including the "magic" value) must match |
1 | string/buffer vs. number: coerce string with ToNumber() and retry comparison; a buffer is first
coerced to string and then to number (e.g. buffer with "2.5" coerces eventually to number 2.5) |
2 | boolean vs. any: coerce boolean with ToNumber() and retry comparison |
@@ -44,23 +46,29 @@ Equality (non-strict)
in particular that comparing a number to a pointer returns false. This seems
a bit unintuitive, but numbers cannot represent 64-pointers accurately,
comparing numbers and pointers might be error prone.
+Lightfunc: comparison against any other type returns false. Comparison
+ to a lightfunc returns true if and only if both the Duktape/C function
+ pointers and internal control flags (including the "magic" value) match.
+ Note that a lightfunc never compares equal to an ordinary Function object,
+ even when the Function object was created by coercing a lightfunc to an object.
The standard behavior as well as behavior for Duktape custom types is summarized in the table below:
- | und | nul | boo | num | str | obj | buf | ptr |
+ | und | nul | boo | num | str | obj | buf | ptr | lfn |
-und | t | t | f | f | f | f | f | f |
-nul | | t | f | f | f | f | f | f |
-boo | | | s | 2 | 2 | 2 | 2 | f |
-num | | | | n | 1 | 3 | 1 | f |
-str | | | | | s | 3 | s | f |
-obj | | | | | | p | 3 | f |
-buf | | | | | | | s | f |
-ptr | | | | | | | | s |
+und | t | t | f | f | f | f | f | f | f |
+nul | | t | f | f | f | f | f | f | f |
+boo | | | s | 2 | 2 | 2 | 2 | f | f |
+num | | | | n | 1 | 3 | 1 | f | f |
+str | | | | | s | 3 | s | f | f |
+obj | | | | | | p | 3 | f | f |
+buf | | | | | | | s | f | f |
+ptr | | | | | | | | s | f |
+lfn | | | | | | | | | L |
@@ -78,23 +86,25 @@ Strict equality
string interning). This is intentional, as it is important to be
able to compare buffer values quickly.
Pointer: like non-strict equality.
+Lightfunc: like non-strict equality.
The standard behavior as well as behavior for Duktape custom types is summarized in the table below:
- | und | nul | boo | num | str | obj | buf | ptr |
+ | und | nul | boo | num | str | obj | buf | ptr | lfn |
-und | t | f | f | f | f | f | f | f |
-nul | | t | f | f | f | f | f | f |
-boo | | | s | f | f | f | f | f |
-num | | | | n | f | f | f | f |
-str | | | | | s | f | f | f |
-obj | | | | | | p | f | f |
-buf | | | | | | | p | f |
-ptr | | | | | | | | s |
+und | t | f | f | f | f | f | f | f | f |
+nul | | t | f | f | f | f | f | f | f |
+boo | | | s | f | f | f | f | f | f |
+num | | | | n | f | f | f | f | f |
+str | | | | | s | f | f | f | f |
+obj | | | | | | p | f | f | f |
+buf | | | | | | | p | f | f |
+ptr | | | | | | | | s | f |
+lfn | | | | | | | | | L |
@@ -110,27 +120,29 @@ SameValue
- Buffer: like strict equality.
- Pointer: like non-strict (and strict) equality.
+- Lightfunc: like non-strict (and strict equality).
The standard behavior as well as behavior for Duktape custom types is summarized in the table below:
- | und | nul | boo | num | str | obj | buf | ptr |
+ | und | nul | boo | num | str | obj | buf | ptr | lfn |
-und | t | f | f | f | f | f | f | f |
-nul | | t | f | f | f | f | f | f |
-boo | | | s | f | f | f | f | f |
-num | | | | N | f | f | f | f |
-str | | | | | s | f | f | f |
-obj | | | | | | p | f | f |
-buf | | | | | | | p | f |
-ptr | | | | | | | | s |
+und | t | f | f | f | f | f | f | f | f |
+nul | | t | f | f | f | f | f | f | f |
+boo | | | s | f | f | f | f | f | f |
+num | | | | N | f | f | f | f | f |
+str | | | | | s | f | f | f | f |
+obj | | | | | | p | f | f | f |
+buf | | | | | | | p | f | f |
+ptr | | | | | | | | s | f |
+lfn | | | | | | | | | L |
-Type conversion and testing
+Type conversion and testing
The custom types behave as follows for Ecmascript coercions described
Type Conversion and Testing
@@ -138,21 +150,23 @@
Type conversion and testing
- | buffer | pointer |
+ | buffer | pointer | lightfunc |
-ToPrimitive | identity | identity |
-ToBoolean | false for zero-size buffer, true otherwise | false for NULL pointer, true otherwise |
-ToNumber | like ToNumber() for a string | 0 for NULL pointer, 1 otherwise |
-ToInteger | same as ToNumber | same as ToNumber |
-ToInt32 | same as ToNumber | same as ToNumber |
-ToUint32 | same as ToNumber | same as ToNumber |
-ToUint16 | same as ToNumber | same as ToNumber |
-ToString | string with bytes from buffer data | sprintf() with %p format (platform specific) |
-ToObject | Buffer object | Pointer object |
-CheckObjectCoercible | allow (no error) | allow (no error) |
-IsCallable | false | false |
-SameValue | (covered above) | (covered above) |
+DefaultValue | TypeError | TypeError | "light_<PTR>_<FLAGS>" (toString/valueOf) |
+ToPrimitive | identity | identity |
+"light_<PTR>_<FLAGS>" (toString/valueOf) |
+ToBoolean | false for zero-size buffer, true otherwise | false for NULL pointer, true otherwise | true |
+ToNumber | like ToNumber() for a string | 0 for NULL pointer, 1 otherwise | NaN |
+ToInteger | same as ToNumber | same as ToNumber | 0 |
+ToInt32 | same as ToNumber | same as ToNumber | 0 |
+ToUint32 | same as ToNumber | same as ToNumber | 0 |
+ToUint16 | same as ToNumber | same as ToNumber | 0 |
+ToString | string with bytes from buffer data | sprintf() with %p format (platform specific) | "light_<PTR>_<FLAGS>" |
+ToObject | Buffer object | Pointer object | Function object |
+CheckObjectCoercible | allow (no error) | allow (no error) | allow (no error) |
+IsCallable | false | false | true |
+SameValue | (covered above) | (covered above) | (covered above) |
@@ -160,6 +174,14 @@ Type conversion and testing
directly as string data. The bytes will then be interpreted as CESU-8
(or extended UTF-8) from Ecmascript point of view.
+When a lightfunc is coerced with ToPrimitive() it behaves like an ordinary
+function: it gets coerced with Function.prototype.toString()
with
+the result (normally) being the same as ToString() coercion.
+
+When a lightfunc is object coerced, a new Function object is created
+and the virtual properties (name
and length
and
+the internal "magic" value are copied over to the Function object.
+
Custom coercions (ToBuffer, ToPointer)
ToBuffer() coercion is used when a value is forced into a buffer
@@ -168,7 +190,7 @@
Custom coercions (ToBuffer, ToPointer)
- A buffer coerces to itself (identity). The same buffer value is
returned.
-- Any other type (including pointer) is first string coerced with
+
- Any other type (including pointer and lightfunc) is first string coerced with
ToString,
and the resulting string is then copied, byte-by-byte, into a
fixed-size buffer.
@@ -184,6 +206,8 @@ Custom coercions (ToBuffer, ToPointer)
buffer or a string does not point to the buffer/string data area.
(This coercion is likely to change.)
- Any other types (including number) coerce to a NULL pointer.
+- Lightfunc coerces to a NULL pointer. This is the case because C function
+ pointers cannot be coerced to a
void *
in a portable manner.
The following table summarizes how different types are handled:
@@ -201,9 +225,15 @@ Custom coercions (ToBuffer, ToPointer)
object | buffer with ToString(value) | ptr to heap hdr |
buffer | identity | ptr to heap hdr |
pointer | sprintf() with %p format (platform specific) | identity |
+lightfunc | buffer with ToString(value) | NULL |
+
+There is currently no ToLightFunc() coercion. Lightfuncs can only be created
+using the Duktape C API.
+
+
Addition
The Ecmascript addition operator is specified in
@@ -213,12 +243,13 @@
Addition
is extended to custom types as follows:
- As for standard types, object values are first coerced with
ToPrimitive()
,
- e.g. a Buffer
object is converted to a plain buffer value.
+ e.g. a Buffer
object is converted to a plain buffer value, and a
+ lightfunc value is (normally) coerced with ToString()
.
- The string concatenation rule is triggered if either argument is a string
or a buffer. The arguments are coerced to strings and then concatenated into
the result string. This means that adding two buffers currently results in a
string, not a buffer.
-- Pointer types fall into the default number addition case. They are coerced
+
- Pointer values fall into the default number addition case. They are coerced
with
ToNumber()
and then added as numbers. NULL pointers coerce to
0, non-NULL pointers to 1, so addition results may not be very intuitive.
@@ -235,8 +266,15 @@ Property access
duk> buf = Duktape.dec('hex', '414243'); // plain buffer
= ABC
duk> buf.toString;
-= function toString() {/* native code */}
+= function toString() {/* native */}
duk> typeof buf.toString();
= string
+Lightfuncs have a few non-configurable and non-writable virtual properties
+(name
and length
) and inherit their remaining
+properties from Function.prototype
, which allows ordinary inherited
+Function methods to be called:
+
+var bound = myLightFunc.bind('dummy', 123);
+
From b7398ce866dd5e7dfbff5163b03a9ecb52b4829c Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Mon, 10 Nov 2014 03:47:53 +0200
Subject: [PATCH 7/8] Lightfunc built-ins for ajduk
---
Makefile | 1 +
1 file changed, 1 insertion(+)
diff --git a/Makefile b/Makefile
index b058a8561e..f8379ddc86 100644
--- a/Makefile
+++ b/Makefile
@@ -797,6 +797,7 @@ ajduk: alljoyn-js ajtcl dist
$(CCOPTS_NONDEBUG) \
-m32 \
-UDUK_CMDLINE_FANCY -DDUK_CMDLINE_AJSHEAP -D_POSIX_C_SOURCE=200809L \
+ -DDUK_OPT_LIGHTFUNC_BUILTINS \
$(DUKTAPE_SOURCES) $(DUKTAPE_CMDLINE_SOURCES) \
alljoyn-js/ajs_heap.c ajtcl/src/aj_debug.c ajtcl/target/linux/aj_target_util.c \
-lm -lpthread
From ba1ada65a5eeff157d57cddc9adeff7b579e57c4 Mon Sep 17 00:00:00 2001
From: Sami Vaarala
Date: Sat, 15 Nov 2014 01:06:05 +0200
Subject: [PATCH 8/8] Release note: lightfunc changes
---
RELEASES.rst | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/RELEASES.rst b/RELEASES.rst
index cbf59db0f6..169bd03bc1 100644
--- a/RELEASES.rst
+++ b/RELEASES.rst
@@ -627,6 +627,18 @@ Planned
* Main release goal: improved low memory support to allow Duktape to run
better on devices with 128kB system memory
+* Add lightfunc (DUK_TYPE_LIGHTFUNC) primitive type, representing a
+ Duktape/C function with a plain tagged value without any heap allocations
+
+* Add duk_push_c_lightfunc() API call to push user lightfuncs on the
+ value stack
+
+* Add duk_is_lightfunc() API call to type check for lightfuncs
+
+* Add feature option DUK_OPT_LIGHTFUNC_BUILTINS which causes Duktape to use
+ lightfuncs for almost all built-in functions, saving around 14kB of Duktape
+ heap on 32-bit platforms
+
* Add duk_is_error() API call to check if a value inherits from Error
* Add duk_get_error_code() API call to check if a value inherits from