changeset 472:76aaaf7cd5a3 release-2.2 2.2.0

misc: update before 2.2.0
author David Demelier <markand@malikania.fr>
date Tue, 26 Sep 2017 12:33:56 +0200
parents a8f65c73dae6 (current diff) eeabfc77d7ca (diff)
children d5bfaf362ded
files CHANGES.md cmake/IrccdVersion.cmake plugins/debugnative/main.cpp
diffstat 52 files changed, 10556 insertions(+), 6305 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/CHANGES.md	Tue Sep 26 12:33:56 2017 +0200
@@ -1,6 +1,15 @@
 IRC Client Daemon CHANGES
 =========================
 
+irccd 2.2.0 2017-09-26
+----------------------
+
+  - Add new Irccd.Util.cut function (#635),
+  - Add new irccdctl commands to edit rules (#641),
+  - Plugin plugin: add options max-list-lines, max-list-columns (#618),
+  - Import Duktape 2.1.0 (#648),
+  - Fix identity.ctcp-version option (#690).
+
 irccd 2.1.3 2017-07-28
 ----------------------
 
--- a/INSTALL.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/INSTALL.md	Tue Sep 26 12:33:56 2017 +0200
@@ -4,7 +4,7 @@
 This guide will help you to install irccd on your computer. For a better guide
 with more details see:
 
-http://projects.malikania.fr/irccd/guide.html
+    http://projects.malikania.fr/irccd//build/build-from-sources.html
 
 Requirements
 ------------
--- a/cmake/IrccdOptions.cmake	Tue Nov 06 11:08:29 2018 +0100
+++ b/cmake/IrccdOptions.cmake	Tue Sep 26 12:33:56 2017 +0200
@@ -39,7 +39,6 @@
 #
 # WITH_BINDIR           Binary directory for irccd, irccdctl
 # WITH_PLUGINDIR        Path where plugins must be installed
-# WITH_NPLUGINDIR       Path where native plugins must be installed
 # WITH_DOCDIR           Path where to install documentation
 # WITH_MANDIR           Path where to install manuals
 # WITH_CONFDIR          Path where to search configuration files
@@ -48,13 +47,6 @@
 # WITH_SYSTEMDDIR       Path where to install systemd unit file
 #
 
-#
-# Options for unit tests only:
-#
-# WITH_TEST_IRCHOST     Which IRC server to use for tests (default: 127.0.0.1)
-# WITH_TEST_IRCPORT     Which IRC server port to use for tests (default: 6667)
-#
-
 # Manual pages on Windows are pretty useless.
 if (WIN32)
     set(DEFAULT_MAN "No")
@@ -86,9 +78,6 @@
 option(WITH_MAN "Install man pages" ${DEFAULT_MAN})
 option(WITH_PKGCONFIG "Enable pkg-config file" ${DEFAULT_PKGCONFIG})
 
-set(WITH_TEST_IRCHOST "127.0.0.1" CACHE STRING "IRC host for tests")
-set(WITH_TEST_IRCPORT 6667 CACHE STRING "IRC port for test")
-
 #
 # Installation paths.
 # -------------------------------------------------------------------
@@ -109,13 +98,11 @@
     set(WITH_DATADIR "share" CACHE STRING "Data directory")
     set(WITH_CACHEDIR "var" CACHE STRING "Temporary files directory")
     set(WITH_PLUGINDIR "share/plugins" CACHE STRING "Module prefix where to install")
-    set(WITH_NPLUGINDIR "lib/irccd/plugins" CACHE STRING "Directory for native plugins")
     set(WITH_DOCDIR "share/doc" CACHE STRING "Documentation directory")
 else ()
     set(WITH_DATADIR "share/irccd" CACHE STRING "Data directory")
     set(WITH_CACHEDIR "var/irccd" CACHE STRING "Temporary files directory")
     set(WITH_PLUGINDIR "share/irccd/plugins" CACHE STRING "Module prefix where to install")
-    set(WITH_NPLUGINDIR "lib/irccd/plugins" CACHE STRING "Directory for native plugins")
     set(WITH_DOCDIR "share/doc/irccd" CACHE STRING "Documentation directory")
 endif ()
 
--- a/cmake/IrccdVersion.cmake	Tue Nov 06 11:08:29 2018 +0100
+++ b/cmake/IrccdVersion.cmake	Tue Sep 26 12:33:56 2017 +0200
@@ -18,8 +18,8 @@
 
 # Irccd version.
 set(IRCCD_VERSION_MAJOR "2")
-set(IRCCD_VERSION_MINOR "1")
-set(IRCCD_VERSION_PATCH "3")
+set(IRCCD_VERSION_MINOR "2")
+set(IRCCD_VERSION_PATCH "0")
 set(IRCCD_VERSION "${IRCCD_VERSION_MAJOR}.${IRCCD_VERSION_MINOR}.${IRCCD_VERSION_PATCH}")
 set(IRCCD_VERSION_SHLIB "2")
 
@@ -32,6 +32,6 @@
 # IRCCD_RELEASE_DATE_DAY        2 digits (01 = first day of month)
 #
 set(IRCCD_RELEASE_DATE_YEAR 2017)
-set(IRCCD_RELEASE_DATE_MONTH 07)
-set(IRCCD_RELEASE_DATE_DAY 28)
+set(IRCCD_RELEASE_DATE_MONTH 09)
+set(IRCCD_RELEASE_DATE_DAY 26)
 set(IRCCD_RELEASE_DATE "${IRCCD_RELEASE_DATE_YEAR}-${IRCCD_RELEASE_DATE_MONTH}-${IRCCD_RELEASE_DATE_DAY}")
--- a/doc/examples/irccd.conf.sample	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/examples/irccd.conf.sample	Tue Sep 26 12:33:56 2017 +0200
@@ -18,13 +18,9 @@
 #    Load plugins by name or by paths.
 #
 # [plugins]
-# abc =                         # This will search for abc
+# abc = ""                      # This will search for abc
 # ask = /tmp/ask.js             # This use /tmp/ask.js to load the plugin
 
-[plugins]
-history = ""
-ask = ""
-
 # Section identities:
 #    This describe identities, you can add any number you want they are used with servers so you can reuse an
 #    identity for one or more servers if you want.
@@ -35,13 +31,12 @@
 # username = "bar"              # (string) the realname (Optional, default: IRC Client daemon),
 # realname = "Jean"             # (string) the username name (Optional, default: irccd),
 # ctcp-version = "irccd"        # (string) what version to respond to CTCP VERSION (Optional, default: IRC Client Daemon),
-# ctcp-autoreply = true         # (bool) enable auto CTCP VERSION reply, (Optional, default: true).
 
 [identity]
 name = "default"
 nickname = "superbot"
 username = "superbot"
-realname = "Superbot Killer"
+realname = "Superbot"
 
 # Section transport:
 #    You can use transport to wait for any input you want. Unix and internet sockets are supported. Unix are used
@@ -65,7 +60,7 @@
 type = "ip"
 address = "*"
 port = "5980"
-family = "ipv4 ipv6"
+family = ( "ipv4", "ipv6" )
 
 # Section server:
 #    List of server you want to connect to. A server may use an identity to
--- a/doc/examples/irccdctl.conf.sample	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/examples/irccdctl.conf.sample	Tue Sep 26 12:33:56 2017 +0200
@@ -20,7 +20,7 @@
 # type = "ip"
 # host = "localhost"            # (string) host to connect,
 # port = "1234"                 # (int) port number,
-# family = "ipv4 ipv6"          # (string) internet family: ipv6 or ipv4 (Optional, default: ipv4).
+# family = "ipv4" or "ipv6"     # (string) internet family: ipv6 or ipv4 (Optional, default: ipv4).
 #
 # For unix sockets:
 #
--- a/doc/html/CMakeLists.txt	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/html/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -152,6 +152,12 @@
     ${html_SOURCE_DIR}/irccdctl/command/plugin-load.md
     ${html_SOURCE_DIR}/irccdctl/command/plugin-reload.md
     ${html_SOURCE_DIR}/irccdctl/command/plugin-unload.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-add.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-edit.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-info.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-list.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-move.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-remove.md
     ${html_SOURCE_DIR}/irccdctl/command/server-cmode.md
     ${html_SOURCE_DIR}/irccdctl/command/server-cnotice.md
     ${html_SOURCE_DIR}/irccdctl/command/server-connect.md
--- a/doc/html/api/module/Irccd.Util/Irccd.Util.cut.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/html/api/module/Irccd.Util/Irccd.Util.cut.md	Tue Sep 26 12:33:56 2017 +0200
@@ -1,5 +1,5 @@
 ---
-function: format
+function: cut
 js: true
 summary: "Cut a piece of data into several lines."
 synopsis: "lines = Irccd.Util.cut(data, maxc, maxl)"
--- a/doc/html/irccd/configuring.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/html/irccd/configuring.md	Tue Sep 26 12:33:56 2017 +0200
@@ -114,8 +114,7 @@
   - **nickname**: (string) the nickname (Optional, default: irccd),
   - **realname**: (string) the realname (Optional, default: IRC Client Daemon),
   - **username**: (string) the username name (Optional, default: irccd),
-  - **ctcp-version**: (string) what version to respond to CTCP VERSION (Optional, default: IRC Client Daemon),
-  - **ctcp-autoreply**: (bool) enable auto CTCP VERSION reply, (Optional, default: true).
+  - **ctcp-version**: (string) what version to respond to CTCP VERSION (Optional, default: IRC Client Daemon).
 
 **Example**
 
--- a/doc/html/irccdctl/command/index.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/html/irccdctl/command/index.md	Tue Sep 26 12:33:56 2017 +0200
@@ -12,6 +12,12 @@
   - [plugin-load](plugin-load.html)
   - [plugin-reload](plugin-reload.html)
   - [plugin-unload](plugin-unload.html)
+  - [rule-add](rule-add.html)
+  - [rule-edit](rule-edit.html)
+  - [rule-info](rule-info.html)
+  - [rule-list](rule-list.html)
+  - [rule-move](rule-move.html)
+  - [rule-remove](rule-remove.html)
   - [server-cmode](server-cmode.html)
   - [server-cnotice](server-cnotice.html)
   - [server-connect](server-connect.html)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/html/irccdctl/command/rule-add.md	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,31 @@
+---
+title: rule-add
+guide: yes
+---
+
+# rule-add
+
+Add a new rule to irccd.
+
+If no index is specified, the rule is added to the end.
+
+## Usage
+
+````nohighlight
+$ irccdctl rule-add [options] accept|drop
+````
+
+Available options:
+
+  - **-c, --add-channel**: match a channel
+  - **-e, --add-event**: match an event
+  - **-i, --index**: rule position
+  - **-p, --add-plugin**: match a plugin
+  - **-s, --add-server**: match a server
+
+## Example
+
+````nohighlight
+$ irccdctl rule-add -p hangman drop
+$ irccdctl rule-add -s localhost -c #games -p hangman accept
+````
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/html/irccdctl/command/rule-edit.md	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,35 @@
+---
+title: rule-edit
+guide: yes
+---
+
+# rule-edit
+
+Edit an existing rule in irccd.
+
+All options can be specified multiple times.
+
+Available options:
+
+  - **a, --action**: set action
+  - **c, --add-channel**: match a channel
+  - **C, --remove-channel**: remove a channel
+  - **e, --add-event**: match an event
+  - **E, --remove-event**: remove an event
+  - **p, --add-plugin**: match a plugin
+  - **P, --add-plugin**: remove a plugin
+  - **s, --add-server**: match a server
+  - **S, --remove-server**: remove a server
+
+## Usage
+
+````nohighlight
+usage: irccdctl rule-edit [options] index
+````
+
+## Example
+
+````nohighlight
+$ irccdctl rule-edit -p hangman 0
+$ irccdctl rule-edit -S localhost -c #games -p hangman 1
+````
Binary file doc/html/irccdctl/command/rule-info.md has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/html/irccdctl/command/rule-list.md	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,14 @@
+---
+title: rule-list
+guide: yes
+---
+
+# rule-list
+
+List all rules.
+
+## Usage
+
+````nohighlight
+$ irccdctl rule-list
+````
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/html/irccdctl/command/rule-move.md	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,25 @@
+---
+title: rule-move
+guide: yes
+---
+
+# rule-move
+
+Move a rule from the given source at the specified destination index.
+
+The rule will replace the existing one at the given destination moving
+down every other rules. If destination is greater or equal the number of rules,
+the rule is moved to the end.
+
+## Usage
+
+````nohighlight
+irccdctl rule-move source destination
+````
+
+## Example
+
+````nohighlight
+irccdctl rule-move 0 5
+irccdctl rule-move 4 3
+````
Binary file doc/html/irccdctl/command/rule-remove.md has changed
--- a/doc/html/irccdctl/usage.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/html/irccdctl/usage.md	Tue Sep 26 12:33:56 2017 +0200
@@ -16,6 +16,7 @@
        irccdctl plugin-load logger
        irccdctl plugin-reload plugin
        irccdctl plugin-unload plugin
+       irccdctl rule-list
        irccdctl server-cmode server channel mode
        irccdctl server-cnotice server channel message
        irccdctl server-connect [options] id host [port]
--- a/doc/man/irccd.conf.5.in	Tue Nov 06 11:08:29 2018 +0100
+++ b/doc/man/irccd.conf.5.in	Tue Sep 26 12:33:56 2017 +0200
@@ -113,8 +113,6 @@
 (string) the username name (Optional, default: irccd).
 .It ctcp-version
 (string) what version to respond to CTCP VERSION (Optional, default: IRC Client Daemon).
-.It ctcp-autoreply
-(bool) enable auto CTCP VERSION reply, (Optional, default: true).
 .El
 .\" SERVER
 .Ss server
--- a/extern/VERSION.duktape.txt	Tue Nov 06 11:08:29 2018 +0100
+++ b/extern/VERSION.duktape.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -1,1 +1,1 @@
-2.0.1
+2.1.0
--- a/extern/duktape/duk_config.h	Tue Nov 06 11:08:29 2018 +0100
+++ b/extern/duktape/duk_config.h	Tue Sep 26 12:33:56 2017 +0200
@@ -1,9 +1,9 @@
 /*
  *  duk_config.h configuration header generated by genconfig.py.
  *
- *  Git commit: 3f5e91704aff97c754f6ef5d44aedc5e529d1b16
- *  Git describe: v2.0.1
- *  Git branch: v2.0-maintenance
+ *  Git commit: 1f1f51a4f9595ffe8def0e9ba45b20f14679393a
+ *  Git describe: v2.1.0
+ *  Git branch: master
  *
  *  Supported platforms:
  *      - Mac OSX, iPhone, Darwin
@@ -12,6 +12,7 @@
  *      - Generic BSD
  *      - Atari ST TOS
  *      - AmigaOS
+ *      - Durango (XboxOne)
  *      - Windows
  *      - Flashplayer (Crossbridge)
  *      - QNX
@@ -19,6 +20,8 @@
  *      - Emscripten
  *      - Linux
  *      - Solaris
+ *      - AIX
+ *      - HPUX
  *      - Generic POSIX
  *      - Cygwin
  *      - Generic UNIX
@@ -126,6 +129,11 @@
 #endif
 #endif
 
+/* Durango (Xbox One) */
+#if defined(_DURANGO) || defined(_XBOX_ONE)
+#define DUK_F_DURANGO
+#endif
+
 /* Windows, both 32-bit and 64-bit */
 #if defined(_WIN32) || defined(WIN32) || defined(_WIN64) || defined(WIN64) || \
     defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__)
@@ -170,6 +178,28 @@
 /* illumos / Solaris */
 #if defined(__sun) && defined(__SVR4)
 #define DUK_F_SUN
+#if defined(__SUNPRO_C) && (__SUNPRO_C < 0x550)
+#define DUK_F_OLD_SOLARIS
+/* Defines _ILP32 / _LP64 required by DUK_F_X86/DUK_F_X64.  Platforms
+ * are processed before architectures, so this happens before the
+ * DUK_F_X86/DUK_F_X64 detection is emitted.
+ */
+#include <sys/isa_defs.h>
+#endif
+#endif
+
+/* AIX */
+#if defined(_AIX)
+/* defined(__xlc__) || defined(__IBMC__): works but too wide */
+#define DUK_F_AIX
+#endif
+
+/* HPUX */
+#if defined(__hpux)
+#define DUK_F_HPUX
+#if defined(__ia64)
+#define DUK_F_HPUX_ITANIUM
+#endif
 #endif
 
 /* POSIX */
@@ -188,17 +218,6 @@
 #define DUK_F_UNIX
 #endif
 
-/* stdint.h not available */
-#if defined(DUK_F_WINDOWS) && defined(_MSC_VER)
-#if (_MSC_VER < 1700)
-/* VS2012+ has stdint.h, < VS2012 does not (but it's available for download). */
-#define DUK_F_NO_STDINT_H
-#endif
-#endif
-#if !defined(DUK_F_NO_STDINT_H) && (defined(DUK_F_TOS) || defined(DUK_F_BCC))
-#define DUK_F_NO_STDINT_H
-#endif
-
 /* C++ */
 #undef DUK_F_CPP
 #if defined(__cplusplus)
@@ -208,6 +227,9 @@
 /* Intel x86 (32-bit), x64 (64-bit) or x32 (64-bit but 32-bit pointers),
  * define only one of DUK_F_X86, DUK_F_X64, DUK_F_X32.
  * https://sites.google.com/site/x32abi/
+ *
+ * With DUK_F_OLD_SOLARIS the <sys/isa_defs.h> header must be included
+ * before this.
  */
 #if defined(__amd64__) || defined(__amd64) || \
     defined(__x86_64__) || defined(__x86_64) || \
@@ -334,6 +356,15 @@
 #define DUK_F_VBCC
 #endif
 
+#if defined(ANDROID) || defined(__ANDROID__)
+#define DUK_F_ANDROID
+#endif
+
+/* Atari Mint */
+#if defined(__MINT__)
+#define DUK_F_MINT
+#endif
+
 /*
  *  Platform autodetection
  */
@@ -458,6 +489,40 @@
 #if !defined(DUK_USE_BYTEORDER) && (defined(DUK_F_M68K) || defined(DUK_F_PPC))
 #define DUK_USE_BYTEORDER 3
 #endif
+#elif defined(DUK_F_DURANGO)
+/* --- Durango (XboxOne) --- */
+/* Durango = XboxOne
+ * Configuration is nearly identical to Windows, except for
+ * DUK_USE_DATE_TZO_WINDOWS.
+ */
+
+/* Initial fix: disable secure CRT related warnings when compiling Duktape
+ * itself (must be defined before including Windows headers).  Don't define
+ * for user code including duktape.h.
+ */
+#if defined(DUK_COMPILING_DUKTAPE) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+/* MSVC does not have sys/param.h */
+#define DUK_USE_DATE_NOW_WINDOWS
+#define DUK_USE_DATE_TZO_WINDOWS_NO_DST
+/* Note: PRS and FMT are intentionally left undefined for now.  This means
+ * there is no platform specific date parsing/formatting but there is still
+ * the ISO 8601 standard format.
+ */
+#if defined(DUK_COMPILING_DUKTAPE)
+/* Only include when compiling Duktape to avoid polluting application build
+ * with a lot of unnecessary defines.
+ */
+#include <windows.h>
+#endif
+
+#define DUK_USE_OS_STRING "durango"
+
+#if !defined(DUK_USE_BYTEORDER)
+#define DUK_USE_BYTEORDER 1
+#endif
 #elif defined(DUK_F_WINDOWS)
 /* --- Windows --- */
 /* Initial fix: disable secure CRT related warnings when compiling Duktape
@@ -527,6 +592,10 @@
 #define DUK_USE_OS_STRING "qnx"
 #elif defined(DUK_F_TINSPIRE)
 /* --- TI-Nspire --- */
+#if defined(DUK_COMPILING_DUKTAPE) && !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE    /* e.g. strptime */
+#endif
+
 #define DUK_USE_DATE_NOW_GETTIMEOFDAY
 #define DUK_USE_DATE_TZO_GMTIME_R
 #define DUK_USE_DATE_PRS_STRPTIME
@@ -607,12 +676,50 @@
 #define DUK_USE_DATE_FMT_STRFTIME
 
 #include <sys/types.h>
+#if defined(DUK_F_OLD_SOLARIS)
+/* Old Solaris with no endian.h, stdint.h */
+#define DUK_F_NO_STDINT_H
+#if !defined(DUK_USE_BYTEORDER)
+#define DUK_USE_BYTEORDER 3
+#endif
+#else  /* DUK_F_OLD_SOLARIS */
 #include <ast/endian.h>
+#endif  /* DUK_F_OLD_SOLARIS */
+
 #include <sys/param.h>
 #include <sys/time.h>
 #include <time.h>
 
 #define DUK_USE_OS_STRING "solaris"
+#elif defined(DUK_F_AIX)
+/* --- AIX --- */
+#if !defined(DUK_USE_BYTEORDER)
+#define DUK_USE_BYTEORDER 3
+#endif
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+
+#define DUK_USE_OS_STRING "aix"
+#elif defined(DUK_F_HPUX)
+/* --- HPUX --- */
+#define DUK_F_NO_STDINT_H
+#if !defined(DUK_USE_BYTEORDER)
+#define DUK_USE_BYTEORDER 3
+#endif
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+
+#define DUK_USE_OS_STRING "hpux"
 #elif defined(DUK_F_POSIX)
 /* --- Generic POSIX --- */
 #define DUK_USE_DATE_NOW_GETTIMEOFDAY
@@ -769,12 +876,8 @@
 /* --- MIPS 32-bit --- */
 #define DUK_USE_ARCH_STRING "mips32"
 /* MIPS byte order varies so rely on autodetection. */
-/* Based on 'make checkalign' there are no alignment requirements on
- * Linux MIPS except for doubles, which need align by 4.  Alignment
- * requirements vary based on target though.
- */
 #if !defined(DUK_USE_ALIGN_BY)
-#define DUK_USE_ALIGN_BY 4
+#define DUK_USE_ALIGN_BY 8
 #endif
 #define DUK_USE_PACKED_TVAL
 #define DUK_F_PACKED_TVAL_PROVIDED
@@ -782,9 +885,6 @@
 /* --- MIPS 64-bit --- */
 #define DUK_USE_ARCH_STRING "mips64"
 /* MIPS byte order varies so rely on autodetection. */
-/* Good default is a bit arbitrary because alignment requirements
- * depend on target.  See https://github.com/svaarala/duktape/issues/102.
- */
 #if !defined(DUK_USE_ALIGN_BY)
 #define DUK_USE_ALIGN_BY 8
 #endif
@@ -908,6 +1008,9 @@
 #define DUK_ALWAYS_INLINE   inline __attribute__((always_inline))
 #endif
 
+/* DUK_HOT */
+/* DUK_COLD */
+
 #if defined(DUK_F_DLL_BUILD) && defined(DUK_F_WINDOWS)
 /* MSVC dllexport/dllimport: appropriate __declspec depends on whether we're
  * compiling Duktape or the application.
@@ -1018,6 +1121,12 @@
 #define DUK_ALWAYS_INLINE   inline __attribute__((always_inline))
 #endif
 
+#if (defined(DUK_F_C99) || defined(DUK_F_CPP11)) && \
+    defined(DUK_F_GCC_VERSION) && (DUK_F_GCC_VERSION >= 40300)
+#define DUK_HOT             __attribute__((hot))
+#define DUK_COLD            __attribute__((cold))
+#endif
+
 #if defined(DUK_F_DLL_BUILD) && defined(DUK_F_WINDOWS)
 /* MSVC dllexport/dllimport: appropriate __declspec depends on whether we're
  * compiling Duktape or the application.
@@ -1424,10 +1533,14 @@
 #if defined(DUK_F_X86) || defined(DUK_F_X32) || \
     defined(DUK_F_M68K) || defined(DUK_F_PPC32) || \
     defined(DUK_F_BCC) || \
-    (defined(__WORDSIZE) && (__WORDSIZE == 32))
+    (defined(__WORDSIZE) && (__WORDSIZE == 32)) || \
+    ((defined(DUK_F_OLD_SOLARIS) || defined(DUK_F_AIX) || \
+      defined(DUK_F_HPUX)) && defined(_ILP32))
 #define DUK_F_32BIT_PTRS
 #elif defined(DUK_F_X64) || \
-      (defined(__WORDSIZE) && (__WORDSIZE == 64))
+      (defined(__WORDSIZE) && (__WORDSIZE == 64)) || \
+   ((defined(DUK_F_OLD_SOLARIS) || defined(DUK_F_AIX) || \
+     defined(DUK_F_HPUX)) && defined(_LP64))
 #define DUK_F_64BIT_PTRS
 #else
 /* not sure, not needed with C99 anyway */
@@ -2050,7 +2163,8 @@
 #define DUK_DOUBLE_INFINITY  (__builtin_inf())
 #elif defined(INFINITY)
 #define DUK_DOUBLE_INFINITY  ((double) INFINITY)
-#elif !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC) && !defined(DUK_F_BCC)
+#elif !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC) && !defined(DUK_F_BCC) && \
+      !defined(DUK_F_OLD_SOLARIS) && !defined(DUK_F_AIX)
 #define DUK_DOUBLE_INFINITY  (1.0 / 0.0)
 #else
 /* In VBCC (1.0 / 0.0) results in a warning and 0.0 instead of infinity.
@@ -2066,7 +2180,8 @@
 #undef DUK_USE_COMPUTED_NAN
 #if defined(NAN)
 #define DUK_DOUBLE_NAN       NAN
-#elif !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC) && !defined(DUK_F_BCC)
+#elif !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC) && !defined(DUK_F_BCC) && \
+      !defined(DUK_F_OLD_SOLARIS) && !defined(DUK_F_AIX)
 #define DUK_DOUBLE_NAN       (0.0 / 0.0)
 #else
 /* In VBCC (0.0 / 0.0) results in a warning and 0.0 instead of NaN.
@@ -2115,6 +2230,9 @@
  * To be safe, use replacements.
  */
 #define DUK_F_USE_REPL_ALL
+#elif defined(DUK_F_AIX)
+/* Older versions may be missing isnan(), etc. */
+#define DUK_F_USE_REPL_ALL
 #endif
 
 #if defined(DUK_F_USE_REPL_ALL)
@@ -2203,9 +2321,10 @@
 /* The functions below exist only in C99/C++11 or later and need a workaround
  * for platforms that don't include them.  MSVC isn't detected as C99, but
  * these functions also exist in MSVC 2013 and later so include a clause for
- * that too.
+ * that too.  Android doesn't have log2; disable all of these for Android.
  */
-#if defined(DUK_F_C99) || defined(DUK_F_CPP11) || (defined(_MSC_VER) && (_MSC_VER >= 1800))
+#if (defined(DUK_F_C99) || defined(DUK_F_CPP11) || (defined(_MSC_VER) && (_MSC_VER >= 1800))) && \
+    !defined(DUK_F_ANDROID) && !defined(DUK_F_MINT)
 #if !defined(DUK_CBRT)
 #define DUK_CBRT             cbrt
 #endif
@@ -2218,7 +2337,7 @@
 #if !defined(DUK_TRUNC)
 #define DUK_TRUNC            trunc
 #endif
-#endif  /* DUK_F_C99 */
+#endif  /* DUK_F_C99 etc */
 
 /* NetBSD 6.0 x86 (at least) has a few problems with pow() semantics,
  * see test-bug-netbsd-math-pow.js.  MinGW has similar (but different)
@@ -2431,7 +2550,8 @@
 /* Macro for suppressing warnings for potentially unreferenced variables.
  * The variables can be actually unreferenced or unreferenced in some
  * specific cases only; for instance, if a variable is only debug printed,
- * it is unreferenced when debug printing is disabled.
+ * it is unreferenced when debug printing is disabled.  May cause warnings
+ * for volatile arguments.
  */
 #define DUK_UNREF(x)  do { (void) (x); } while (0)
 #endif
@@ -2473,6 +2593,13 @@
 #define DUK_ALWAYS_INLINE  /*nop*/
 #endif
 
+#if !defined(DUK_HOT)
+#define DUK_HOT            /*nop*/
+#endif
+#if !defined(DUK_COLD)
+#define DUK_COLD           /*nop*/
+#endif
+
 #if !defined(DUK_EXTERNAL_DECL)
 #define DUK_EXTERNAL_DECL  extern
 #endif
@@ -2696,6 +2823,7 @@
 #define DUK_USE_FAST_REFCOUNT_DEFAULT
 #undef DUK_USE_FATAL_HANDLER
 #define DUK_USE_FINALIZER_SUPPORT
+#undef DUK_USE_FINALIZER_TORTURE
 #undef DUK_USE_FUNCPTR16
 #undef DUK_USE_FUNCPTR_DEC16
 #undef DUK_USE_FUNCPTR_ENC16
@@ -2704,16 +2832,26 @@
 #define DUK_USE_FUNC_NAME_PROPERTY
 #undef DUK_USE_GC_TORTURE
 #undef DUK_USE_GET_RANDOM_DOUBLE
+#undef DUK_USE_GLOBAL_BINDING
 #define DUK_USE_GLOBAL_BUILTIN
 #undef DUK_USE_HEAPPTR16
 #undef DUK_USE_HEAPPTR_DEC16
 #undef DUK_USE_HEAPPTR_ENC16
 #define DUK_USE_HEX_FASTPATH
+#define DUK_USE_HOBJECT_ARRAY_ABANDON_LIMIT 2
+#define DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT 9
+#define DUK_USE_HOBJECT_ARRAY_MINGROW_ADD 16
+#define DUK_USE_HOBJECT_ARRAY_MINGROW_DIVISOR 8
+#define DUK_USE_HOBJECT_ENTRY_MINGROW_ADD 16
+#define DUK_USE_HOBJECT_ENTRY_MINGROW_DIVISOR 8
 #define DUK_USE_HOBJECT_HASH_PART
+#define DUK_USE_HOBJECT_HASH_PROP_LIMIT 8
 #define DUK_USE_HSTRING_ARRIDX
 #define DUK_USE_HSTRING_CLEN
 #undef DUK_USE_HSTRING_EXTDATA
+#define DUK_USE_HTML_COMMENTS
 #define DUK_USE_IDCHAR_FASTPATH
+#undef DUK_USE_INJECT_HEAP_ALLOC_ERROR
 #undef DUK_USE_INTERRUPT_COUNTER
 #undef DUK_USE_INTERRUPT_DEBUG_FIXUP
 #define DUK_USE_JC
@@ -2729,10 +2867,8 @@
 #define DUK_USE_JX
 #define DUK_USE_LEXER_SLIDING_WINDOW
 #undef DUK_USE_LIGHTFUNC_BUILTINS
-#undef DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE
 #define DUK_USE_MARK_AND_SWEEP_RECLIMIT 256
 #define DUK_USE_MATH_BUILTIN
-#define DUK_USE_MS_STRINGTABLE_RESIZE
 #define DUK_USE_NATIVE_CALL_RECLIMIT 1000
 #define DUK_USE_NONSTD_ARRAY_CONCAT_TRAILER
 #define DUK_USE_NONSTD_ARRAY_MAP_TRAILER
@@ -2752,9 +2888,9 @@
 #undef DUK_USE_PREFER_SIZE
 #define DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
 #undef DUK_USE_REFCOUNT16
+#define DUK_USE_REFCOUNT32
 #define DUK_USE_REFERENCE_COUNTING
 #define DUK_USE_REFLECT_BUILTIN
-#undef DUK_USE_REFZERO_FINALIZER_TORTURE
 #undef DUK_USE_REGEXP_CANON_WORKAROUND
 #define DUK_USE_REGEXP_COMPILER_RECLIMIT 10000
 #define DUK_USE_REGEXP_EXECUTOR_RECLIMIT 10000
@@ -2766,6 +2902,7 @@
 #undef DUK_USE_ROM_STRINGS
 #define DUK_USE_SECTION_B
 #undef DUK_USE_SELF_TESTS
+#define DUK_USE_SHEBANG_COMMENTS
 #undef DUK_USE_SHUFFLE_TORTURE
 #define DUK_USE_SOURCE_NONBMP
 #undef DUK_USE_STRHASH16
@@ -2775,9 +2912,13 @@
 #undef DUK_USE_STRICT_UTF8_SOURCE
 #define DUK_USE_STRING_BUILTIN
 #undef DUK_USE_STRLEN16
-#undef DUK_USE_STRTAB_CHAIN
-#undef DUK_USE_STRTAB_CHAIN_SIZE
-#define DUK_USE_STRTAB_PROBE
+#define DUK_USE_STRTAB_GROW_LIMIT 17
+#define DUK_USE_STRTAB_MAXSIZE 268435456L
+#define DUK_USE_STRTAB_MINSIZE 1024
+#undef DUK_USE_STRTAB_PTRCOMP
+#define DUK_USE_STRTAB_RESIZE_CHECK_MASK 255
+#define DUK_USE_STRTAB_SHRINK_LIMIT 6
+#undef DUK_USE_STRTAB_TORTURE
 #undef DUK_USE_SYMBOL_BUILTIN
 #define DUK_USE_TAILCALL
 #define DUK_USE_TARGET_INFO "unknown"
@@ -2822,10 +2963,12 @@
 
 #if defined(DUK_USE_DATE_GET_LOCAL_TZOFFSET)
 /* External provider already defined. */
-#elif defined(DUK_USE_DATE_TZO_GMTIME_R) || defined(DUK_USE_DATE_TZO_GMTIME)
+#elif defined(DUK_USE_DATE_TZO_GMTIME_R) || defined(DUK_USE_DATE_TZO_GMTIME_S) || defined(DUK_USE_DATE_TZO_GMTIME)
 #define DUK_USE_DATE_GET_LOCAL_TZOFFSET(d)   duk_bi_date_get_local_tzoffset_gmtime((d))
 #elif defined(DUK_USE_DATE_TZO_WINDOWS)
 #define DUK_USE_DATE_GET_LOCAL_TZOFFSET(d)   duk_bi_date_get_local_tzoffset_windows((d))
+#elif defined(DUK_USE_DATE_TZO_WINDOWS_NO_DST)
+#define DUK_USE_DATE_GET_LOCAL_TZOFFSET(d)   duk_bi_date_get_local_tzoffset_windows_no_dst((d))
 #else
 #error no provider for DUK_USE_DATE_GET_LOCAL_TZOFFSET()
 #endif
@@ -3307,6 +3450,9 @@
 #if defined(DUK_USE_INTEGER_ME) && defined(DUK_USE_INTEGER_BE)
 #error config option DUK_USE_INTEGER_ME conflicts with option DUK_USE_INTEGER_BE (which is also defined)
 #endif
+#if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE)
+#error unsupported config option used (option has been removed): DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE
+#endif
 #if defined(DUK_USE_MARK_AND_SWEEP)
 #error unsupported config option used (option has been removed): DUK_USE_MARK_AND_SWEEP
 #endif
@@ -3319,6 +3465,9 @@
 #if defined(DUK_USE_MATH_ROUND)
 #error unsupported config option used (option has been removed): DUK_USE_MATH_ROUND
 #endif
+#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
+#error unsupported config option used (option has been removed): DUK_USE_MS_STRINGTABLE_RESIZE
+#endif
 #if defined(DUK_USE_NONSTD_REGEXP_DOLLAR_ESCAPE)
 #error unsupported config option used (option has been removed): DUK_USE_NONSTD_REGEXP_DOLLAR_ESCAPE
 #endif
@@ -3349,6 +3498,9 @@
 #if defined(DUK_USE_RDTSC)
 #error unsupported config option used (option has been removed): DUK_USE_RDTSC
 #endif
+#if defined(DUK_USE_REFZERO_FINALIZER_TORTURE)
+#error unsupported config option used (option has been removed): DUK_USE_REFZERO_FINALIZER_TORTURE
+#endif
 #if defined(DUK_USE_ROM_GLOBAL_CLONE) && !defined(DUK_USE_ROM_STRINGS)
 #error config option DUK_USE_ROM_GLOBAL_CLONE requires option DUK_USE_ROM_STRINGS (which is missing)
 #endif
@@ -3379,9 +3531,21 @@
 #if defined(DUK_USE_SIGSETJMP)
 #error unsupported config option used (option has been removed): DUK_USE_SIGSETJMP
 #endif
+#if defined(DUK_USE_STRTAB_CHAIN)
+#error unsupported config option used (option has been removed): DUK_USE_STRTAB_CHAIN
+#endif
+#if defined(DUK_USE_STRTAB_CHAIN_SIZE)
+#error unsupported config option used (option has been removed): DUK_USE_STRTAB_CHAIN_SIZE
+#endif
 #if defined(DUK_USE_STRTAB_CHAIN_SIZE) && !defined(DUK_USE_STRTAB_CHAIN)
 #error config option DUK_USE_STRTAB_CHAIN_SIZE requires option DUK_USE_STRTAB_CHAIN (which is missing)
 #endif
+#if defined(DUK_USE_STRTAB_PROBE)
+#error unsupported config option used (option has been removed): DUK_USE_STRTAB_PROBE
+#endif
+#if defined(DUK_USE_STRTAB_PTRCOMP) && !defined(DUK_USE_HEAPPTR16)
+#error config option DUK_USE_STRTAB_PTRCOMP requires option DUK_USE_HEAPPTR16 (which is missing)
+#endif
 #if defined(DUK_USE_TAILCALL) && defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
 #error config option DUK_USE_TAILCALL conflicts with option DUK_USE_NONSTD_FUNC_CALLER_PROPERTY (which is also defined)
 #endif
--- a/extern/duktape/duktape.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/extern/duktape/duktape.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -1,8 +1,8 @@
 /*
- *  Single source autogenerated distributable for Duktape 2.0.1.
- *
- *  Git commit 3f5e91704aff97c754f6ef5d44aedc5e529d1b16 (v2.0.1).
- *  Git branch v2.0-maintenance.
+ *  Single source autogenerated distributable for Duktape 2.1.0.
+ *
+ *  Git commit 1f1f51a4f9595ffe8def0e9ba45b20f14679393a (v2.1.0).
+ *  Git branch master.
  *
  *  See Duktape AUTHORS.rst and LICENSE.txt for copyright and
  *  licensing information.
@@ -82,6 +82,8 @@
 *  * Brett Vickers (https://github.com/beevik)
 *  * Dominik Okwieka (https://github.com/okitec)
 *  * Remko Tron\u00e7on (https://el-tramo.be)
+*  * Romero Malaquias (rbsm@ic.ufal.br)
+*  * Michael Drake <michael.drake@codethink.co.uk>
 *
 *  Other contributions
 *  ===================
@@ -179,6 +181,431 @@
  *  dependencies.
  */
 
+/* #include duk_dblunion.h */
+#line 1 "duk_dblunion.h"
+/*
+ *  Union to access IEEE double memory representation, indexes for double
+ *  memory representation, and some macros for double manipulation.
+ *
+ *  Also used by packed duk_tval.  Use a union for bit manipulation to
+ *  minimize aliasing issues in practice.  The C99 standard does not
+ *  guarantee that this should work, but it's a very widely supported
+ *  practice for low level manipulation.
+ *
+ *  IEEE double format summary:
+ *
+ *    seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
+ *       A        B        C        D        E        F        G        H
+ *
+ *    s       sign bit
+ *    eee...  exponent field
+ *    fff...  fraction
+ *
+ *  See http://en.wikipedia.org/wiki/Double_precision_floating-point_format.
+ *
+ *  NaNs are represented as exponent 0x7ff and mantissa != 0.  The NaN is a
+ *  signaling NaN when the highest bit of the mantissa is zero, and a quiet
+ *  NaN when the highest bit is set.
+ *
+ *  At least three memory layouts are relevant here:
+ *
+ *    A B C D E F G H    Big endian (e.g. 68k)           DUK_USE_DOUBLE_BE
+ *    H G F E D C B A    Little endian (e.g. x86)        DUK_USE_DOUBLE_LE
+ *    D C B A H G F E    Mixed/cross endian (e.g. ARM)   DUK_USE_DOUBLE_ME
+ *
+ *  ARM is a special case: ARM double values are in mixed/cross endian
+ *  format while ARM duk_uint64_t values are in standard little endian
+ *  format (H G F E D C B A).  When a double is read as a duk_uint64_t
+ *  from memory, the register will contain the (logical) value
+ *  E F G H A B C D.  This requires some special handling below.
+ *
+ *  Indexes of various types (8-bit, 16-bit, 32-bit) in memory relative to
+ *  the logical (big endian) order:
+ *
+ *  byte order      duk_uint8_t    duk_uint16_t     duk_uint32_t
+ *    BE             01234567         0123               01
+ *    LE             76543210         3210               10
+ *    ME (ARM)       32107654         1032               01
+ *
+ *  Some processors may alter NaN values in a floating point load+store.
+ *  For instance, on X86 a FLD + FSTP may convert a signaling NaN to a
+ *  quiet one.  This is catastrophic when NaN space is used in packed
+ *  duk_tval values.  See: misc/clang_aliasing.c.
+ */
+
+#if !defined(DUK_DBLUNION_H_INCLUDED)
+#define DUK_DBLUNION_H_INCLUDED
+
+/*
+ *  Union for accessing double parts, also serves as packed duk_tval
+ */
+
+union duk_double_union {
+	double d;
+	float f[2];
+#if defined(DUK_USE_64BIT_OPS)
+	duk_uint64_t ull[1];
+#endif
+	duk_uint32_t ui[2];
+	duk_uint16_t us[4];
+	duk_uint8_t uc[8];
+#if defined(DUK_USE_PACKED_TVAL)
+	void *vp[2];  /* used by packed duk_tval, assumes sizeof(void *) == 4 */
+#endif
+};
+
+typedef union duk_double_union duk_double_union;
+
+/*
+ *  Indexes of various types with respect to big endian (logical) layout
+ */
+
+#if defined(DUK_USE_DOUBLE_LE)
+#if defined(DUK_USE_64BIT_OPS)
+#define DUK_DBL_IDX_ULL0   0
+#endif
+#define DUK_DBL_IDX_UI0    1
+#define DUK_DBL_IDX_UI1    0
+#define DUK_DBL_IDX_US0    3
+#define DUK_DBL_IDX_US1    2
+#define DUK_DBL_IDX_US2    1
+#define DUK_DBL_IDX_US3    0
+#define DUK_DBL_IDX_UC0    7
+#define DUK_DBL_IDX_UC1    6
+#define DUK_DBL_IDX_UC2    5
+#define DUK_DBL_IDX_UC3    4
+#define DUK_DBL_IDX_UC4    3
+#define DUK_DBL_IDX_UC5    2
+#define DUK_DBL_IDX_UC6    1
+#define DUK_DBL_IDX_UC7    0
+#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
+#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
+#elif defined(DUK_USE_DOUBLE_BE)
+#if defined(DUK_USE_64BIT_OPS)
+#define DUK_DBL_IDX_ULL0   0
+#endif
+#define DUK_DBL_IDX_UI0    0
+#define DUK_DBL_IDX_UI1    1
+#define DUK_DBL_IDX_US0    0
+#define DUK_DBL_IDX_US1    1
+#define DUK_DBL_IDX_US2    2
+#define DUK_DBL_IDX_US3    3
+#define DUK_DBL_IDX_UC0    0
+#define DUK_DBL_IDX_UC1    1
+#define DUK_DBL_IDX_UC2    2
+#define DUK_DBL_IDX_UC3    3
+#define DUK_DBL_IDX_UC4    4
+#define DUK_DBL_IDX_UC5    5
+#define DUK_DBL_IDX_UC6    6
+#define DUK_DBL_IDX_UC7    7
+#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
+#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
+#elif defined(DUK_USE_DOUBLE_ME)
+#if defined(DUK_USE_64BIT_OPS)
+#define DUK_DBL_IDX_ULL0   0  /* not directly applicable, byte order differs from a double */
+#endif
+#define DUK_DBL_IDX_UI0    0
+#define DUK_DBL_IDX_UI1    1
+#define DUK_DBL_IDX_US0    1
+#define DUK_DBL_IDX_US1    0
+#define DUK_DBL_IDX_US2    3
+#define DUK_DBL_IDX_US3    2
+#define DUK_DBL_IDX_UC0    3
+#define DUK_DBL_IDX_UC1    2
+#define DUK_DBL_IDX_UC2    1
+#define DUK_DBL_IDX_UC3    0
+#define DUK_DBL_IDX_UC4    7
+#define DUK_DBL_IDX_UC5    6
+#define DUK_DBL_IDX_UC6    5
+#define DUK_DBL_IDX_UC7    4
+#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
+#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
+#else
+#error internal error
+#endif
+
+/*
+ *  Helper macros for reading/writing memory representation parts, used
+ *  by duk_numconv.c and duk_tval.h.
+ */
+
+#define DUK_DBLUNION_SET_DOUBLE(u,v)  do {  \
+		(u)->d = (v); \
+	} while (0)
+
+#define DUK_DBLUNION_SET_HIGH32(u,v)  do {  \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \
+	} while (0)
+
+#if defined(DUK_USE_64BIT_OPS)
+#if defined(DUK_USE_DOUBLE_ME)
+#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \
+	} while (0)
+#else
+#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = ((duk_uint64_t) (v)) << 32; \
+	} while (0)
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0; \
+	} while (0)
+#endif  /* DUK_USE_64BIT_OPS */
+
+#define DUK_DBLUNION_SET_LOW32(u,v)  do {  \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \
+	} while (0)
+
+#define DUK_DBLUNION_GET_DOUBLE(u)  ((u)->d)
+#define DUK_DBLUNION_GET_HIGH32(u)  ((u)->ui[DUK_DBL_IDX_UI0])
+#define DUK_DBLUNION_GET_LOW32(u)   ((u)->ui[DUK_DBL_IDX_UI1])
+
+#if defined(DUK_USE_64BIT_OPS)
+#if defined(DUK_USE_DOUBLE_ME)
+#define DUK_DBLUNION_SET_UINT64(u,v)  do { \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) ((v) >> 32); \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \
+	} while (0)
+#define DUK_DBLUNION_GET_UINT64(u) \
+	((((duk_uint64_t) (u)->ui[DUK_DBL_IDX_UI0]) << 32) | \
+	 ((duk_uint64_t) (u)->ui[DUK_DBL_IDX_UI1]))
+#else
+#define DUK_DBLUNION_SET_UINT64(u,v)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \
+	} while (0)
+#define DUK_DBLUNION_GET_UINT64(u)  ((u)->ull[DUK_DBL_IDX_ULL0])
+#endif
+#define DUK_DBLUNION_SET_INT64(u,v) DUK_DBLUNION_SET_UINT64((u), (duk_uint64_t) (v))
+#define DUK_DBLUNION_GET_INT64(u)   ((duk_int64_t) DUK_DBLUNION_GET_UINT64((u)))
+#endif  /* DUK_USE_64BIT_OPS */
+
+/*
+ *  Double NaN manipulation macros related to NaN normalization needed when
+ *  using the packed duk_tval representation.  NaN normalization is necessary
+ *  to keep double values compatible with the duk_tval format.
+ *
+ *  When packed duk_tval is used, the NaN space is used to store pointers
+ *  and other tagged values in addition to NaNs.  Actual NaNs are normalized
+ *  to a specific quiet NaN.  The macros below are used by the implementation
+ *  to check and normalize NaN values when they might be created.  The macros
+ *  are essentially NOPs when the non-packed duk_tval representation is used.
+ *
+ *  A FULL check is exact and checks all bits.  A NOTFULL check is used by
+ *  the packed duk_tval and works correctly for all NaNs except those that
+ *  begin with 0x7ff0.  Since the 'normalized NaN' values used with packed
+ *  duk_tval begin with 0x7ff8, the partial check is reliable when packed
+ *  duk_tval is used.  The 0x7ff8 prefix means the normalized NaN will be a
+ *  quiet NaN regardless of its remaining lower bits.
+ *
+ *  The ME variant below is specifically for ARM byte order, which has the
+ *  feature that while doubles have a mixed byte order (32107654), unsigned
+ *  long long values has a little endian byte order (76543210).  When writing
+ *  a logical double value through a ULL pointer, the 32-bit words need to be
+ *  swapped; hence the #if defined()s below for ULL writes with DUK_USE_DOUBLE_ME.
+ *  This is not full ARM support but suffices for some environments.
+ */
+
+#if defined(DUK_USE_64BIT_OPS)
+#if defined(DUK_USE_DOUBLE_ME)
+/* Macros for 64-bit ops + mixed endian doubles. */
+#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = 0x000000007ff80000ULL; \
+	} while (0)
+#define DUK__DBLUNION_IS_NAN_FULL(u) \
+	((((u)->ull[DUK_DBL_IDX_ULL0] & 0x000000007ff00000ULL) == 0x000000007ff00000ULL) && \
+	 ((((u)->ull[DUK_DBL_IDX_ULL0]) & 0xffffffff000fffffULL) != 0))
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x000000007ff80000ULL)
+#define DUK__DBLUNION_IS_ANYINF(u) \
+	(((u)->ull[DUK_DBL_IDX_ULL0] & 0xffffffff7fffffffULL) == 0x000000007ff00000ULL)
+#define DUK__DBLUNION_IS_POSINF(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x000000007ff00000ULL)
+#define DUK__DBLUNION_IS_NEGINF(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x00000000fff00000ULL)
+#define DUK__DBLUNION_IS_ANYZERO(u) \
+	(((u)->ull[DUK_DBL_IDX_ULL0] & 0xffffffff7fffffffULL) == 0x0000000000000000ULL)
+#define DUK__DBLUNION_IS_POSZERO(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x0000000000000000ULL)
+#define DUK__DBLUNION_IS_NEGZERO(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x0000000080000000ULL)
+#else
+/* Macros for 64-bit ops + big/little endian doubles. */
+#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = 0x7ff8000000000000ULL; \
+	} while (0)
+#define DUK__DBLUNION_IS_NAN_FULL(u) \
+	((((u)->ull[DUK_DBL_IDX_ULL0] & 0x7ff0000000000000ULL) == 0x7ff0000000000000UL) && \
+	 ((((u)->ull[DUK_DBL_IDX_ULL0]) & 0x000fffffffffffffULL) != 0))
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x7ff8000000000000ULL)
+#define DUK__DBLUNION_IS_ANYINF(u) \
+	(((u)->ull[DUK_DBL_IDX_ULL0] & 0x7fffffffffffffffULL) == 0x7ff0000000000000ULL)
+#define DUK__DBLUNION_IS_POSINF(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x7ff0000000000000ULL)
+#define DUK__DBLUNION_IS_NEGINF(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0xfff0000000000000ULL)
+#define DUK__DBLUNION_IS_ANYZERO(u) \
+	(((u)->ull[DUK_DBL_IDX_ULL0] & 0x7fffffffffffffffULL) == 0x0000000000000000ULL)
+#define DUK__DBLUNION_IS_POSZERO(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x0000000000000000ULL)
+#define DUK__DBLUNION_IS_NEGZERO(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x8000000000000000ULL)
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+/* Macros for no 64-bit ops, any endianness. */
+#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) 0x7ff80000UL; \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0x00000000UL; \
+	} while (0)
+#define DUK__DBLUNION_IS_NAN_FULL(u) \
+	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7ff00000UL) == 0x7ff00000UL) && \
+	 (((u)->ui[DUK_DBL_IDX_UI0] & 0x000fffffUL) != 0 || \
+          (u)->ui[DUK_DBL_IDX_UI1] != 0))
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
+	(((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff80000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#define DUK__DBLUNION_IS_ANYINF(u) \
+	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7fffffffUL) == 0x7ff00000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#define DUK__DBLUNION_IS_POSINF(u) \
+	(((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff00000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#define DUK__DBLUNION_IS_NEGINF(u) \
+	(((u)->ui[DUK_DBL_IDX_UI0] == 0xfff00000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#define DUK__DBLUNION_IS_ANYZERO(u) \
+	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7fffffffUL) == 0x00000000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#define DUK__DBLUNION_IS_POSZERO(u) \
+	(((u)->ui[DUK_DBL_IDX_UI0] == 0x00000000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#define DUK__DBLUNION_IS_NEGZERO(u) \
+	(((u)->ui[DUK_DBL_IDX_UI0] == 0x80000000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#endif  /* DUK_USE_64BIT_OPS */
+
+#define DUK__DBLUNION_SET_NAN_NOTFULL(u)  do { \
+		(u)->us[DUK_DBL_IDX_US0] = 0x7ff8UL; \
+	} while (0)
+
+#define DUK__DBLUNION_IS_NAN_NOTFULL(u) \
+	/* E == 0x7ff, topmost four bits of F != 0 => assume NaN */ \
+	((((u)->us[DUK_DBL_IDX_US0] & 0x7ff0UL) == 0x7ff0UL) && \
+	 (((u)->us[DUK_DBL_IDX_US0] & 0x000fUL) != 0x0000UL))
+
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL(u) \
+	/* E == 0x7ff, F == 8 => normalized NaN */ \
+	((u)->us[DUK_DBL_IDX_US0] == 0x7ff8UL)
+
+#define DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL(u)  do { \
+		if (DUK__DBLUNION_IS_NAN_FULL((u))) { \
+			DUK__DBLUNION_SET_NAN_FULL((u)); \
+		} \
+	} while (0)
+
+#define DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL(u)  do { \
+		if (DUK__DBLUNION_IS_NAN_NOTFULL((u))) { \
+			DUK__DBLUNION_SET_NAN_NOTFULL((u)); \
+		} \
+	} while (0)
+
+/* Concrete macros for NaN handling used by the implementation internals.
+ * Chosen so that they match the duk_tval representation: with a packed
+ * duk_tval, ensure NaNs are properly normalized; with a non-packed duk_tval
+ * these are essentially NOPs.
+ */
+
+#if defined(DUK_USE_PACKED_TVAL)
+#if defined(DUK_USE_FULL_TVAL)
+#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL((u))
+#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_FULL((u))
+#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NORMALIZED_NAN_FULL((u))
+#define DUK_DBLUNION_SET_NAN(d)              DUK__DBLUNION_SET_NAN_FULL((d))
+#else
+#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL((u))
+#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_NOTFULL((u))
+#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL((u))
+#define DUK_DBLUNION_SET_NAN(d)              DUK__DBLUNION_SET_NAN_NOTFULL((d))
+#endif
+#define DUK_DBLUNION_IS_NORMALIZED(u) \
+	(!DUK_DBLUNION_IS_NAN((u)) ||  /* either not a NaN */ \
+	 DUK_DBLUNION_IS_NORMALIZED_NAN((u)))  /* or is a normalized NaN */
+#else  /* DUK_USE_PACKED_TVAL */
+#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  /* nop: no need to normalize */
+#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_FULL((u))  /* (DUK_ISNAN((u)->d)) */
+#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NAN_FULL((u))  /* (DUK_ISNAN((u)->d)) */
+#define DUK_DBLUNION_IS_NORMALIZED(u)        1  /* all doubles are considered normalized */
+#define DUK_DBLUNION_SET_NAN(u)  do { \
+		/* in non-packed representation we don't care about which NaN is used */ \
+		(u)->d = DUK_DOUBLE_NAN; \
+	} while (0)
+#endif  /* DUK_USE_PACKED_TVAL */
+
+#define DUK_DBLUNION_IS_ANYINF(u) DUK__DBLUNION_IS_ANYINF((u))
+#define DUK_DBLUNION_IS_POSINF(u) DUK__DBLUNION_IS_POSINF((u))
+#define DUK_DBLUNION_IS_NEGINF(u) DUK__DBLUNION_IS_NEGINF((u))
+
+#define DUK_DBLUNION_IS_ANYZERO(u) DUK__DBLUNION_IS_ANYZERO((u))
+#define DUK_DBLUNION_IS_POSZERO(u) DUK__DBLUNION_IS_POSZERO((u))
+#define DUK_DBLUNION_IS_NEGZERO(u) DUK__DBLUNION_IS_NEGZERO((u))
+
+/* XXX: native 64-bit byteswaps when available */
+
+/* 64-bit byteswap, same operation independent of target endianness. */
+#define DUK_DBLUNION_BSWAP64(u) do { \
+		duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \
+		duk__bswaptmp1 = (u)->ui[0]; \
+		duk__bswaptmp2 = (u)->ui[1]; \
+		duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \
+		duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \
+		(u)->ui[0] = duk__bswaptmp2; \
+		(u)->ui[1] = duk__bswaptmp1; \
+	} while (0)
+
+/* Byteswap an IEEE double in the duk_double_union from host to network
+ * order.  For a big endian target this is a no-op.
+ */
+#if defined(DUK_USE_DOUBLE_LE)
+#define DUK_DBLUNION_DOUBLE_HTON(u) do { \
+		duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \
+		duk__bswaptmp1 = (u)->ui[0]; \
+		duk__bswaptmp2 = (u)->ui[1]; \
+		duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \
+		duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \
+		(u)->ui[0] = duk__bswaptmp2; \
+		(u)->ui[1] = duk__bswaptmp1; \
+	} while (0)
+#elif defined(DUK_USE_DOUBLE_ME)
+#define DUK_DBLUNION_DOUBLE_HTON(u) do { \
+		duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \
+		duk__bswaptmp1 = (u)->ui[0]; \
+		duk__bswaptmp2 = (u)->ui[1]; \
+		duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \
+		duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \
+		(u)->ui[0] = duk__bswaptmp1; \
+		(u)->ui[1] = duk__bswaptmp2; \
+	} while (0)
+#elif defined(DUK_USE_DOUBLE_BE)
+#define DUK_DBLUNION_DOUBLE_HTON(u) do { } while (0)
+#else
+#error internal error, double endianness insane
+#endif
+
+/* Reverse operation is the same. */
+#define DUK_DBLUNION_DOUBLE_NTOH(u) DUK_DBLUNION_DOUBLE_HTON((u))
+
+/* Some sign bit helpers. */
+#if defined(DUK_USE_64BIT_OPS)
+#define DUK_DBLUNION_HAS_SIGNBIT(u) (((u)->ull[DUK_DBL_IDX_ULL0] & 0x8000000000000000ULL) != 0)
+#define DUK_DBLUNION_GET_SIGNBIT(u) (((u)->ull[DUK_DBL_IDX_ULL0] >> 63U))
+#else
+#define DUK_DBLUNION_HAS_SIGNBIT(u) (((u)->ui[DUK_DBL_IDX_UI0] & 0x80000000UL) != 0)
+#define DUK_DBLUNION_GET_SIGNBIT(u) (((u)->ui[DUK_DBL_IDX_UI0] >> 31U))
+#endif
+
+#endif  /* DUK_DBLUNION_H_INCLUDED */
 /* #include duk_replacements.h */
 #line 1 "duk_replacements.h"
 #if !defined(DUK_REPLACEMENTS_H_INCLUDED)
@@ -286,6 +713,8 @@
 struct duk_hnatfunc;
 struct duk_hthread;
 struct duk_hbufobj;
+struct duk_hdecenv;
+struct duk_hobjenv;
 struct duk_hbuffer;
 struct duk_hbuffer_fixed;
 struct duk_hbuffer_dynamic;
@@ -340,8 +769,10 @@
 typedef struct duk_hobject duk_hobject;
 typedef struct duk_hcompfunc duk_hcompfunc;
 typedef struct duk_hnatfunc duk_hnatfunc;
+typedef struct duk_hthread duk_hthread;
 typedef struct duk_hbufobj duk_hbufobj;
-typedef struct duk_hthread duk_hthread;
+typedef struct duk_hdecenv duk_hdecenv;
+typedef struct duk_hobjenv duk_hobjenv;
 typedef struct duk_hbuffer duk_hbuffer;
 typedef struct duk_hbuffer_fixed duk_hbuffer_fixed;
 typedef struct duk_hbuffer_dynamic duk_hbuffer_dynamic;
@@ -1316,233 +1747,224 @@
 #define DUK_STRIDX_INT_PC2LINE                                        95                             /* '\xffPc2line' */
 #define DUK_HEAP_STRING_INT_PC2LINE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_PC2LINE)
 #define DUK_HTHREAD_STRING_INT_PC2LINE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_PC2LINE)
-#define DUK_STRIDX_INT_ARGS                                           96                             /* '\xffArgs' */
+#define DUK_STRIDX_INT_THIS                                           96                             /* '\xffThis' */
+#define DUK_HEAP_STRING_INT_THIS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THIS)
+#define DUK_HTHREAD_STRING_INT_THIS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THIS)
+#define DUK_STRIDX_INT_ARGS                                           97                             /* '\xffArgs' */
 #define DUK_HEAP_STRING_INT_ARGS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_ARGS)
 #define DUK_HTHREAD_STRING_INT_ARGS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_ARGS)
-#define DUK_STRIDX_INT_MAP                                            97                             /* '\xffMap' */
+#define DUK_STRIDX_INT_MAP                                            98                             /* '\xffMap' */
 #define DUK_HEAP_STRING_INT_MAP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_MAP)
 #define DUK_HTHREAD_STRING_INT_MAP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_MAP)
-#define DUK_STRIDX_INT_VARENV                                         98                             /* '\xffVarenv' */
+#define DUK_STRIDX_INT_VARENV                                         99                             /* '\xffVarenv' */
 #define DUK_HEAP_STRING_INT_VARENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARENV)
 #define DUK_HTHREAD_STRING_INT_VARENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARENV)
-#define DUK_STRIDX_INT_FINALIZER                                      99                             /* '\xffFinalizer' */
+#define DUK_STRIDX_INT_FINALIZER                                      100                            /* '\xffFinalizer' */
 #define DUK_HEAP_STRING_INT_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FINALIZER)
 #define DUK_HTHREAD_STRING_INT_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FINALIZER)
-#define DUK_STRIDX_INT_HANDLER                                        100                            /* '\xffHandler' */
+#define DUK_STRIDX_INT_TARGET                                         101                            /* '\xffTarget' */
+#define DUK_HEAP_STRING_INT_TARGET(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET)
+#define DUK_HTHREAD_STRING_INT_TARGET(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET)
+#define DUK_STRIDX_INT_HANDLER                                        102                            /* '\xffHandler' */
 #define DUK_HEAP_STRING_INT_HANDLER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_HANDLER)
 #define DUK_HTHREAD_STRING_INT_HANDLER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_HANDLER)
-#define DUK_STRIDX_INT_CALLEE                                         101                            /* '\xffCallee' */
-#define DUK_HEAP_STRING_INT_CALLEE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_CALLEE)
-#define DUK_HTHREAD_STRING_INT_CALLEE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_CALLEE)
-#define DUK_STRIDX_INT_THREAD                                         102                            /* '\xffThread' */
-#define DUK_HEAP_STRING_INT_THREAD(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THREAD)
-#define DUK_HTHREAD_STRING_INT_THREAD(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THREAD)
-#define DUK_STRIDX_INT_REGBASE                                        103                            /* '\xffRegbase' */
-#define DUK_HEAP_STRING_INT_REGBASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_REGBASE)
-#define DUK_HTHREAD_STRING_INT_REGBASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_REGBASE)
-#define DUK_STRIDX_INT_TARGET                                         104                            /* '\xffTarget' */
-#define DUK_HEAP_STRING_INT_TARGET(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET)
-#define DUK_HTHREAD_STRING_INT_TARGET(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET)
-#define DUK_STRIDX_INT_THIS                                           105                            /* '\xffThis' */
-#define DUK_HEAP_STRING_INT_THIS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THIS)
-#define DUK_HTHREAD_STRING_INT_THIS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THIS)
-#define DUK_STRIDX_COMPILE                                            106                            /* 'compile' */
+#define DUK_STRIDX_COMPILE                                            103                            /* 'compile' */
 #define DUK_HEAP_STRING_COMPILE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPILE)
 #define DUK_HTHREAD_STRING_COMPILE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPILE)
-#define DUK_STRIDX_INPUT                                              107                            /* 'input' */
+#define DUK_STRIDX_INPUT                                              104                            /* 'input' */
 #define DUK_HEAP_STRING_INPUT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INPUT)
 #define DUK_HTHREAD_STRING_INPUT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INPUT)
-#define DUK_STRIDX_ERR_CREATE                                         108                            /* 'errCreate' */
+#define DUK_STRIDX_ERR_CREATE                                         105                            /* 'errCreate' */
 #define DUK_HEAP_STRING_ERR_CREATE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_CREATE)
 #define DUK_HTHREAD_STRING_ERR_CREATE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_CREATE)
-#define DUK_STRIDX_ERR_THROW                                          109                            /* 'errThrow' */
+#define DUK_STRIDX_ERR_THROW                                          106                            /* 'errThrow' */
 #define DUK_HEAP_STRING_ERR_THROW(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_THROW)
 #define DUK_HTHREAD_STRING_ERR_THROW(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_THROW)
-#define DUK_STRIDX_ENV                                                110                            /* 'env' */
+#define DUK_STRIDX_ENV                                                107                            /* 'env' */
 #define DUK_HEAP_STRING_ENV(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENV)
 #define DUK_HTHREAD_STRING_ENV(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENV)
-#define DUK_STRIDX_HEX                                                111                            /* 'hex' */
+#define DUK_STRIDX_HEX                                                108                            /* 'hex' */
 #define DUK_HEAP_STRING_HEX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HEX)
 #define DUK_HTHREAD_STRING_HEX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HEX)
-#define DUK_STRIDX_BASE64                                             112                            /* 'base64' */
+#define DUK_STRIDX_BASE64                                             109                            /* 'base64' */
 #define DUK_HEAP_STRING_BASE64(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BASE64)
 #define DUK_HTHREAD_STRING_BASE64(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BASE64)
-#define DUK_STRIDX_JX                                                 113                            /* 'jx' */
+#define DUK_STRIDX_JX                                                 110                            /* 'jx' */
 #define DUK_HEAP_STRING_JX(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JX)
 #define DUK_HTHREAD_STRING_JX(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JX)
-#define DUK_STRIDX_JC                                                 114                            /* 'jc' */
+#define DUK_STRIDX_JC                                                 111                            /* 'jc' */
 #define DUK_HEAP_STRING_JC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JC)
 #define DUK_HTHREAD_STRING_JC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JC)
-#define DUK_STRIDX_RESUME                                             115                            /* 'resume' */
+#define DUK_STRIDX_RESUME                                             112                            /* 'resume' */
 #define DUK_HEAP_STRING_RESUME(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RESUME)
 #define DUK_HTHREAD_STRING_RESUME(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RESUME)
-#define DUK_STRIDX_JSON_EXT_UNDEFINED                                 116                            /* '{"_undef":true}' */
+#define DUK_STRIDX_JSON_EXT_UNDEFINED                                 113                            /* '{"_undef":true}' */
 #define DUK_HEAP_STRING_JSON_EXT_UNDEFINED(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_UNDEFINED)
 #define DUK_HTHREAD_STRING_JSON_EXT_UNDEFINED(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_UNDEFINED)
-#define DUK_STRIDX_JSON_EXT_NAN                                       117                            /* '{"_nan":true}' */
+#define DUK_STRIDX_JSON_EXT_NAN                                       114                            /* '{"_nan":true}' */
 #define DUK_HEAP_STRING_JSON_EXT_NAN(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NAN)
 #define DUK_HTHREAD_STRING_JSON_EXT_NAN(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NAN)
-#define DUK_STRIDX_JSON_EXT_POSINF                                    118                            /* '{"_inf":true}' */
+#define DUK_STRIDX_JSON_EXT_POSINF                                    115                            /* '{"_inf":true}' */
 #define DUK_HEAP_STRING_JSON_EXT_POSINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_POSINF)
 #define DUK_HTHREAD_STRING_JSON_EXT_POSINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_POSINF)
-#define DUK_STRIDX_JSON_EXT_NEGINF                                    119                            /* '{"_ninf":true}' */
+#define DUK_STRIDX_JSON_EXT_NEGINF                                    116                            /* '{"_ninf":true}' */
 #define DUK_HEAP_STRING_JSON_EXT_NEGINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NEGINF)
 #define DUK_HTHREAD_STRING_JSON_EXT_NEGINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NEGINF)
-#define DUK_STRIDX_JSON_EXT_FUNCTION1                                 120                            /* '{"_func":true}' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION1                                 117                            /* '{"_func":true}' */
 #define DUK_HEAP_STRING_JSON_EXT_FUNCTION1(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION1)
 #define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION1(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION1)
-#define DUK_STRIDX_JSON_EXT_FUNCTION2                                 121                            /* '{_func:true}' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION2                                 118                            /* '{_func:true}' */
 #define DUK_HEAP_STRING_JSON_EXT_FUNCTION2(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION2)
 #define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION2(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION2)
-#define DUK_STRIDX_BREAK                                              122                            /* 'break' */
+#define DUK_STRIDX_BREAK                                              119                            /* 'break' */
 #define DUK_HEAP_STRING_BREAK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BREAK)
 #define DUK_HTHREAD_STRING_BREAK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BREAK)
-#define DUK_STRIDX_CASE                                               123                            /* 'case' */
+#define DUK_STRIDX_CASE                                               120                            /* 'case' */
 #define DUK_HEAP_STRING_CASE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CASE)
 #define DUK_HTHREAD_STRING_CASE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CASE)
-#define DUK_STRIDX_CATCH                                              124                            /* 'catch' */
+#define DUK_STRIDX_CATCH                                              121                            /* 'catch' */
 #define DUK_HEAP_STRING_CATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CATCH)
 #define DUK_HTHREAD_STRING_CATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CATCH)
-#define DUK_STRIDX_CONTINUE                                           125                            /* 'continue' */
+#define DUK_STRIDX_CONTINUE                                           122                            /* 'continue' */
 #define DUK_HEAP_STRING_CONTINUE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONTINUE)
 #define DUK_HTHREAD_STRING_CONTINUE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONTINUE)
-#define DUK_STRIDX_DEBUGGER                                           126                            /* 'debugger' */
+#define DUK_STRIDX_DEBUGGER                                           123                            /* 'debugger' */
 #define DUK_HEAP_STRING_DEBUGGER(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEBUGGER)
 #define DUK_HTHREAD_STRING_DEBUGGER(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEBUGGER)
-#define DUK_STRIDX_DEFAULT                                            127                            /* 'default' */
+#define DUK_STRIDX_DEFAULT                                            124                            /* 'default' */
 #define DUK_HEAP_STRING_DEFAULT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFAULT)
 #define DUK_HTHREAD_STRING_DEFAULT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFAULT)
-#define DUK_STRIDX_DELETE                                             128                            /* 'delete' */
+#define DUK_STRIDX_DELETE                                             125                            /* 'delete' */
 #define DUK_HEAP_STRING_DELETE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE)
 #define DUK_HTHREAD_STRING_DELETE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE)
-#define DUK_STRIDX_DO                                                 129                            /* 'do' */
+#define DUK_STRIDX_DO                                                 126                            /* 'do' */
 #define DUK_HEAP_STRING_DO(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DO)
 #define DUK_HTHREAD_STRING_DO(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DO)
-#define DUK_STRIDX_ELSE                                               130                            /* 'else' */
+#define DUK_STRIDX_ELSE                                               127                            /* 'else' */
 #define DUK_HEAP_STRING_ELSE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ELSE)
 #define DUK_HTHREAD_STRING_ELSE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ELSE)
-#define DUK_STRIDX_FINALLY                                            131                            /* 'finally' */
+#define DUK_STRIDX_FINALLY                                            128                            /* 'finally' */
 #define DUK_HEAP_STRING_FINALLY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FINALLY)
 #define DUK_HTHREAD_STRING_FINALLY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FINALLY)
-#define DUK_STRIDX_FOR                                                132                            /* 'for' */
+#define DUK_STRIDX_FOR                                                129                            /* 'for' */
 #define DUK_HEAP_STRING_FOR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR)
 #define DUK_HTHREAD_STRING_FOR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR)
-#define DUK_STRIDX_LC_FUNCTION                                        133                            /* 'function' */
+#define DUK_STRIDX_LC_FUNCTION                                        130                            /* 'function' */
 #define DUK_HEAP_STRING_LC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FUNCTION)
 #define DUK_HTHREAD_STRING_LC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FUNCTION)
-#define DUK_STRIDX_IF                                                 134                            /* 'if' */
+#define DUK_STRIDX_IF                                                 131                            /* 'if' */
 #define DUK_HEAP_STRING_IF(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IF)
 #define DUK_HTHREAD_STRING_IF(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IF)
-#define DUK_STRIDX_IN                                                 135                            /* 'in' */
+#define DUK_STRIDX_IN                                                 132                            /* 'in' */
 #define DUK_HEAP_STRING_IN(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IN)
 #define DUK_HTHREAD_STRING_IN(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IN)
-#define DUK_STRIDX_INSTANCEOF                                         136                            /* 'instanceof' */
+#define DUK_STRIDX_INSTANCEOF                                         133                            /* 'instanceof' */
 #define DUK_HEAP_STRING_INSTANCEOF(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INSTANCEOF)
 #define DUK_HTHREAD_STRING_INSTANCEOF(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INSTANCEOF)
-#define DUK_STRIDX_NEW                                                137                            /* 'new' */
+#define DUK_STRIDX_NEW                                                134                            /* 'new' */
 #define DUK_HEAP_STRING_NEW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEW)
 #define DUK_HTHREAD_STRING_NEW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEW)
-#define DUK_STRIDX_RETURN                                             138                            /* 'return' */
+#define DUK_STRIDX_RETURN                                             135                            /* 'return' */
 #define DUK_HEAP_STRING_RETURN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RETURN)
 #define DUK_HTHREAD_STRING_RETURN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RETURN)
-#define DUK_STRIDX_SWITCH                                             139                            /* 'switch' */
+#define DUK_STRIDX_SWITCH                                             136                            /* 'switch' */
 #define DUK_HEAP_STRING_SWITCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SWITCH)
 #define DUK_HTHREAD_STRING_SWITCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SWITCH)
-#define DUK_STRIDX_THIS                                               140                            /* 'this' */
+#define DUK_STRIDX_THIS                                               137                            /* 'this' */
 #define DUK_HEAP_STRING_THIS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THIS)
 #define DUK_HTHREAD_STRING_THIS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THIS)
-#define DUK_STRIDX_THROW                                              141                            /* 'throw' */
+#define DUK_STRIDX_THROW                                              138                            /* 'throw' */
 #define DUK_HEAP_STRING_THROW(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW)
 #define DUK_HTHREAD_STRING_THROW(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW)
-#define DUK_STRIDX_TRY                                                142                            /* 'try' */
+#define DUK_STRIDX_TRY                                                139                            /* 'try' */
 #define DUK_HEAP_STRING_TRY(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRY)
 #define DUK_HTHREAD_STRING_TRY(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRY)
-#define DUK_STRIDX_TYPEOF                                             143                            /* 'typeof' */
+#define DUK_STRIDX_TYPEOF                                             140                            /* 'typeof' */
 #define DUK_HEAP_STRING_TYPEOF(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPEOF)
 #define DUK_HTHREAD_STRING_TYPEOF(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPEOF)
-#define DUK_STRIDX_VAR                                                144                            /* 'var' */
+#define DUK_STRIDX_VAR                                                141                            /* 'var' */
 #define DUK_HEAP_STRING_VAR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VAR)
 #define DUK_HTHREAD_STRING_VAR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VAR)
-#define DUK_STRIDX_CONST                                              145                            /* 'const' */
+#define DUK_STRIDX_CONST                                              142                            /* 'const' */
 #define DUK_HEAP_STRING_CONST(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONST)
 #define DUK_HTHREAD_STRING_CONST(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONST)
-#define DUK_STRIDX_VOID                                               146                            /* 'void' */
+#define DUK_STRIDX_VOID                                               143                            /* 'void' */
 #define DUK_HEAP_STRING_VOID(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VOID)
 #define DUK_HTHREAD_STRING_VOID(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VOID)
-#define DUK_STRIDX_WHILE                                              147                            /* 'while' */
+#define DUK_STRIDX_WHILE                                              144                            /* 'while' */
 #define DUK_HEAP_STRING_WHILE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WHILE)
 #define DUK_HTHREAD_STRING_WHILE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WHILE)
-#define DUK_STRIDX_WITH                                               148                            /* 'with' */
+#define DUK_STRIDX_WITH                                               145                            /* 'with' */
 #define DUK_HEAP_STRING_WITH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WITH)
 #define DUK_HTHREAD_STRING_WITH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WITH)
-#define DUK_STRIDX_CLASS                                              149                            /* 'class' */
+#define DUK_STRIDX_CLASS                                              146                            /* 'class' */
 #define DUK_HEAP_STRING_CLASS(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLASS)
 #define DUK_HTHREAD_STRING_CLASS(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLASS)
-#define DUK_STRIDX_ENUM                                               150                            /* 'enum' */
+#define DUK_STRIDX_ENUM                                               147                            /* 'enum' */
 #define DUK_HEAP_STRING_ENUM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUM)
 #define DUK_HTHREAD_STRING_ENUM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUM)
-#define DUK_STRIDX_EXPORT                                             151                            /* 'export' */
+#define DUK_STRIDX_EXPORT                                             148                            /* 'export' */
 #define DUK_HEAP_STRING_EXPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXPORT)
 #define DUK_HTHREAD_STRING_EXPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXPORT)
-#define DUK_STRIDX_EXTENDS                                            152                            /* 'extends' */
+#define DUK_STRIDX_EXTENDS                                            149                            /* 'extends' */
 #define DUK_HEAP_STRING_EXTENDS(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXTENDS)
 #define DUK_HTHREAD_STRING_EXTENDS(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXTENDS)
-#define DUK_STRIDX_IMPORT                                             153                            /* 'import' */
+#define DUK_STRIDX_IMPORT                                             150                            /* 'import' */
 #define DUK_HEAP_STRING_IMPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPORT)
 #define DUK_HTHREAD_STRING_IMPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPORT)
-#define DUK_STRIDX_SUPER                                              154                            /* 'super' */
+#define DUK_STRIDX_SUPER                                              151                            /* 'super' */
 #define DUK_HEAP_STRING_SUPER(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUPER)
 #define DUK_HTHREAD_STRING_SUPER(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUPER)
-#define DUK_STRIDX_LC_NULL                                            155                            /* 'null' */
+#define DUK_STRIDX_LC_NULL                                            152                            /* 'null' */
 #define DUK_HEAP_STRING_LC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NULL)
 #define DUK_HTHREAD_STRING_LC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NULL)
-#define DUK_STRIDX_TRUE                                               156                            /* 'true' */
+#define DUK_STRIDX_TRUE                                               153                            /* 'true' */
 #define DUK_HEAP_STRING_TRUE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUE)
 #define DUK_HTHREAD_STRING_TRUE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUE)
-#define DUK_STRIDX_FALSE                                              157                            /* 'false' */
+#define DUK_STRIDX_FALSE                                              154                            /* 'false' */
 #define DUK_HEAP_STRING_FALSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FALSE)
 #define DUK_HTHREAD_STRING_FALSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FALSE)
-#define DUK_STRIDX_IMPLEMENTS                                         158                            /* 'implements' */
+#define DUK_STRIDX_IMPLEMENTS                                         155                            /* 'implements' */
 #define DUK_HEAP_STRING_IMPLEMENTS(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPLEMENTS)
 #define DUK_HTHREAD_STRING_IMPLEMENTS(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPLEMENTS)
-#define DUK_STRIDX_INTERFACE                                          159                            /* 'interface' */
+#define DUK_STRIDX_INTERFACE                                          156                            /* 'interface' */
 #define DUK_HEAP_STRING_INTERFACE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INTERFACE)
 #define DUK_HTHREAD_STRING_INTERFACE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INTERFACE)
-#define DUK_STRIDX_LET                                                160                            /* 'let' */
+#define DUK_STRIDX_LET                                                157                            /* 'let' */
 #define DUK_HEAP_STRING_LET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LET)
 #define DUK_HTHREAD_STRING_LET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LET)
-#define DUK_STRIDX_PACKAGE                                            161                            /* 'package' */
+#define DUK_STRIDX_PACKAGE                                            158                            /* 'package' */
 #define DUK_HEAP_STRING_PACKAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PACKAGE)
 #define DUK_HTHREAD_STRING_PACKAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PACKAGE)
-#define DUK_STRIDX_PRIVATE                                            162                            /* 'private' */
+#define DUK_STRIDX_PRIVATE                                            159                            /* 'private' */
 #define DUK_HEAP_STRING_PRIVATE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRIVATE)
 #define DUK_HTHREAD_STRING_PRIVATE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRIVATE)
-#define DUK_STRIDX_PROTECTED                                          163                            /* 'protected' */
+#define DUK_STRIDX_PROTECTED                                          160                            /* 'protected' */
 #define DUK_HEAP_STRING_PROTECTED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTECTED)
 #define DUK_HTHREAD_STRING_PROTECTED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTECTED)
-#define DUK_STRIDX_PUBLIC                                             164                            /* 'public' */
+#define DUK_STRIDX_PUBLIC                                             161                            /* 'public' */
 #define DUK_HEAP_STRING_PUBLIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUBLIC)
 #define DUK_HTHREAD_STRING_PUBLIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUBLIC)
-#define DUK_STRIDX_STATIC                                             165                            /* 'static' */
+#define DUK_STRIDX_STATIC                                             162                            /* 'static' */
 #define DUK_HEAP_STRING_STATIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STATIC)
 #define DUK_HTHREAD_STRING_STATIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STATIC)
-#define DUK_STRIDX_YIELD                                              166                            /* 'yield' */
+#define DUK_STRIDX_YIELD                                              163                            /* 'yield' */
 #define DUK_HEAP_STRING_YIELD(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_YIELD)
 #define DUK_HTHREAD_STRING_YIELD(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_YIELD)
 
-#define DUK_HEAP_NUM_STRINGS                                          167
-#define DUK_STRIDX_START_RESERVED                                     122
-#define DUK_STRIDX_START_STRICT_RESERVED                              158
-#define DUK_STRIDX_END_RESERVED                                       167                            /* exclusive endpoint */
+#define DUK_HEAP_NUM_STRINGS                                          164
+#define DUK_STRIDX_START_RESERVED                                     119
+#define DUK_STRIDX_START_STRICT_RESERVED                              155
+#define DUK_STRIDX_END_RESERVED                                       164                            /* exclusive endpoint */
 
 /* To convert a heap stridx to a token number, subtract
  * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED.
  */
 #if !defined(DUK_SINGLE_FILE)
-DUK_INTERNAL_DECL const duk_uint8_t duk_strings_data[921];
+DUK_INTERNAL_DECL const duk_uint8_t duk_strings_data[903];
 #endif  /* !DUK_SINGLE_FILE */
 #define DUK_STRDATA_MAX_STRLEN                                        17
-#define DUK_STRDATA_DATA_LENGTH                                       921
+#define DUK_STRDATA_DATA_LENGTH                                       903
 #endif  /* DUK_USE_ROM_STRINGS */
 
 #if defined(DUK_USE_ROM_OBJECTS)
@@ -1634,6 +2056,8 @@
 DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_caseconv_shared(duk_context *ctx);
 DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_trim(duk_context *ctx);
 DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_repeat(duk_context *ctx);
+DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_context *ctx);
+DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_includes(duk_context *ctx);
 DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_substr(duk_context *ctx);
 DUK_INTERNAL_DECL duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_context *ctx);
 DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_to_string(duk_context *ctx);
@@ -1712,7 +2136,7 @@
 DUK_INTERNAL_DECL duk_ret_t duk_bi_textdecoder_prototype_shared_getter(duk_context *ctx);
 DUK_INTERNAL_DECL duk_ret_t duk_bi_textdecoder_prototype_decode(duk_context *ctx);
 #if !defined(DUK_SINGLE_FILE)
-DUK_INTERNAL_DECL const duk_c_function duk_bi_native_functions[164];
+DUK_INTERNAL_DECL const duk_c_function duk_bi_native_functions[166];
 #endif  /* !DUK_SINGLE_FILE */
 #define DUK_BIDX_GLOBAL                                               0
 #define DUK_BIDX_GLOBAL_ENV                                           1
@@ -1793,25 +2217,25 @@
 #define DUK_NUM_ALL_BUILTINS                                          74
 #if defined(DUK_USE_DOUBLE_LE)
 #if !defined(DUK_SINGLE_FILE)
-DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[3790];
+DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[3819];
 #endif  /* !DUK_SINGLE_FILE */
-#define DUK_BUILTINS_DATA_LENGTH                                      3790
+#define DUK_BUILTINS_DATA_LENGTH                                      3819
 #elif defined(DUK_USE_DOUBLE_BE)
 #if !defined(DUK_SINGLE_FILE)
-DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[3790];
+DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[3819];
 #endif  /* !DUK_SINGLE_FILE */
-#define DUK_BUILTINS_DATA_LENGTH                                      3790
+#define DUK_BUILTINS_DATA_LENGTH                                      3819
 #elif defined(DUK_USE_DOUBLE_ME)
 #if !defined(DUK_SINGLE_FILE)
-DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[3790];
+DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[3819];
 #endif  /* !DUK_SINGLE_FILE */
-#define DUK_BUILTINS_DATA_LENGTH                                      3790
+#define DUK_BUILTINS_DATA_LENGTH                                      3819
 #else
 #error invalid endianness defines
 #endif
 #endif  /* DUK_USE_ROM_OBJECTS */
 #endif  /* DUK_BUILTINS_H_INCLUDED */
-#line 50 "duk_internal.h"
+#line 51 "duk_internal.h"
 
 /* #include duk_util.h */
 #line 1 "duk_util.h"
@@ -1822,10 +2246,6 @@
 #if !defined(DUK_UTIL_H_INCLUDED)
 #define DUK_UTIL_H_INCLUDED
 
-#define DUK_UTIL_MIN_HASH_PRIME  17  /* must match genhashsizes.py */
-
-#define DUK_UTIL_GET_HASH_PROBE_STEP(hash)  (duk_util_probe_steps[(hash) & 0x1f])
-
 #if defined(DUK_USE_GET_RANDOM_DOUBLE)
 #define DUK_UTIL_GET_RANDOM_DOUBLE(thr) DUK_USE_GET_RANDOM_DOUBLE((thr)->heap_udata)
 #else
@@ -2311,7 +2731,7 @@
 #endif  /* !DUK_SINGLE_FILE */
 
 /* Note: assumes that duk_util_probe_steps size is 32 */
-#if defined(DUK_USE_HOBJECT_HASH_PART) || defined(DUK_USE_STRTAB_PROBE)
+#if defined(DUK_USE_HOBJECT_HASH_PART)
 #if !defined(DUK_SINGLE_FILE)
 DUK_INTERNAL_DECL duk_uint8_t duk_util_probe_steps[32];
 #endif  /* !DUK_SINGLE_FILE */
@@ -2321,13 +2741,10 @@
 DUK_INTERNAL_DECL duk_uint32_t duk_util_hashbytes(const duk_uint8_t *data, duk_size_t len, duk_uint32_t seed);
 #endif
 
-#if defined(DUK_USE_HOBJECT_HASH_PART) || defined(DUK_USE_STRTAB_PROBE)
-DUK_INTERNAL_DECL duk_uint32_t duk_util_get_hash_prime(duk_uint32_t size);
-#endif
-
 DUK_INTERNAL_DECL duk_uint32_t duk_bd_decode(duk_bitdecoder_ctx *ctx, duk_small_int_t bits);
 DUK_INTERNAL_DECL duk_small_uint_t duk_bd_decode_flag(duk_bitdecoder_ctx *ctx);
 DUK_INTERNAL_DECL duk_uint32_t duk_bd_decode_flagged(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_uint32_t def_value);
+DUK_INTERNAL_DECL duk_int32_t duk_bd_decode_flagged_signed(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_int32_t def_value);
 DUK_INTERNAL_DECL duk_uint32_t duk_bd_decode_varuint(duk_bitdecoder_ctx *ctx);
 DUK_INTERNAL_DECL duk_small_uint_t duk_bd_decode_bitpacked_string(duk_bitdecoder_ctx *bd, duk_uint8_t *out);
 
@@ -2521,6 +2938,7 @@
 #define DUK_STR_INVALID_REGEXP_ESCAPE            "invalid regexp escape"
 #define DUK_STR_INVALID_BACKREFS                 "invalid backreference(s)"
 #define DUK_STR_INVALID_REGEXP_CHARACTER         "invalid regexp character"
+#define DUK_STR_INVALID_REGEXP_GROUP             "invalid regexp group"
 #define DUK_STR_UNTERMINATED_CHARCLASS           "unterminated character class"
 #define DUK_STR_INVALID_RANGE                    "invalid range"
 
@@ -3007,8 +3425,7 @@
 #define DUK_BC_TRYCATCH_FLAG_WITH_BINDING   (1 << 3)
 
 /* DUK_OP_DECLVAR flags in A; bottom bits are reserved for propdesc flags (DUK_PROPDESC_FLAG_XXX) */
-#define DUK_BC_DECLVAR_FLAG_UNDEF_VALUE     (1 << 4)  /* use 'undefined' for value automatically */
-#define DUK_BC_DECLVAR_FLAG_FUNC_DECL       (1 << 5)  /* function declaration */
+#define DUK_BC_DECLVAR_FLAG_FUNC_DECL       (1 << 4)  /* function declaration */
 
 /* Misc constants and helper macros. */
 #define DUK_BC_LDINT_BIAS           (1L << 15)
@@ -3052,7 +3469,7 @@
 
 #define DUK_LEXER_GETPOINT(ctx,pt)    duk_lexer_getpoint((ctx), (pt))
 
-/* currently 6 characters of lookup are actually needed (duk_lexer.c) */
+/* Currently 6 characters of lookup are actually needed (duk_lexer.c). */
 #define DUK_LEXER_WINDOW_SIZE                     6
 #if defined(DUK_USE_LEXER_SLIDING_WINDOW)
 #define DUK_LEXER_BUFFER_SIZE                     64
@@ -3430,6 +3847,8 @@
 
 	duk_int_t token_count;                         /* number of tokens parsed */
 	duk_int_t token_limit;                         /* maximum token count before error (sanity backstop) */
+
+	duk_small_uint_t flags;                        /* lexer flags, use compiler flag defines for now */
 };
 
 /*
@@ -3678,10 +4097,6 @@
  *  Prototypes
  */
 
-#define DUK_JS_COMPILE_FLAG_EVAL      (1 << 0)  /* source is eval code (not global) */
-#define DUK_JS_COMPILE_FLAG_STRICT    (1 << 1)  /* strict outer context */
-#define DUK_JS_COMPILE_FLAG_FUNCEXPR  (1 << 2)  /* source is a function expression (used for Function constructor) */
-
 DUK_INTERNAL_DECL void duk_js_compile(duk_hthread *thr, const duk_uint8_t *src_buffer, duk_size_t src_length, duk_small_uint_t flags);
 
 #endif  /* DUK_JS_COMPILER_H_INCLUDED */
@@ -3786,30 +4201,45 @@
  *
  *  All heap objects share the same flags and refcount fields.  Objects other
  *  than strings also need to have a single or double linked list pointers
- *  for insertion into the "heap allocated" list.  Strings are held in the
- *  heap-wide string table so they don't need link pointers.
+ *  for insertion into the "heap allocated" list.  Strings have single linked
+ *  list pointers for string table chaining.
  *
  *  Technically, 'h_refcount' must be wide enough to guarantee that it cannot
- *  wrap (otherwise objects might be freed incorrectly after wrapping).  This
- *  means essentially that the refcount field must be as wide as data pointers.
- *  On 64-bit platforms this means that the refcount needs to be 64 bits even
- *  if an 'int' is 32 bits.  This is a bit unfortunate, and compromising on
- *  this might be reasonable in the future.
+ *  wrap; otherwise objects might be freed incorrectly after wrapping.  The
+ *  default refcount field is 32 bits even on 64-bit systems: while that's in
+ *  theory incorrect, the Duktape heap needs to be larger than 64GB for the
+ *  count to actually wrap (assuming 16-byte duk_tvals).  This is very unlikely
+ *  to ever be an issue, but if it is, disabling DUK_USE_REFCOUNT32 causes
+ *  Duktape to use size_t for refcounts which should always be safe.
  *
  *  Heap header size on 32-bit platforms: 8 bytes without reference counting,
  *  16 bytes with reference counting.
- */
+ *
+ *  Note that 'raw' macros such as DUK_HEAPHDR_GET_REFCOUNT() are not
+ *  defined without DUK_USE_REFERENCE_COUNTING, so caller must #if defined()
+ *  around them.
+ */
+
+/* XXX: macro for shared header fields (avoids some padding issues) */
 
 struct duk_heaphdr {
 	duk_uint32_t h_flags;
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
+#if defined(DUK_USE_ASSERTIONS)
+	/* When assertions enabled, used by mark-and-sweep for refcount
+	 * validation.  Largest reasonable type; also detects overflows.
+	 */
+	duk_size_t h_assert_refcount;
+#endif
 #if defined(DUK_USE_REFCOUNT16)
-	duk_uint16_t h_refcount16;
+	duk_uint16_t h_refcount;
+#elif defined(DUK_USE_REFCOUNT32)
+	duk_uint32_t h_refcount;
 #else
 	duk_size_t h_refcount;
 #endif
-#endif
+#endif  /* DUK_USE_REFERENCE_COUNTING */
 
 #if defined(DUK_USE_HEAPPTR16)
 	duk_uint16_t h_next16;
@@ -3849,15 +4279,26 @@
 	duk_uint32_t h_flags;
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
+#if defined(DUK_USE_ASSERTIONS)
+	/* When assertions enabled, used by mark-and-sweep for refcount
+	 * validation.  Largest reasonable type; also detects overflows.
+	 */
+	duk_size_t h_assert_refcount;
+#endif
 #if defined(DUK_USE_REFCOUNT16)
-	duk_uint16_t h_refcount16;
+	duk_uint16_t h_refcount;
 	duk_uint16_t h_strextra16;  /* round out to 8 bytes */
+#elif defined(DUK_USE_REFCOUNT32)
+	duk_uint32_t h_refcount;
 #else
 	duk_size_t h_refcount;
 #endif
 #else
 	duk_uint16_t h_strextra16;
-#endif
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+	duk_hstring *h_next;
+	/* No 'h_prev' pointer for strings. */
 };
 
 #define DUK_HEAPHDR_FLAGS_TYPE_MASK      0x00000003UL
@@ -3913,21 +4354,13 @@
 #endif
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
-#if defined(DUK_USE_REFCOUNT16)
-#define DUK_HEAPHDR_GET_REFCOUNT(h)   ((h)->h_refcount16)
-#define DUK_HEAPHDR_SET_REFCOUNT(h,val)  do { \
-		(h)->h_refcount16 = (val); \
-	} while (0)
-#define DUK_HEAPHDR_PREINC_REFCOUNT(h)  (++(h)->h_refcount16)  /* result: updated refcount */
-#define DUK_HEAPHDR_PREDEC_REFCOUNT(h)  (--(h)->h_refcount16)  /* result: updated refcount */
-#else
 #define DUK_HEAPHDR_GET_REFCOUNT(h)   ((h)->h_refcount)
 #define DUK_HEAPHDR_SET_REFCOUNT(h,val)  do { \
 		(h)->h_refcount = (val); \
+		DUK_ASSERT((h)->h_refcount == (val));  /* No truncation. */ \
 	} while (0)
 #define DUK_HEAPHDR_PREINC_REFCOUNT(h)  (++(h)->h_refcount)  /* result: updated refcount */
 #define DUK_HEAPHDR_PREDEC_REFCOUNT(h)  (--(h)->h_refcount)  /* result: updated refcount */
-#endif
 #else
 /* refcount macros not defined without refcounting, caller must #if defined() now */
 #endif  /* DUK_USE_REFERENCE_COUNTING */
@@ -4015,18 +4448,23 @@
 	} while (0)
 #endif
 
-#define DUK_HEAPHDR_STRING_INIT_NULLS(h)  /* currently nop */
+#define DUK_HEAPHDR_STRING_INIT_NULLS(h)  do { \
+		(h)->h_next = NULL; \
+	} while (0)
 
 /*
  *  Type tests
  */
 
-#define DUK_HEAPHDR_IS_OBJECT(h) \
-	(DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_OBJECT)
-#define DUK_HEAPHDR_IS_STRING(h) \
-	(DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_STRING)
-#define DUK_HEAPHDR_IS_BUFFER(h) \
-	(DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_BUFFER)
+/* Take advantage of the fact that for DUK_HTYPE_xxx numbers the lowest bit
+ * is only set for DUK_HTYPE_OBJECT (= 1).
+ */
+#if 0
+#define DUK_HEAPHDR_IS_OBJECT(h) (DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_OBJECT)
+#endif
+#define DUK_HEAPHDR_IS_OBJECT(h) ((h)->h_flags & 0x01UL)
+#define DUK_HEAPHDR_IS_STRING(h) (DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_STRING)
+#define DUK_HEAPHDR_IS_BUFFER(h) (DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_BUFFER)
 
 /*
  *  Assert helpers
@@ -4049,16 +4487,23 @@
 #define DUK_ASSERT_HEAPHDR_LINKS(heap,h) do {} while (0)
 #endif
 
+#define DUK_ASSERT_HEAPHDR_VALID(h) do { \
+		DUK_ASSERT((h) != NULL); \
+		DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID((h))); \
+	} while (0)
+
+#endif  /* DUK_HEAPHDR_H_INCLUDED */
+/* #include duk_refcount.h */
+#line 1 "duk_refcount.h"
 /*
  *  Reference counting helper macros.  The macros take a thread argument
  *  and must thus always be executed in a specific thread context.  The
- *  thread argument is needed for features like finalization.  Currently
- *  it is not required for INCREF, but it is included just in case.
- *
- *  Note that 'raw' macros such as DUK_HEAPHDR_GET_REFCOUNT() are not
- *  defined without DUK_USE_REFERENCE_COUNTING, so caller must #if defined()
- *  around them.
- */
+ *  thread argument is not really needed anymore: DECREF can operate with
+ *  a heap pointer only, and INCREF needs neither.
+ */
+
+#if !defined(DUK_REFCOUNT_H_INCLUDED)
+#define DUK_REFCOUNT_H_INCLUDED
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
 
@@ -4089,6 +4534,7 @@
 			DUK_ASSERT(duk__h != NULL); \
 			DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \
 			DUK_HEAPHDR_PREINC_REFCOUNT(duk__h); \
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) != 0);  /* No wrapping. */ \
 		} \
 	} while (0)
 #define DUK_TVAL_DECREF_FAST(thr,tv) do { \
@@ -4123,6 +4569,7 @@
 		DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \
 		if (DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE(duk__h)) { \
 			DUK_HEAPHDR_PREINC_REFCOUNT(duk__h); \
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) != 0);  /* No wrapping. */ \
 		} \
 	} while (0)
 #define DUK_HEAPHDR_DECREF_FAST_RAW(thr,h,rzcall,rzcast) do { \
@@ -4289,17 +4736,22 @@
 		} \
 	} while (0)
 
-/* Free pending refzero entries; quick check to avoid call because often
- * the queue is empty.
- */
+/* Called after one or more DECREF NORZ calls to handle pending side effects.
+ * At present DECREF NORZ does freeing inline but doesn't execute finalizers,
+ * so these macros check for pending finalizers and execute them.  The FAST
+ * variant is performance critical.
+ */
+#if defined(DUK_USE_FINALIZER_SUPPORT)
 #define DUK_REFZERO_CHECK_FAST(thr) do { \
-		if ((thr)->heap->refzero_list != NULL) { \
-			duk_refzero_free_pending((thr)); \
-		} \
+		duk_refzero_check_fast((thr)); \
 	} while (0)
 #define DUK_REFZERO_CHECK_SLOW(thr) do { \
-		duk_refzero_free_pending((thr)); \
-	} while (0)
+		duk_refzero_check_slow((thr)); \
+	} while (0)
+#else  /* DUK_USE_FINALIZER_SUPPORT */
+#define DUK_REFZERO_CHECK_FAST(thr) do { } while (0)
+#define DUK_REFZERO_CHECK_SLOW(thr) do { } while (0)
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
 
 /*
  *  Macros to set a duk_tval and update refcount of the target (decref the
@@ -4706,7 +5158,41 @@
 
 #endif  /* DUK_USE_REFERENCE_COUNTING */
 
-#endif  /* DUK_HEAPHDR_H_INCLUDED */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+DUK_INTERNAL_DECL void duk_refzero_check_slow(duk_hthread *thr);
+DUK_INTERNAL_DECL void duk_refzero_check_fast(duk_hthread *thr);
+#endif
+DUK_INTERNAL_DECL void duk_heaphdr_refcount_finalize_norz(duk_heap *heap, duk_heaphdr *hdr);
+DUK_INTERNAL_DECL void duk_hobject_refcount_finalize_norz(duk_heap *heap, duk_hobject *h);
+#if 0  /* Not needed: fast path handles inline; slow path uses duk_heaphdr_decref() which is needed anyway. */
+DUK_INTERNAL_DECL void duk_hstring_decref(duk_hthread *thr, duk_hstring *h);
+DUK_INTERNAL_DECL void duk_hstring_decref_norz(duk_hthread *thr, duk_hstring *h);
+DUK_INTERNAL_DECL void duk_hbuffer_decref(duk_hthread *thr, duk_hbuffer *h);
+DUK_INTERNAL_DECL void duk_hbuffer_decref_norz(duk_hthread *thr, duk_hbuffer *h);
+DUK_INTERNAL_DECL void duk_hobject_decref(duk_hthread *thr, duk_hobject *h);
+DUK_INTERNAL_DECL void duk_hobject_decref_norz(duk_hthread *thr, duk_hobject *h);
+#endif
+DUK_INTERNAL_DECL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h);
+DUK_INTERNAL_DECL void duk_heaphdr_refzero_norz(duk_hthread *thr, duk_heaphdr *h);
+#if defined(DUK_USE_FAST_REFCOUNT_DEFAULT)
+DUK_INTERNAL_DECL void duk_hstring_refzero(duk_hthread *thr, duk_hstring *h);  /* no 'norz' variant */
+DUK_INTERNAL_DECL void duk_hbuffer_refzero(duk_hthread *thr, duk_hbuffer *h);  /* no 'norz' variant */
+DUK_INTERNAL_DECL void duk_hobject_refzero(duk_hthread *thr, duk_hobject *h);
+DUK_INTERNAL_DECL void duk_hobject_refzero_norz(duk_hthread *thr, duk_hobject *h);
+#else
+DUK_INTERNAL_DECL void duk_tval_incref(duk_tval *tv);
+DUK_INTERNAL_DECL void duk_tval_decref(duk_hthread *thr, duk_tval *tv);
+DUK_INTERNAL_DECL void duk_tval_decref_norz(duk_hthread *thr, duk_tval *tv);
+DUK_INTERNAL_DECL void duk_heaphdr_incref(duk_heaphdr *h);
+DUK_INTERNAL_DECL void duk_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h);
+DUK_INTERNAL_DECL void duk_heaphdr_decref_norz(duk_hthread *thr, duk_heaphdr *h);
+#endif
+#else  /* DUK_USE_REFERENCE_COUNTING */
+/* no refcounting */
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+#endif  /* DUK_REFCOUNT_H_INCLUDED */
 /* #include duk_api_internal.h */
 #line 1 "duk_api_internal.h"
 /*
@@ -4812,7 +5298,7 @@
 DUK_INTERNAL_DECL duk_hcompfunc *duk_get_hcompfunc(duk_context *ctx, duk_idx_t idx);
 DUK_INTERNAL_DECL duk_hnatfunc *duk_get_hnatfunc(duk_context *ctx, duk_idx_t idx);
 
-DUK_INTERNAL_DECL void *duk_get_buffer_data_raw(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, duk_bool_t throw_flag, duk_bool_t *out_found);
+DUK_INTERNAL_DECL void *duk_get_buffer_data_raw(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_len, duk_bool_t throw_flag, duk_bool_t *out_isbuffer);
 
 DUK_INTERNAL_DECL duk_hobject *duk_get_hobject_with_class(duk_context *ctx, duk_idx_t idx, duk_small_uint_t classnum);
 
@@ -4992,6 +5478,8 @@
 
 DUK_INTERNAL_DECL duk_idx_t duk_get_top_require_min(duk_context *ctx, duk_idx_t min_top);
 DUK_INTERNAL_DECL duk_idx_t duk_get_top_index_unsafe(duk_context *ctx);
+DUK_INTERNAL_DECL void duk_pop_n_unsafe(duk_context *ctx, duk_idx_t count);
+DUK_INTERNAL_DECL void duk_pop_n_nodecref_unsafe(duk_context *ctx, duk_idx_t count);
 DUK_INTERNAL_DECL void duk_pop_unsafe(duk_context *ctx);
 
 DUK_INTERNAL_DECL void duk_compact_m1(duk_context *ctx);
@@ -5102,7 +5590,7 @@
         */
 #define DUK_HSTRING_IS_ASCII(x)                     (DUK_HSTRING_GET_BYTELEN((x)) == DUK_HSTRING_GET_CHARLEN((x)))
 #endif
-#define DUK_HSTRING_IS_ASCII(x)                     DUK_HSTRING_HAS_ASCII((x))
+#define DUK_HSTRING_IS_ASCII(x)                     DUK_HSTRING_HAS_ASCII((x))  /* lazily set! */
 #define DUK_HSTRING_IS_EMPTY(x)                     (DUK_HSTRING_GET_BYTELEN((x)) == 0)
 
 #if defined(DUK_USE_STRHASH16)
@@ -5123,7 +5611,7 @@
 		(x)->hdr.h_strextra16 = (v); \
 	} while (0)
 #if defined(DUK_USE_HSTRING_CLEN)
-#define DUK_HSTRING_GET_CHARLEN(x)                  ((x)->clen16)
+#define DUK_HSTRING_GET_CHARLEN(x)                  duk_hstring_get_charlen((x))
 #define DUK_HSTRING_SET_CHARLEN(x,v) do { \
 		(x)->clen16 = (v); \
 	} while (0)
@@ -5138,7 +5626,7 @@
 #define DUK_HSTRING_SET_BYTELEN(x,v) do { \
 		(x)->blen = (v); \
 	} while (0)
-#define DUK_HSTRING_GET_CHARLEN(x)                  ((x)->clen)
+#define DUK_HSTRING_GET_CHARLEN(x)                  duk_hstring_get_charlen((x))
 #define DUK_HSTRING_SET_CHARLEN(x,v) do { \
 		(x)->clen = (v); \
 	} while (0)
@@ -5169,11 +5657,11 @@
  * avoids helper call if string has no array index value.
  */
 #define DUK_HSTRING_GET_ARRIDX_FAST(h)  \
-	(DUK_HSTRING_HAS_ARRIDX((h)) ? duk_js_to_arrayindex_string_helper((h)) : DUK_HSTRING_NO_ARRAY_INDEX)
+	(DUK_HSTRING_HAS_ARRIDX((h)) ? duk_js_to_arrayindex_hstring_fast_known((h)) : DUK_HSTRING_NO_ARRAY_INDEX)
 
 /* Slower but more compact variant. */
 #define DUK_HSTRING_GET_ARRIDX_SLOW(h)  \
-	(duk_js_to_arrayindex_string_helper((h)))
+	(duk_js_to_arrayindex_hstring_fast((h)))
 #endif
 
 /*
@@ -5188,30 +5676,26 @@
 	 */
 	duk_heaphdr_string hdr;
 
-	/* Note: we could try to stuff a partial hash (e.g. 16 bits) into the
-	 * shared heap header.  Good hashing needs more hash bits though.
-	 */
-
-	/* string hash */
+	/* String hash. */
 #if defined(DUK_USE_STRHASH16)
 	/* If 16-bit hash is in use, stuff it into duk_heaphdr_string flags. */
 #else
 	duk_uint32_t hash;
 #endif
 
-	/* precomputed array index (or DUK_HSTRING_NO_ARRAY_INDEX) */
+	/* Precomputed array index (or DUK_HSTRING_NO_ARRAY_INDEX). */
 #if defined(DUK_USE_HSTRING_ARRIDX)
 	duk_uarridx_t arridx;
 #endif
 
-	/* length in bytes (not counting NUL term) */
+	/* Length in bytes (not counting NUL term). */
 #if defined(DUK_USE_STRLEN16)
 	/* placed in duk_heaphdr_string */
 #else
 	duk_uint32_t blen;
 #endif
 
-	/* length in codepoints (must be E5 compatible) */
+	/* Length in codepoints (must be E5 compatible). */
 #if defined(DUK_USE_STRLEN16)
 #if defined(DUK_USE_HSTRING_CLEN)
 	duk_uint16_t clen16;
@@ -5223,7 +5707,7 @@
 #endif
 
 	/*
-	 *  String value of 'blen+1' bytes follows (+1 for NUL termination
+	 *  String data of 'blen+1' bytes follows (+1 for NUL termination
 	 *  convenience for C API).  No alignment needs to be guaranteed
 	 *  for strings, but fields above should guarantee alignment-by-4
 	 *  (but not alignment-by-8).
@@ -5248,10 +5732,7 @@
  */
 
 DUK_INTERNAL_DECL duk_ucodepoint_t duk_hstring_char_code_at_raw(duk_hthread *thr, duk_hstring *h, duk_uint_t pos, duk_bool_t surrogate_aware);
-
-#if !defined(DUK_USE_HSTRING_CLEN)
 DUK_INTERNAL_DECL duk_size_t duk_hstring_get_charlen(duk_hstring *h);
-#endif
 
 #endif  /* DUK_HSTRING_H_INCLUDED */
 /* #include duk_hobject.h */
@@ -5290,8 +5771,8 @@
 #if !defined(DUK_HOBJECT_H_INCLUDED)
 #define DUK_HOBJECT_H_INCLUDED
 
-/* Object flag.  There are currently 25 flag bits available.  Make sure
- * this stays in sync with debugger object inspection code.
+/* Object flags.  Make sure this stays in sync with debugger object
+ * inspection code.
  */
 
 /* XXX: some flags are object subtype specific (e.g. common to all function
@@ -5303,14 +5784,14 @@
 #define DUK_HOBJECT_FLAG_COMPFUNC              DUK_HEAPHDR_USER_FLAG(4)   /* object is a compiled function (duk_hcompfunc) */
 #define DUK_HOBJECT_FLAG_NATFUNC               DUK_HEAPHDR_USER_FLAG(5)   /* object is a native function (duk_hnatfunc) */
 #define DUK_HOBJECT_FLAG_BUFOBJ                DUK_HEAPHDR_USER_FLAG(6)   /* object is a buffer object (duk_hbufobj) (always exotic) */
-#define DUK_HOBJECT_FLAG_THREAD                DUK_HEAPHDR_USER_FLAG(7)   /* object is a thread (duk_hthread) */
+#define DUK_HOBJECT_FLAG_FASTREFS              DUK_HEAPHDR_USER_FLAG(7)   /* object has no fields needing DECREF/marking beyond base duk_hobject header */
 #define DUK_HOBJECT_FLAG_ARRAY_PART            DUK_HEAPHDR_USER_FLAG(8)   /* object has an array part (a_size may still be 0) */
 #define DUK_HOBJECT_FLAG_STRICT                DUK_HEAPHDR_USER_FLAG(9)   /* function: function object is strict */
 #define DUK_HOBJECT_FLAG_NOTAIL                DUK_HEAPHDR_USER_FLAG(10)  /* function: function must not be tail called */
 #define DUK_HOBJECT_FLAG_NEWENV                DUK_HEAPHDR_USER_FLAG(11)  /* function: create new environment when called (see duk_hcompfunc) */
 #define DUK_HOBJECT_FLAG_NAMEBINDING           DUK_HEAPHDR_USER_FLAG(12)  /* function: create binding for func name (function templates only, used for named function expressions) */
 #define DUK_HOBJECT_FLAG_CREATEARGS            DUK_HEAPHDR_USER_FLAG(13)  /* function: create an arguments object on function call */
-#define DUK_HOBJECT_FLAG_ENVRECCLOSED          DUK_HEAPHDR_USER_FLAG(14)  /* envrec: (declarative) record is closed */
+#define DUK_HOBJECT_FLAG_HAVE_FINALIZER        DUK_HEAPHDR_USER_FLAG(14)  /* object has a callable finalizer property */
 #define DUK_HOBJECT_FLAG_EXOTIC_ARRAY          DUK_HEAPHDR_USER_FLAG(15)  /* 'Array' object, array length and index exotic behavior */
 #define DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ      DUK_HEAPHDR_USER_FLAG(16)  /* 'String' object, array index exotic behavior */
 #define DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS      DUK_HEAPHDR_USER_FLAG(17)  /* 'Arguments' object and has arguments exotic behavior (non-strict callee) */
@@ -5390,7 +5871,6 @@
 #define DUK_HOBJECT_CMASK_OBJENV               (1UL << DUK_HOBJECT_CLASS_OBJENV)
 #define DUK_HOBJECT_CMASK_DECENV               (1UL << DUK_HOBJECT_CLASS_DECENV)
 #define DUK_HOBJECT_CMASK_POINTER              (1UL << DUK_HOBJECT_CLASS_POINTER)
-#define DUK_HOBJECT_CMASK_THREAD               (1UL << DUK_HOBJECT_CLASS_THREAD)
 #define DUK_HOBJECT_CMASK_ARRAYBUFFER          (1UL << DUK_HOBJECT_CLASS_ARRAYBUFFER)
 #define DUK_HOBJECT_CMASK_DATAVIEW             (1UL << DUK_HOBJECT_CLASS_DATAVIEW)
 #define DUK_HOBJECT_CMASK_INT8ARRAY            (1UL << DUK_HOBJECT_CLASS_INT8ARRAY)
@@ -5424,7 +5904,7 @@
 #define DUK_HOBJECT_IS_COMPFUNC(h)             DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
 #define DUK_HOBJECT_IS_NATFUNC(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
 #define DUK_HOBJECT_IS_BUFOBJ(h)               DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
-#define DUK_HOBJECT_IS_THREAD(h)               DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_IS_THREAD(h)               (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_THREAD)
 
 #define DUK_HOBJECT_IS_NONBOUND_FUNCTION(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
                                                         DUK_HOBJECT_FLAG_COMPFUNC | \
@@ -5462,14 +5942,14 @@
 #define DUK_HOBJECT_HAS_COMPFUNC(h)            DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
 #define DUK_HOBJECT_HAS_NATFUNC(h)             DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
 #define DUK_HOBJECT_HAS_BUFOBJ(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
-#define DUK_HOBJECT_HAS_THREAD(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_HAS_FASTREFS(h)            DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS)
 #define DUK_HOBJECT_HAS_ARRAY_PART(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
 #define DUK_HOBJECT_HAS_STRICT(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
 #define DUK_HOBJECT_HAS_NOTAIL(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL)
 #define DUK_HOBJECT_HAS_NEWENV(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
 #define DUK_HOBJECT_HAS_NAMEBINDING(h)         DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
 #define DUK_HOBJECT_HAS_CREATEARGS(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
-#define DUK_HOBJECT_HAS_ENVRECCLOSED(h)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
+#define DUK_HOBJECT_HAS_HAVE_FINALIZER(h)      DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_HAVE_FINALIZER)
 #define DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
 #define DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
 #define DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
@@ -5482,14 +5962,14 @@
 #define DUK_HOBJECT_SET_COMPFUNC(h)            DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
 #define DUK_HOBJECT_SET_NATFUNC(h)             DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
 #define DUK_HOBJECT_SET_BUFOBJ(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
-#define DUK_HOBJECT_SET_THREAD(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_SET_FASTREFS(h)            DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS)
 #define DUK_HOBJECT_SET_ARRAY_PART(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
 #define DUK_HOBJECT_SET_STRICT(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
 #define DUK_HOBJECT_SET_NOTAIL(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL)
 #define DUK_HOBJECT_SET_NEWENV(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
 #define DUK_HOBJECT_SET_NAMEBINDING(h)         DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
 #define DUK_HOBJECT_SET_CREATEARGS(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
-#define DUK_HOBJECT_SET_ENVRECCLOSED(h)        DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
+#define DUK_HOBJECT_SET_HAVE_FINALIZER(h)      DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_HAVE_FINALIZER)
 #define DUK_HOBJECT_SET_EXOTIC_ARRAY(h)        DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
 #define DUK_HOBJECT_SET_EXOTIC_STRINGOBJ(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
 #define DUK_HOBJECT_SET_EXOTIC_ARGUMENTS(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
@@ -5502,20 +5982,28 @@
 #define DUK_HOBJECT_CLEAR_COMPFUNC(h)          DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
 #define DUK_HOBJECT_CLEAR_NATFUNC(h)           DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
 #define DUK_HOBJECT_CLEAR_BUFOBJ(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
-#define DUK_HOBJECT_CLEAR_THREAD(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_CLEAR_FASTREFS(h)          DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS)
 #define DUK_HOBJECT_CLEAR_ARRAY_PART(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
 #define DUK_HOBJECT_CLEAR_STRICT(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
 #define DUK_HOBJECT_CLEAR_NOTAIL(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL)
 #define DUK_HOBJECT_CLEAR_NEWENV(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
 #define DUK_HOBJECT_CLEAR_NAMEBINDING(h)       DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
 #define DUK_HOBJECT_CLEAR_CREATEARGS(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
-#define DUK_HOBJECT_CLEAR_ENVRECCLOSED(h)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
+#define DUK_HOBJECT_CLEAR_HAVE_FINALIZER(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_HAVE_FINALIZER)
 #define DUK_HOBJECT_CLEAR_EXOTIC_ARRAY(h)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
 #define DUK_HOBJECT_CLEAR_EXOTIC_STRINGOBJ(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
 #define DUK_HOBJECT_CLEAR_EXOTIC_ARGUMENTS(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
 #define DUK_HOBJECT_CLEAR_EXOTIC_DUKFUNC(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC)
 #define DUK_HOBJECT_CLEAR_EXOTIC_PROXYOBJ(h)   DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
 
+/* Object can/cannot use FASTREFS, i.e. has no strong reference fields beyond
+ * duk_hobject base header.
+ */
+#define DUK_HOBJECT_PROHIBITS_FASTREFS(h) \
+	(DUK_HOBJECT_IS_COMPFUNC((h)) || DUK_HOBJECT_IS_DECENV((h)) || DUK_HOBJECT_IS_OBJENV((h)) || \
+	 DUK_HOBJECT_IS_BUFOBJ((h)) || DUK_HOBJECT_IS_THREAD((h)))
+#define DUK_HOBJECT_ALLOWS_FASTREFS(h) (!DUK_HOBJECT_PROHIBITS_FASTREFS((h)))
+
 /* Flags used for property attributes in duk_propdesc and packed flags.
  * Must fit into 8 bits.
  */
@@ -5897,6 +6385,16 @@
 #define DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr,h,p)       duk_hobject_set_prototype_updref((thr), (h), (p))
 
 /*
+ *  Finalizer check
+ */
+
+#if defined(DUK_USE_HEAPPTR16)
+#define DUK_HOBJECT_HAS_FINALIZER_FAST(heap,h) duk_hobject_has_finalizer_fast_raw((heap), (h))
+#else
+#define DUK_HOBJECT_HAS_FINALIZER_FAST(heap,h) duk_hobject_has_finalizer_fast_raw((h))
+#endif
+
+/*
  *  Resizing and hash behavior
  */
 
@@ -5909,21 +6407,8 @@
 #if defined(DUK_USE_OBJSIZES16)
 #define DUK_HOBJECT_MAX_PROPERTIES       0x0000ffffUL
 #else
-#define DUK_HOBJECT_MAX_PROPERTIES       0x7fffffffUL   /* 2**31-1 ~= 2G properties */
-#endif
-
-/* higher value conserves memory; also note that linear scan is cache friendly */
-#define DUK_HOBJECT_E_USE_HASH_LIMIT     32
-
-/* hash size relative to entries size: for value X, approx. hash_prime(e_size + e_size / X) */
-#define DUK_HOBJECT_H_SIZE_DIVISOR       4  /* hash size approx. 1.25 times entries size */
-
-/* if new_size < L * old_size, resize without abandon check; L = 3-bit fixed point, e.g. 9 -> 9/8 = 112.5% */
-#define DUK_HOBJECT_A_FAST_RESIZE_LIMIT  9  /* 112.5%, i.e. new size less than 12.5% higher -> fast resize */
-
-/* if density < L, abandon array part, L = 3-bit fixed point, e.g. 2 -> 2/8 = 25% */
-/* limit is quite low: one array entry is 8 bytes, one normal entry is 4+1+8+4 = 17 bytes (with hash entry) */
-#define DUK_HOBJECT_A_ABANDON_LIMIT      2  /* 25%, i.e. less than 25% used -> abandon */
+#define DUK_HOBJECT_MAX_PROPERTIES       0x3fffffffUL   /* 2**30-1 ~= 1G properties */
+#endif
 
 /* internal align target for props allocation, must be 2*n for some n */
 #if (DUK_USE_ALIGN_BY == 4)
@@ -5936,18 +6421,6 @@
 #error invalid DUK_USE_ALIGN_BY
 #endif
 
-/* controls for minimum entry part growth */
-#define DUK_HOBJECT_E_MIN_GROW_ADD       16
-#define DUK_HOBJECT_E_MIN_GROW_DIVISOR   8  /* 2^3 -> 1/8 = 12.5% min growth */
-
-/* controls for minimum array part growth */
-#define DUK_HOBJECT_A_MIN_GROW_ADD       16
-#define DUK_HOBJECT_A_MIN_GROW_DIVISOR   8  /* 2^3 -> 1/8 = 12.5% min growth */
-
-/* probe sequence */
-#define DUK_HOBJECT_HASH_INITIAL(hash,h_size)  ((hash) % (h_size))
-#define DUK_HOBJECT_HASH_PROBE_STEP(hash)      DUK_UTIL_GET_HASH_PROBE_STEP((hash))
-
 /*
  *  PC-to-line constants
  */
@@ -5977,7 +6450,7 @@
 
 struct duk_propdesc {
 	/* read-only values 'lifted' for ease of use */
-	duk_small_int_t flags;
+	duk_small_uint_t flags;
 	duk_hobject *get;
 	duk_hobject *set;
 
@@ -6101,17 +6574,18 @@
  */
 
 /* alloc and init */
-DUK_INTERNAL_DECL duk_hobject *duk_hobject_alloc(duk_heap *heap, duk_uint_t hobject_flags);
-#if 0  /* unused */
-DUK_INTERNAL_DECL duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, duk_uint_t hobject_flags);
-#endif
-DUK_INTERNAL_DECL duk_harray *duk_harray_alloc(duk_heap *heap, duk_uint_t hobject_flags);
-DUK_INTERNAL_DECL duk_hcompfunc *duk_hcompfunc_alloc(duk_heap *heap, duk_uint_t hobject_flags);
-DUK_INTERNAL_DECL duk_hnatfunc *duk_hnatfunc_alloc(duk_heap *heap, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hobject *duk_hobject_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hobject *duk_hobject_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_harray *duk_harray_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hcompfunc *duk_hcompfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hnatfunc *duk_hnatfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
-DUK_INTERNAL_DECL duk_hbufobj *duk_hbufobj_alloc(duk_heap *heap, duk_uint_t hobject_flags);
-#endif
-DUK_INTERNAL_DECL duk_hthread *duk_hthread_alloc(duk_heap *heap, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hbufobj *duk_hbufobj_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
+#endif
+DUK_INTERNAL_DECL duk_hthread *duk_hthread_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hthread *duk_hthread_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hdecenv *duk_hdecenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
+DUK_INTERNAL_DECL duk_hobjenv *duk_hobjenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags);
 
 /* resize */
 DUK_INTERNAL_DECL void duk_hobject_realloc_props(duk_hthread *thr,
@@ -6148,6 +6622,11 @@
 DUK_INTERNAL_DECL void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags);
 DUK_INTERNAL_DECL void duk_hobject_define_property_internal_arridx(duk_hthread *thr, duk_hobject *obj, duk_uarridx_t arr_idx, duk_small_uint_t flags);
 DUK_INTERNAL_DECL duk_size_t duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj);
+#if defined(DUK_USE_HEAPPTR16)
+DUK_INTERNAL_DECL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_heap *heap, duk_hobject *obj);
+#else
+DUK_INTERNAL_DECL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_hobject *obj);
+#endif
 
 /* helpers for defineProperty() and defineProperties() */
 DUK_INTERNAL_DECL
@@ -6194,11 +6673,6 @@
 /* macros */
 DUK_INTERNAL_DECL void duk_hobject_set_prototype_updref(duk_hthread *thr, duk_hobject *h, duk_hobject *p);
 
-/* finalization */
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-DUK_INTERNAL_DECL void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj);
-#endif
-
 /* pc2line */
 #if defined(DUK_USE_PC2LINE)
 DUK_INTERNAL_DECL void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, duk_uint_fast32_t length);
@@ -6798,8 +7272,6 @@
 #endif
 #endif  /* DUK_USE_ROM_STRINGS */
 
-#define DUK_HTHREAD_GET_CURRENT_ACTIVATION(thr)  (&(thr)->callstack[(thr)->callstack_top - 1])
-
 /* values for the state field */
 #define DUK_HTHREAD_STATE_INACTIVE     1   /* thread not currently running */
 #define DUK_HTHREAD_STATE_RUNNING      2   /* thread currently running (only one at a time) */
@@ -6981,8 +7453,9 @@
 
 	/* Call stack.  [0,callstack_top[ is GC reachable. */
 	duk_activation *callstack;
+	duk_activation *callstack_curr;         /* current activation (or NULL if none) */
 	duk_size_t callstack_size;              /* allocation size */
-	duk_size_t callstack_top;               /* next to use, highest used is top - 1 */
+	duk_size_t callstack_top;               /* next to use, highest used is top - 1 (or none if top == 0) */
 	duk_size_t callstack_preventcount;      /* number of activation records in callstack preventing a yield */
 
 	/* Catch stack.  [0,catchstack_top[ is GC reachable. */
@@ -7044,12 +7517,19 @@
 
 DUK_INTERNAL_DECL void duk_hthread_callstack_grow(duk_hthread *thr);
 DUK_INTERNAL_DECL void duk_hthread_callstack_shrink_check(duk_hthread *thr);
+DUK_INTERNAL_DECL void duk_hthread_callstack_unwind_norz(duk_hthread *thr, duk_size_t new_top);
 DUK_INTERNAL_DECL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_top);
 DUK_INTERNAL_DECL void duk_hthread_catchstack_grow(duk_hthread *thr);
 DUK_INTERNAL_DECL void duk_hthread_catchstack_shrink_check(duk_hthread *thr);
+DUK_INTERNAL_DECL void duk_hthread_catchstack_unwind_norz(duk_hthread *thr, duk_size_t new_top);
 DUK_INTERNAL_DECL void duk_hthread_catchstack_unwind(duk_hthread *thr, duk_size_t new_top);
 
-DUK_INTERNAL_DECL duk_activation *duk_hthread_get_current_activation(duk_hthread *thr);
+#if defined(DUK_USE_FINALIZER_TORTURE)
+DUK_INTERNAL_DECL void duk_hthread_valstack_torture_realloc(duk_hthread *thr);
+DUK_INTERNAL_DECL void duk_hthread_callstack_torture_realloc(duk_hthread *thr);
+DUK_INTERNAL_DECL void duk_hthread_catchstack_torture_realloc(duk_hthread *thr);
+#endif
+
 DUK_INTERNAL_DECL void *duk_hthread_get_valstack_ptr(duk_heap *heap, void *ud);  /* indirect allocs */
 DUK_INTERNAL_DECL void *duk_hthread_get_callstack_ptr(duk_heap *heap, void *ud);  /* indirect allocs */
 DUK_INTERNAL_DECL void *duk_hthread_get_catchstack_ptr(duk_heap *heap, void *ud);  /* indirect allocs */
@@ -7111,6 +7591,56 @@
 };
 
 #endif  /* DUK_HARRAY_H_INCLUDED */
+/* #include duk_henv.h */
+#line 1 "duk_henv.h"
+/*
+ *  Environment object representation.
+ */
+
+#if !defined(DUK_HENV_H_INCLUDED)
+#define DUK_HENV_H_INCLUDED
+
+#define DUK_ASSERT_HDECENV_VALID(h) do { \
+		DUK_ASSERT((h) != NULL); \
+		DUK_ASSERT(DUK_HOBJECT_IS_DECENV((duk_hobject *) (h))); \
+		DUK_ASSERT((h)->thread == NULL || (h)->varmap != NULL); \
+	} while (0)
+
+#define DUK_ASSERT_HOBJENV_VALID(h) do { \
+		DUK_ASSERT((h) != NULL); \
+		DUK_ASSERT(DUK_HOBJECT_IS_OBJENV((duk_hobject *) (h))); \
+		DUK_ASSERT((h)->target != NULL); \
+		DUK_ASSERT((h)->has_this == 0 || (h)->has_this == 1); \
+	} while (0)
+
+struct duk_hdecenv {
+	/* Shared object part. */
+	duk_hobject obj;
+
+	/* These control variables provide enough information to access live
+	 * variables for a closure that is still open.  If thread == NULL,
+	 * the record is closed and the identifiers are in the property table.
+	 */
+	duk_hthread *thread;
+	duk_hobject *varmap;
+	duk_size_t regbase;
+};
+
+struct duk_hobjenv {
+	/* Shared object part. */
+	duk_hobject obj;
+
+	/* Target object and 'this' binding for object binding. */
+	duk_hobject *target;
+
+	/* The 'target' object is used as a this binding in only some object
+	 * environments.  For example, the global environment does not provide
+	 * a this binding, but a with statement does.
+	 */
+	duk_bool_t has_this;
+};
+
+#endif  /* DUK_HENV_H_INCLUDED */
 /* #include duk_hbuffer.h */
 #line 1 "duk_hbuffer.h"
 /*
@@ -7459,13 +7989,11 @@
  *  Heap flags
  */
 
-#define DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING                     (1 << 0)  /* mark-and-sweep is currently running */
-#define DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED            (1 << 1)  /* mark-and-sweep marking reached a recursion limit and must use multi-pass marking */
-#define DUK_HEAP_FLAG_REFZERO_FREE_RUNNING                     (1 << 2)  /* refcount code is processing refzero list */
-#define DUK_HEAP_FLAG_ERRHANDLER_RUNNING                       (1 << 3)  /* an error handler (user callback to augment/replace error) is running */
-#define DUK_HEAP_FLAG_INTERRUPT_RUNNING                        (1 << 4)  /* executor interrupt running (used to avoid nested interrupts) */
-#define DUK_HEAP_FLAG_FINALIZER_NORESCUE                       (1 << 5)  /* heap destruction ongoing, finalizer rescue no longer possible */
-#define DUK_HEAP_FLAG_DEBUGGER_PAUSED                          (1 << 6)  /* debugger is paused: talk with debug client until step/resume */
+#define DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED            (1 << 0)  /* mark-and-sweep marking reached a recursion limit and must use multi-pass marking */
+#define DUK_HEAP_FLAG_ERRHANDLER_RUNNING                       (1 << 1)  /* an error handler (user callback to augment/replace error) is running */
+#define DUK_HEAP_FLAG_INTERRUPT_RUNNING                        (1 << 2)  /* executor interrupt running (used to avoid nested interrupts) */
+#define DUK_HEAP_FLAG_FINALIZER_NORESCUE                       (1 << 3)  /* heap destruction ongoing, finalizer rescue no longer possible */
+#define DUK_HEAP_FLAG_DEBUGGER_PAUSED                          (1 << 4)  /* debugger is paused: talk with debug client until step/resume */
 
 #define DUK__HEAP_HAS_FLAGS(heap,bits)               ((heap)->flags & (bits))
 #define DUK__HEAP_SET_FLAGS(heap,bits)  do { \
@@ -7475,25 +8003,19 @@
 		(heap)->flags &= ~(bits); \
 	} while (0)
 
-#define DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)            DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
 #define DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)   DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
-#define DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)            DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)
 #define DUK_HEAP_HAS_ERRHANDLER_RUNNING(heap)              DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_ERRHANDLER_RUNNING)
 #define DUK_HEAP_HAS_INTERRUPT_RUNNING(heap)               DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_INTERRUPT_RUNNING)
 #define DUK_HEAP_HAS_FINALIZER_NORESCUE(heap)              DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_FINALIZER_NORESCUE)
 #define DUK_HEAP_HAS_DEBUGGER_PAUSED(heap)                 DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_DEBUGGER_PAUSED)
 
-#define DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap)            DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
 #define DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap)   DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
-#define DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap)            DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)
 #define DUK_HEAP_SET_ERRHANDLER_RUNNING(heap)              DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_ERRHANDLER_RUNNING)
 #define DUK_HEAP_SET_INTERRUPT_RUNNING(heap)               DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_INTERRUPT_RUNNING)
 #define DUK_HEAP_SET_FINALIZER_NORESCUE(heap)              DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_FINALIZER_NORESCUE)
 #define DUK_HEAP_SET_DEBUGGER_PAUSED(heap)                 DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_DEBUGGER_PAUSED)
 
-#define DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap)          DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
 #define DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap) DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
-#define DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap)          DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)
 #define DUK_HEAP_CLEAR_ERRHANDLER_RUNNING(heap)            DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_ERRHANDLER_RUNNING)
 #define DUK_HEAP_CLEAR_INTERRUPT_RUNNING(heap)             DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_INTERRUPT_RUNNING)
 #define DUK_HEAP_CLEAR_FINALIZER_NORESCUE(heap)            DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_FINALIZER_NORESCUE)
@@ -7520,11 +8042,25 @@
  *  field and the GC caller can impose further flags.
  */
 
-#define DUK_MS_FLAG_EMERGENCY                (1 << 0)   /* emergency mode: try extra hard */
-#define DUK_MS_FLAG_NO_STRINGTABLE_RESIZE    (1 << 1)   /* don't resize stringtable (but may sweep it); needed during stringtable resize */
-#define DUK_MS_FLAG_NO_OBJECT_COMPACTION     (1 << 2)   /* don't compact objects; needed during object property allocation resize */
-#define DUK_MS_FLAG_NO_FINALIZERS            (1 << 3)   /* don't run finalizers; leave finalizable objects in finalize_list for next round */
-#define DUK_MS_FLAG_SKIP_FINALIZERS          (1 << 4)   /* don't run finalizers; queue finalizable objects back to heap_allocated */
+/* Emergency mark-and-sweep: try extra hard, even at the cost of
+ * performance.
+ */
+#define DUK_MS_FLAG_EMERGENCY                (1 << 0)
+
+/* Voluntary mark-and-sweep: triggered periodically. */
+#define DUK_MS_FLAG_VOLUNTARY                (1 << 1)
+
+/* Postpone rescue decisions for reachable objects with FINALIZED set.
+ * Used during finalize_list processing to avoid incorrect rescue
+ * decisions due to finalize_list being a reachability root.
+ */
+#define DUK_MS_FLAG_POSTPONE_RESCUE          (1 << 2)
+
+/* Don't compact objects; needed during object property table resize
+ * to prevent a recursive resize.  It would suffice to protect only the
+ * current object being resized, but this is not yet implemented.
+ */
+#define DUK_MS_FLAG_NO_OBJECT_COMPACTION     (1 << 2)
 
 /*
  *  Thread switching
@@ -7566,39 +8102,28 @@
 #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP              256L
 #endif
 
+/* GC torture. */
+#if defined(DUK_USE_GC_TORTURE)
+#define DUK_GC_TORTURE(heap) do { duk_heap_mark_and_sweep((heap), 0); } while (0)
+#else
+#define DUK_GC_TORTURE(heap) do { } while (0)
+#endif
+
 /* Stringcache is used for speeding up char-offset-to-byte-offset
  * translations for non-ASCII strings.
  */
 #define DUK_HEAP_STRCACHE_SIZE                            4
 #define DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT                16  /* strings up to the this length are not cached */
 
-/* helper to insert a (non-string) heap object into heap allocated list */
-#define DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap,hdr)     duk_heap_insert_into_heap_allocated((heap),(hdr))
-
-/*
- *  Stringtable
- */
-
-/* initial stringtable size, must be prime and higher than DUK_UTIL_MIN_HASH_PRIME */
-#define DUK_STRTAB_INITIAL_SIZE            17
-
-/* indicates a deleted string; any fixed non-NULL, non-hstring pointer works */
-#define DUK_STRTAB_DELETED_MARKER(heap)    ((duk_hstring *) heap)
-
-/* resizing parameters */
-#define DUK_STRTAB_MIN_FREE_DIVISOR        4                /* load factor max 75% */
-#define DUK_STRTAB_MIN_USED_DIVISOR        4                /* load factor min 25% */
-#define DUK_STRTAB_GROW_ST_SIZE(n)         ((n) + (n))      /* used entries + approx 100% -> reset load to 50% */
-
-#define DUK_STRTAB_U32_MAX_STRLEN          10               /* 4'294'967'295 */
-#define DUK_STRTAB_HIGHEST_32BIT_PRIME     0xfffffffbUL
-
-/* probe sequence (open addressing) */
-#define DUK_STRTAB_HASH_INITIAL(hash,h_size)    ((hash) % (h_size))
-#define DUK_STRTAB_HASH_PROBE_STEP(hash)        DUK_UTIL_GET_HASH_PROBE_STEP((hash))
-
-/* fixed top level hashtable size (separate chaining) */
-#define DUK_STRTAB_CHAIN_SIZE              DUK_USE_STRTAB_CHAIN_SIZE
+/* Some list management macros. */
+#define DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap,hdr)     duk_heap_insert_into_heap_allocated((heap), (hdr))
+#if defined(DUK_USE_REFERENCE_COUNTING)
+#define DUK_HEAP_REMOVE_FROM_HEAP_ALLOCATED(heap,hdr)     duk_heap_remove_from_heap_allocated((heap), (hdr))
+#endif
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+#define DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap,hdr)      duk_heap_insert_into_finalize_list((heap), (hdr))
+#define DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(heap,hdr)      duk_heap_remove_from_finalize_list((heap), (hdr))
+#endif
 
 /*
  *  Built-in strings
@@ -7669,10 +8194,17 @@
 #define DUK_FREE(heap,ptr)                              duk_heap_mem_free((heap), (ptr))
 
 /*
+ *  Checked allocation, relative to a thread
+ */
+
+#define DUK_ALLOC_CHECKED(thr,size)                     duk_heap_mem_alloc_checked((thr), (size))
+#define DUK_ALLOC_CHECKED_ZEROED(thr,size)              duk_heap_mem_alloc_checked_zeroed((thr), (size))
+
+/*
  *  Memory constants
  */
 
-#define DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT           5   /* Retry allocation after mark-and-sweep for this
+#define DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT           10  /* Retry allocation after mark-and-sweep for this
                                                               * many times.  A single mark-and-sweep round is
                                                               * not guaranteed to free all unreferenced memory
                                                               * because of finalization (in fact, ANY number of
@@ -7713,27 +8245,6 @@
 	duk_uint32_t line;
 };
 
-#if defined(DUK_USE_DEBUGGER_SUPPORT)
-#define DUK_HEAP_IS_DEBUGGER_ATTACHED(heap) ((heap)->dbg_read_cb != NULL)
-#define DUK_HEAP_CLEAR_STEP_STATE(heap) do { \
-		(heap)->dbg_step_type = DUK_STEP_TYPE_NONE; \
-		(heap)->dbg_step_thread = NULL; \
-		(heap)->dbg_step_csindex = 0; \
-		(heap)->dbg_step_startline = 0; \
-	} while (0)
-#define DUK_HEAP_SET_PAUSED(heap) do { \
-		DUK_HEAP_SET_DEBUGGER_PAUSED(heap); \
-		(heap)->dbg_state_dirty = 1; \
-		DUK_HEAP_CLEAR_STEP_STATE((heap)); \
-	} while (0)
-#define DUK_HEAP_CLEAR_PAUSED(heap) do { \
-		DUK_HEAP_CLEAR_DEBUGGER_PAUSED(heap); \
-		(heap)->dbg_state_dirty = 1; \
-		DUK_HEAP_CLEAR_STEP_STATE((heap)); \
-	} while (0)
-#define DUK_HEAP_IS_PAUSED(heap) (DUK_HEAP_HAS_DEBUGGER_PAUSED((heap)))
-#endif  /* DUK_USE_DEBUGGER_SUPPORT */
-
 /*
  *  String cache should ideally be at duk_hthread level, but that would
  *  cause string finalization to slow down relative to the number of
@@ -7762,28 +8273,17 @@
 	duk_tval value2;          /* 2nd related value (type specific) */
 };
 
-/*
- *  Stringtable entry for fixed size stringtable
- */
-
-struct duk_strtab_entry {
-#if defined(DUK_USE_HEAPPTR16)
-	/* A 16-bit listlen makes sense with 16-bit heap pointers: there
-	 * won't be space for 64k strings anyway.
-	 */
-	duk_uint16_t listlen;  /* if 0, 'str16' used, if > 0, 'strlist16' used */
-	union {
-		duk_uint16_t strlist16;
-		duk_uint16_t str16;
-	} u;
-#else
-	duk_size_t listlen;  /* if 0, 'str' used, if > 0, 'strlist' used */
-	union {
-		duk_hstring **strlist;
-		duk_hstring *str;
-	} u;
-#endif
-};
+#define DUK_ASSERT_LJSTATE_UNSET(heap) do { \
+		DUK_ASSERT(heap != NULL); \
+		DUK_ASSERT(heap->lj.type == DUK_LJ_TYPE_UNKNOWN); \
+		DUK_ASSERT(heap->lj.iserror == 0); \
+		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&heap->lj.value1)); \
+		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&heap->lj.value2)); \
+	} while (0)
+#define DUK_ASSERT_LJSTATE_SET(heap) do { \
+		DUK_ASSERT(heap != NULL); \
+		DUK_ASSERT(heap->lj.type != DUK_LJ_TYPE_UNKNOWN); \
+	} while (0)
 
 /*
  *  Main heap structure
@@ -7802,12 +8302,6 @@
 	 */
 	void *heap_udata;
 
-	/* Precomputed pointers when using 16-bit heap pointer packing. */
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t heapptr_null16;
-	duk_uint16_t heapptr_deleted16;
-#endif
-
 	/* Fatal error handling, called e.g. when a longjmp() is needed but
 	 * lj.jmpbuf_ptr is NULL.  fatal_func must never return; it's not
 	 * declared as "noreturn" because doing that for typedefs is a bit
@@ -7815,53 +8309,114 @@
 	 */
 	duk_fatal_function fatal_func;
 
-	/* allocated heap objects */
+	/* Main list of allocated heap objects.  Objects are either here,
+	 * in finalize_list waiting for processing, or in refzero_list
+	 * temporarily while a DECREF refzero cascade finishes.
+	 */
 	duk_heaphdr *heap_allocated;
 
-	/* work list for objects whose refcounts are zero but which have not been
-	 * "finalized"; avoids recursive C calls when refcounts go to zero in a
-	 * chain of objects.
+	/* Temporary work list for freeing a cascade of objects when a DECREF
+	 * (or DECREF_NORZ) encounters a zero refcount.  Using a work list
+	 * allows fixed C stack size when refcounts go to zero for a chain of
+	 * objects.  Outside of DECREF this is always a NULL because DECREF is
+	 * processed without side effects (only memory free calls).
 	 */
 #if defined(DUK_USE_REFERENCE_COUNTING)
 	duk_heaphdr *refzero_list;
-	duk_heaphdr *refzero_list_tail;
-#endif
-
-	/* mark-and-sweep control */
-#if defined(DUK_USE_VOLUNTARY_GC)
-	duk_int_t mark_and_sweep_trigger_counter;
-#endif
-	duk_int_t mark_and_sweep_recursion_depth;
-
-	/* mark-and-sweep flags automatically active (used for critical sections) */
-	duk_small_uint_t mark_and_sweep_base_flags;
-
-	/* work list for objects to be finalized (by mark-and-sweep) */
+#endif
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	/* Work list for objects to be finalized. */
 	duk_heaphdr *finalize_list;
-
-	/* longjmp state */
+#if defined(DUK_USE_ASSERTIONS)
+	/* Object whose finalizer is executing right now (no nesting). */
+	duk_heaphdr *currently_finalizing;
+#endif
+#endif
+
+	/* Voluntary mark-and-sweep trigger counter.  Intentionally signed
+	 * because we continue decreasing the value when voluntary GC cannot
+	 * run.
+	 */
+#if defined(DUK_USE_VOLUNTARY_GC)
+	duk_int_t ms_trigger_counter;
+#endif
+
+	/* Mark-and-sweep recursion control: too deep recursion causes
+	 * multi-pass processing to avoid growing C stack without bound.
+	 */
+	duk_uint_t ms_recursion_depth;
+
+	/* Mark-and-sweep flags automatically active (used for critical sections). */
+	duk_small_uint_t ms_base_flags;
+
+	/* Mark-and-sweep running flag.  Prevents re-entry, and also causes
+	 * refzero events to be ignored (= objects won't be queued to refzero_list).
+	 */
+	duk_uint_t ms_running;
+
+	/* Mark-and-sweep prevent count, stacking.  Used to avoid M&S side
+	 * effects (besides finalizers which are controlled separately) such
+	 * as compacting the string table or object property tables.  This
+	 * is also bumped when ms_running is set to prevent recursive re-entry.
+	 * Can also be bumped when mark-and-sweep is not running.
+	 */
+	duk_uint_t ms_prevent_count;
+
+	/* Finalizer processing prevent count, stacking.  Bumped when finalizers
+	 * are processed to prevent recursive finalizer processing (first call site
+	 * processing finalizers handles all finalizers until the list is empty).
+	 * Can also be bumped explicitly to prevent finalizer execution.
+	 */
+	duk_uint_t pf_prevent_count;
+
+	/* When processing finalize_list, don't actually run finalizers but
+	 * queue finalizable objects back to heap_allocated as is.  This is
+	 * used during heap destruction to deal with finalizers that keep
+	 * on creating more finalizable garbage.
+	 */
+	duk_uint_t pf_skip_finalizers;
+
+#if defined(DUK_USE_ASSERTIONS)
+	/* Set when we're in a critical path where an error throw would cause
+	 * e.g. sandboxing/protected call violations or state corruption.  This
+	 * is just used for asserts.
+	 */
+	duk_bool_t error_not_allowed;
+#endif
+
+#if defined(DUK_USE_ASSERTIONS)
+	/* Set when heap is still being initialized, helps with writing
+	 * some assertions.
+	 */
+	duk_bool_t heap_initializing;
+#endif
+
+	/* Marker for detecting internal "double faults", errors thrown when
+	 * we're trying to create an error object, see duk_error_throw.c.
+	 */
+	duk_bool_t creating_error;
+
+	/* Longjmp state. */
 	duk_ljstate lj;
 
-	/* marker for detecting internal "double faults", see duk_error_throw.c */
-	duk_bool_t handling_error;
-
-	/* heap thread, used internally and for finalization */
+	/* Heap thread, used internally and for finalization. */
 	duk_hthread *heap_thread;
 
-	/* current thread */
-	duk_hthread *curr_thread;  /* currently running thread */
-
-	/* heap level "stash" object (e.g., various reachability roots) */
+	/* Current running thread. */
+	duk_hthread *curr_thread;
+
+	/* Heap level "stash" object (e.g., various reachability roots). */
 	duk_hobject *heap_object;
 
 	/* duk_handle_call / duk_handle_safe_call recursion depth limiting */
 	duk_int_t call_recursion_depth;
 	duk_int_t call_recursion_limit;
 
-	/* mix-in value for computing string hashes; should be reasonably unpredictable */
+	/* Mix-in value for computing string hashes; should be reasonably unpredictable. */
 	duk_uint32_t hash_seed;
 
-	/* rnd_state for duk_util_tinyrandom.c */
+	/* Random number state for duk_util_tinyrandom.c. */
 #if !defined(DUK_USE_GET_RANDOM_DOUBLE)
 #if defined(DUK_USE_PREFER_SIZE) || !defined(DUK_USE_64BIT_OPS)
 	duk_uint32_t rnd_state;  /* State for Shamir's three-op algorithm */
@@ -7870,7 +8425,7 @@
 #endif
 #endif
 
-	/* counter for unique local symbol creation */
+	/* Counter for unique local symbol creation. */
 	/* XXX: When 64-bit types are available, it would be more efficient to
 	 * use a duk_uint64_t at least for incrementing but maybe also for
 	 * string formatting in the Symbol constructor.
@@ -7886,10 +8441,9 @@
 	duk_int_t inst_count_interrupt;
 #endif
 
-	/* debugger */
-
+	/* Debugger state. */
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
-	/* callbacks and udata; dbg_read_cb != NULL is used to indicate attached state */
+	/* Callbacks and udata; dbg_read_cb != NULL is used to indicate attached state. */
 	duk_debug_read_function dbg_read_cb;                /* required, NULL implies detached */
 	duk_debug_write_function dbg_write_cb;              /* required */
 	duk_debug_peek_function dbg_peek_cb;
@@ -7899,7 +8453,7 @@
 	duk_debug_detached_function dbg_detached_cb;
 	void *dbg_udata;
 
-	/* debugger state, only relevant when attached */
+	/* The following are only relevant when debugger is attached. */
 	duk_bool_t dbg_processing;              /* currently processing messages or breakpoints: don't enter message processing recursively (e.g. no breakpoints when processing debugger eval) */
 	duk_bool_t dbg_state_dirty;             /* resend state next time executor is about to run */
 	duk_bool_t dbg_force_restart;           /* force executor restart to recheck breakpoints; used to handle function returns (see GH-303) */
@@ -7923,30 +8477,25 @@
 	duk_uint8_t dbg_next_byte;
 #endif
 
-	/* string intern table (weak refs) */
-#if defined(DUK_USE_STRTAB_PROBE)
-#if defined(DUK_USE_HEAPPTR16)
+	/* String intern table (weak refs). */
+#if defined(DUK_USE_STRTAB_PTRCOMP)
 	duk_uint16_t *strtable16;
 #else
 	duk_hstring **strtable;
 #endif
-	duk_uint32_t st_size;     /* alloc size in elements */
-	duk_uint32_t st_used;     /* used elements (includes DELETED) */
-#endif
-
-	/* XXX: static alloc is OK until separate chaining stringtable
-	 * resizing is implemented.
-	 */
-#if defined(DUK_USE_STRTAB_CHAIN)
-	duk_strtab_entry strtable[DUK_STRTAB_CHAIN_SIZE];
-#endif
-
-	/* string access cache (codepoint offset -> byte offset) for fast string
+	duk_uint32_t st_mask;    /* mask for lookup, st_size - 1 */
+	duk_uint32_t st_size;    /* stringtable size */
+#if (DUK_USE_STRTAB_MINSIZE != DUK_USE_STRTAB_MAXSIZE)
+	duk_uint32_t st_count;   /* string count for resize load factor checks */
+#endif
+	duk_bool_t st_resizing;  /* string table is being resized; avoid recursive resize */
+
+	/* String access cache (codepoint offset -> byte offset) for fast string
 	 * character looping; 'weak' reference which needs special handling in GC.
 	 */
 	duk_strcache strcache[DUK_HEAP_STRCACHE_SIZE];
 
-	/* built-in strings */
+	/* Built-in strings. */
 #if defined(DUK_USE_ROM_STRINGS)
 	/* No field needed when strings are in ROM. */
 #else
@@ -7975,32 +8524,32 @@
 DUK_INTERNAL_DECL void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr);
 
 DUK_INTERNAL_DECL void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
-#if defined(DUK_USE_DOUBLE_LINKED_HEAP) && defined(DUK_USE_REFERENCE_COUNTING)
-DUK_INTERNAL_DECL void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
+#if defined(DUK_USE_REFERENCE_COUNTING)
+DUK_INTERNAL_DECL void duk_heap_remove_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
+#endif
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+DUK_INTERNAL_DECL void duk_heap_insert_into_finalize_list(duk_heap *heap, duk_heaphdr *hdr);
+DUK_INTERNAL_DECL void duk_heap_remove_from_finalize_list(duk_heap *heap, duk_heaphdr *hdr);
+#endif
+#if defined(DUK_USE_ASSERTIONS)
+DUK_INTERNAL_DECL duk_bool_t duk_heap_in_heap_allocated(duk_heap *heap, duk_heaphdr *ptr);
 #endif
 #if defined(DUK_USE_INTERRUPT_COUNTER)
 DUK_INTERNAL_DECL void duk_heap_switch_thread(duk_heap *heap, duk_hthread *new_thr);
 #endif
 
-#if 0  /*unused*/
-DUK_INTERNAL_DECL duk_hstring *duk_heap_string_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen);
-#endif
-DUK_INTERNAL_DECL duk_hstring *duk_heap_string_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen);
-DUK_INTERNAL_DECL duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t len);
-#if 0  /*unused*/
-DUK_INTERNAL_DECL duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_uint32_t val);
-#endif
-DUK_INTERNAL_DECL duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_uint32_t val);
-DUK_INTERNAL_DECL duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val);
+DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen);
+DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t len);
+DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_u32(duk_heap *heap, duk_uint32_t val);
+DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_u32_checked(duk_hthread *thr, duk_uint32_t val);
 #if defined(DUK_USE_REFERENCE_COUNTING)
-DUK_INTERNAL_DECL void duk_heap_string_remove(duk_heap *heap, duk_hstring *h);
-#endif
-#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
-DUK_INTERNAL_DECL void duk_heap_force_strtab_resize(duk_heap *heap);
-#endif
-DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap);
+DUK_INTERNAL_DECL void duk_heap_strtable_unlink(duk_heap *heap, duk_hstring *h);
+#endif
+DUK_INTERNAL_DECL void duk_heap_strtable_unlink_prev(duk_heap *heap, duk_hstring *h, duk_hstring *prev);
+DUK_INTERNAL_DECL void duk_heap_strtable_force_resize(duk_heap *heap);
+DUK_INTERNAL void duk_heap_strtable_free(duk_heap *heap);
 #if defined(DUK_USE_DEBUG)
-DUK_INTERNAL void duk_heap_dump_strtab(duk_heap *heap);
+DUK_INTERNAL void duk_heap_strtable_dump(duk_heap *heap);
 #endif
 
 DUK_INTERNAL_DECL void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h);
@@ -8014,41 +8563,18 @@
 
 DUK_INTERNAL_DECL void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size);
 DUK_INTERNAL_DECL void *duk_heap_mem_alloc_zeroed(duk_heap *heap, duk_size_t size);
+DUK_INTERNAL_DECL void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size);
+DUK_INTERNAL_DECL void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size);
 DUK_INTERNAL_DECL void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize);
 DUK_INTERNAL_DECL void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize);
 DUK_INTERNAL_DECL void duk_heap_mem_free(duk_heap *heap, void *ptr);
 
-#if defined(DUK_USE_REFERENCE_COUNTING)
-DUK_INTERNAL_DECL void duk_refzero_free_pending(duk_hthread *thr);
-DUK_INTERNAL_DECL void duk_heaphdr_refcount_finalize(duk_hthread *thr, duk_heaphdr *hdr);
-#if 0  /* Not needed: fast path handles inline; slow path uses duk_heaphdr_decref() which is needed anyway. */
-DUK_INTERNAL_DECL void duk_hstring_decref(duk_hthread *thr, duk_hstring *h);
-DUK_INTERNAL_DECL void duk_hstring_decref_norz(duk_hthread *thr, duk_hstring *h);
-DUK_INTERNAL_DECL void duk_hbuffer_decref(duk_hthread *thr, duk_hbuffer *h);
-DUK_INTERNAL_DECL void duk_hbuffer_decref_norz(duk_hthread *thr, duk_hbuffer *h);
-DUK_INTERNAL_DECL void duk_hobject_decref(duk_hthread *thr, duk_hobject *h);
-DUK_INTERNAL_DECL void duk_hobject_decref_norz(duk_hthread *thr, duk_hobject *h);
-#endif
-DUK_INTERNAL_DECL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h);
-DUK_INTERNAL_DECL void duk_heaphdr_refzero_norz(duk_hthread *thr, duk_heaphdr *h);
-#if defined(DUK_USE_FAST_REFCOUNT_DEFAULT)
-DUK_INTERNAL_DECL void duk_hstring_refzero(duk_hthread *thr, duk_hstring *h);  /* no 'norz' variant */
-DUK_INTERNAL_DECL void duk_hbuffer_refzero(duk_hthread *thr, duk_hbuffer *h);  /* no 'norz' variant */
-DUK_INTERNAL_DECL void duk_hobject_refzero(duk_hthread *thr, duk_hobject *h);
-DUK_INTERNAL_DECL void duk_hobject_refzero_norz(duk_hthread *thr, duk_hobject *h);
-#else
-DUK_INTERNAL_DECL void duk_tval_incref(duk_tval *tv);
-DUK_INTERNAL_DECL void duk_tval_decref(duk_hthread *thr, duk_tval *tv);
-DUK_INTERNAL_DECL void duk_tval_decref_norz(duk_hthread *thr, duk_tval *tv);
-DUK_INTERNAL_DECL void duk_heaphdr_incref(duk_heaphdr *h);
-DUK_INTERNAL_DECL void duk_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h);
-DUK_INTERNAL_DECL void duk_heaphdr_decref_norz(duk_hthread *thr, duk_heaphdr *h);
-#endif
-#else  /* DUK_USE_REFERENCE_COUNTING */
-/* no refcounting */
-#endif  /* DUK_USE_REFERENCE_COUNTING */
-
-DUK_INTERNAL_DECL duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags);
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+DUK_INTERNAL_DECL void duk_heap_run_finalizer(duk_heap *heap, duk_hobject *obj);
+DUK_INTERNAL_DECL void duk_heap_process_finalize_list(duk_heap *heap);
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
+
+DUK_INTERNAL_DECL void duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags);
 
 DUK_INTERNAL_DECL duk_uint32_t duk_heap_hashstring(duk_heap *heap, const duk_uint8_t *str, duk_size_t len);
 
@@ -8197,7 +8723,13 @@
 
 DUK_INTERNAL_DECL duk_small_int_t duk_debug_add_breakpoint(duk_hthread *thr, duk_hstring *filename, duk_uint32_t line);
 DUK_INTERNAL_DECL duk_bool_t duk_debug_remove_breakpoint(duk_hthread *thr, duk_small_uint_t breakpoint_index);
-#endif
+
+DUK_INTERNAL_DECL duk_bool_t duk_debug_is_attached(duk_heap *heap);
+DUK_INTERNAL_DECL duk_bool_t duk_debug_is_paused(duk_heap *heap);
+DUK_INTERNAL_DECL void duk_debug_set_paused(duk_heap *heap);
+DUK_INTERNAL_DECL void duk_debug_clear_paused(duk_heap *heap);
+DUK_INTERNAL_DECL void duk_debug_clear_step_state(duk_heap *heap);
+#endif  /* DUK_USE_DEBUGGER_SUPPORT */
 
 #endif  /* DUK_DEBUGGER_H_INCLUDED */
 /* #include duk_debug.h */
@@ -8865,7 +9397,10 @@
 
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_default_fatal_handler(void *udata, const char *msg));
 
-DUK_INTERNAL_DECL void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type);
+DUK_INTERNAL_DECL void duk_err_setup_ljstate1(duk_hthread *thr, duk_small_uint_t lj_type, duk_tval *tv_val);
+#if defined(DUK_USE_DEBUGGER_SUPPORT)
+DUK_INTERNAL_DECL void duk_err_check_debugger_integration(duk_hthread *thr);
+#endif
 
 DUK_INTERNAL_DECL duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, duk_errcode_t err_code);
 
@@ -9252,8 +9787,11 @@
 DUK_INTERNAL_DECL duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv);
 DUK_INTERNAL_DECL duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv);
 DUK_INTERNAL_DECL duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv);
-DUK_INTERNAL_DECL duk_small_int_t duk_js_to_arrayindex_raw_string(const duk_uint8_t *str, duk_uint32_t blen, duk_uarridx_t *out_idx);
-DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_string_helper(duk_hstring *h);
+DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_string(const duk_uint8_t *str, duk_uint32_t blen);
+#if !defined(DUK_USE_HSTRING_ARRIDX)
+DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_hstring_fast_known(duk_hstring *h);
+DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_hstring_fast(duk_hstring *h);
+#endif
 DUK_INTERNAL_DECL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags);
 DUK_INTERNAL_DECL duk_small_int_t duk_js_data_compare(const duk_uint8_t *buf1, const duk_uint8_t *buf2, duk_size_t len1, duk_size_t len2);
 DUK_INTERNAL_DECL duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2);
@@ -9306,7 +9844,7 @@
 DUK_INTERNAL_DECL duk_bool_t duk_js_delvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name);
 DUK_INTERNAL_DECL duk_bool_t duk_js_declvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_small_int_t prop_flags, duk_bool_t is_func_decl);
 DUK_INTERNAL_DECL void duk_js_init_activation_environment_records_delayed(duk_hthread *thr, duk_activation *act);
-DUK_INTERNAL_DECL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env, duk_hobject *func, duk_size_t regbase);
+DUK_INTERNAL_DECL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env);
 DUK_INTERNAL_DECL duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, duk_hobject *func, duk_size_t idx_bottom);
 DUK_INTERNAL_DECL
 void duk_js_push_closure(duk_hthread *thr,
@@ -9468,6 +10006,9 @@
 #if defined(DUK_USE_DATE_TZO_WINDOWS)
 DUK_INTERNAL_DECL duk_int_t duk_bi_date_get_local_tzoffset_windows(duk_double_t d);
 #endif
+#if defined(DUK_USE_DATE_TZO_WINDOWS_NO_DST)
+DUK_INTERNAL_DECL duk_int_t duk_bi_date_get_local_tzoffset_windows_no_dst(duk_double_t d);
+#endif
 #if defined(DUK_USE_DATE_PRS_STRPTIME)
 DUK_INTERNAL_DECL duk_bool_t duk_bi_date_parse_string_strptime(duk_context *ctx, const char *str);
 #endif
@@ -9514,7 +10055,7 @@
 #endif
 
 #endif  /* DUK_SELFTEST_H_INCLUDED */
-#line 77 "duk_internal.h"
+#line 80 "duk_internal.h"
 
 #endif  /* DUK_INTERNAL_H_INCLUDED */
 #line 10 "duk_replacements.c"
@@ -9690,10 +10231,16 @@
 
 /* #include duk_internal.h -> already included */
 
+#if defined(DUK_USE_ASSERTIONS)
+#define DUK__REFCINIT(refc) 0 /*h_assert_refcount*/, (refc) /*actual*/
+#else
+#define DUK__REFCINIT(refc) (refc) /*actual*/
+#endif
+
 #if defined(DUK_USE_ROM_STRINGS)
 #error ROM support not enabled, rerun configure.py with --rom-support
 #else  /* DUK_USE_ROM_STRINGS */
-DUK_INTERNAL const duk_uint8_t duk_strings_data[921] = {
+DUK_INTERNAL const duk_uint8_t duk_strings_data[903] = {
 79,40,209,144,168,105,6,78,54,139,89,185,44,48,46,90,120,8,154,140,35,103,
 35,113,193,73,5,52,112,180,104,166,135,52,188,4,98,12,27,146,156,80,211,31,
 129,115,150,64,52,220,109,24,18,68,156,24,38,67,114,36,55,9,119,151,132,
@@ -9721,31 +10268,31 @@
 171,115,147,136,4,65,130,96,35,64,194,32,168,89,56,208,48,135,123,144,217,
 146,38,220,229,64,186,16,187,156,105,47,52,238,112,56,153,4,225,145,27,156,
 43,162,192,46,71,220,229,65,22,1,231,220,228,157,72,136,136,220,227,197,
-164,180,52,133,220,224,34,105,19,115,140,3,207,185,202,130,36,109,85,185,
-194,161,160,90,50,72,163,115,135,3,70,178,68,251,156,16,22,178,16,251,156,
-153,226,64,13,27,156,137,12,16,72,135,220,228,193,19,18,101,220,228,206,
-137,28,78,99,208,178,21,13,125,38,146,70,60,20,72,9,145,4,140,121,51,197,
-214,25,27,81,156,151,48,65,34,107,106,9,55,18,68,104,146,84,97,31,191,189,
-181,70,140,133,222,249,212,227,66,125,245,187,251,219,77,3,119,190,117,56,
-208,159,125,110,254,246,210,26,93,239,157,78,52,39,223,93,191,189,180,212,
-52,187,223,58,156,104,79,190,187,127,123,104,180,104,183,190,117,56,208,
-159,125,102,254,209,104,209,124,234,113,161,62,250,80,196,128,81,4,9,16,
-162,4,196,116,9,205,154,27,66,32,100,13,12,98,68,227,33,65,69,204,195,34,
-201,50,8,110,33,23,34,28,168,104,22,188,12,174,138,11,70,138,104,115,68,
-130,137,13,82,27,41,129,162,35,138,54,146,198,137,39,72,180,210,178,38,35,
-146,103,68,139,51,197,214,28,227,131,79,15,35,138,58,130,37,19,155,41,146,
-174,64,203,99,161,100,37,145,51,148,75,4,164,66,54,140,49,46,247,70,103,37,
-230,70,142,70,67,30,232,204,178,163,201,18,54,139,89,39,26,16,165,2,228,69,
-33,143,89,24,70,206,73,67,102,72,148,2,32,214,73,157,224,18,128,98,29,241,
-69,65,50,37,241,116,200,41,144,102,125,2,180,8,210,152,38,129,23,8,34,198,
+164,180,52,133,220,228,206,137,23,115,128,137,164,77,206,48,15,62,231,42,8,
+145,181,86,231,10,134,129,104,201,34,125,206,76,17,49,38,141,206,28,13,26,
+201,19,137,204,122,22,66,161,175,164,210,72,199,130,137,1,50,32,145,143,38,
+120,186,195,35,106,51,146,230,8,36,77,109,65,38,226,72,141,18,74,140,35,
+247,247,182,168,209,144,187,223,58,156,104,79,190,183,127,123,105,160,110,
+247,206,167,26,19,239,173,223,222,218,67,75,189,243,169,198,132,251,235,
+183,247,182,154,134,151,123,231,83,141,9,247,215,111,239,109,22,141,22,247,
+206,167,26,19,239,172,223,218,45,26,47,157,78,52,39,223,74,24,144,10,32,
+129,34,20,64,152,142,129,57,179,67,104,68,12,129,161,140,72,156,100,40,40,
+185,152,100,89,38,65,13,196,34,228,67,149,13,2,215,129,149,209,65,104,209,
+77,14,104,144,81,33,170,67,101,48,52,68,113,70,210,88,209,36,233,22,154,86,
+68,196,114,76,232,145,102,120,186,195,156,112,105,225,228,113,71,80,68,162,
+115,101,50,85,200,25,108,116,44,132,178,38,114,137,96,148,136,70,209,134,
+37,222,232,204,228,188,200,209,200,200,99,221,25,150,84,121,34,70,209,107,
+36,227,66,20,160,92,136,164,49,235,35,8,217,201,40,108,201,18,128,68,26,
+201,51,188,2,80,12,67,190,40,168,38,68,190,46,153,5,50,12,207,160,86,129,
+26,83,4,208,34,225,4,88,192,
 };
 #endif  /* DUK_USE_ROM_STRINGS */
 
 #if defined(DUK_USE_ROM_OBJECTS)
 #error ROM support not enabled, rerun configure.py with --rom-support
 #else  /* DUK_USE_ROM_OBJECTS */
-/* native functions: 164 */
-DUK_INTERNAL const duk_c_function duk_bi_native_functions[164] = {
+/* native functions: 166 */
+DUK_INTERNAL const duk_c_function duk_bi_native_functions[166] = {
 	NULL,
 	duk_bi_array_constructor,
 	duk_bi_array_constructor_is_array,
@@ -9880,6 +10427,7 @@
 	duk_bi_string_prototype_char_at,
 	duk_bi_string_prototype_char_code_at,
 	duk_bi_string_prototype_concat,
+	duk_bi_string_prototype_includes,
 	duk_bi_string_prototype_indexof_shared,
 	duk_bi_string_prototype_locale_compare,
 	duk_bi_string_prototype_match,
@@ -9888,6 +10436,7 @@
 	duk_bi_string_prototype_search,
 	duk_bi_string_prototype_slice,
 	duk_bi_string_prototype_split,
+	duk_bi_string_prototype_startswith_endswith,
 	duk_bi_string_prototype_substr,
 	duk_bi_string_prototype_substring,
 	duk_bi_string_prototype_to_string,
@@ -9912,540 +10461,549 @@
 	duk_bi_uint8array_plainof,
 };
 #if defined(DUK_USE_DOUBLE_LE)
-DUK_INTERNAL const duk_uint8_t duk_builtins_data[3790] = {
+DUK_INTERNAL const duk_uint8_t duk_builtins_data[3819] = {
 144,148,105,221,32,68,52,228,62,12,104,200,165,134,148,248,81,77,61,191,
 135,35,154,103,34,72,6,157,159,197,145,77,245,126,52,130,106,234,163,196,
 52,226,18,51,161,26,113,1,60,37,64,190,18,49,116,116,33,26,113,1,92,136,26,
 98,112,145,139,163,165,8,211,136,14,228,72,82,68,141,17,56,72,197,209,212,
 132,105,196,5,242,88,108,193,126,18,49,116,117,161,26,113,1,60,158,30,78,
-18,49,116,118,33,26,113,1,29,164,80,78,198,46,142,212,36,68,51,71,224,59,
-147,60,93,110,79,15,39,9,24,186,33,13,63,79,185,39,26,121,223,110,77,66,53,
-116,1,120,248,186,248,136,67,76,196,200,134,186,137,177,13,31,192,174,79,
-15,32,248,8,196,24,8,107,254,39,97,161,175,248,159,16,215,252,80,186,26,
-255,138,57,136,107,254,41,100,33,175,248,167,170,134,191,226,166,138,26,
-255,138,187,40,107,254,43,111,33,171,86,181,16,209,241,11,228,201,121,240,
-141,19,134,72,196,52,123,168,95,38,75,207,131,32,156,50,70,33,195,3,152,
-128,0,0,0,0,0,1,240,255,153,128,0,0,0,0,0,1,224,255,151,137,0,214,9,188,35,
-131,12,225,196,56,177,78,60,99,147,28,229,200,57,162,120,74,129,124,36,98,
-232,156,241,92,136,26,98,112,145,139,162,116,71,114,36,41,34,70,136,156,36,
-98,232,157,49,124,150,27,48,95,132,140,93,19,170,39,147,195,201,194,70,46,
-137,215,17,218,69,4,236,98,232,157,153,39,110,81,220,15,193,209,83,3,200,
-119,130,241,241,117,240,120,80,252,137,10,178,10,103,134,180,122,9,135,136,
-154,120,169,199,142,158,121,10,7,146,162,121,74,71,150,166,121,138,135,154,
-170,121,202,199,158,23,201,146,243,225,26,39,12,145,61,16,190,76,151,159,6,
-65,56,100,137,233,35,93,205,144,33,224,140,137,196,54,121,244,5,60,17,145,
-56,85,184,19,207,16,21,18,227,65,198,231,72,16,137,112,168,106,38,76,225,2,
-70,65,56,100,237,34,140,177,4,134,65,56,100,237,34,129,117,204,123,154,70,
-207,46,64,146,52,78,25,59,72,163,48,65,34,52,78,25,59,72,160,93,115,30,230,
-145,179,204,144,24,146,16,30,76,209,2,40,210,72,64,121,52,4,0,156,88,97,5,
-194,96,227,18,124,124,93,55,79,15,39,28,94,49,38,159,154,136,96,196,159,29,
-102,241,241,115,201,25,227,131,36,133,20,62,110,142,253,2,102,36,248,235,
-55,143,139,158,72,207,28,104,24,73,112,201,3,2,82,65,155,187,94,6,20,72,9,
-147,120,128,225,144,168,105,56,248,185,228,140,241,190,96,128,200,84,52,
-156,124,92,242,70,104,36,183,168,4,145,0,190,41,1,139,18,19,36,226,146,17,
-124,73,82,54,124,37,230,70,201,14,108,184,132,8,68,185,34,1,100,31,8,129,8,
-151,11,23,100,141,225,18,12,68,184,75,204,141,146,2,178,112,72,8,162,98,92,
-50,10,152,147,227,172,222,62,46,121,35,60,114,88,96,92,185,112,201,65,34,
-92,4,1,147,81,159,141,205,32,234,121,96,97,57,64,97,121,128,14,56,37,199,
-89,188,124,92,242,70,120,227,144,53,18,227,226,233,186,120,121,56,226,242,
-8,40,248,185,228,140,241,196,75,132,109,24,72,128,43,39,36,136,48,64,114,0,
-250,156,168,1,64,247,175,25,36,2,8,11,94,80,248,16,40,104,242,103,200,48,
-193,3,162,92,4,98,12,41,14,66,40,106,101,1,132,130,8,24,78,104,129,54,62,
-96,224,144,13,238,124,32,2,62,146,60,51,224,120,146,164,140,137,20,0,178,
-58,11,56,192,5,146,208,34,71,64,36,157,25,200,32,52,158,180,8,146,87,129,
-232,217,29,5,156,179,224,116,52,100,191,28,87,62,130,214,9,79,136,104,201,
-126,56,174,127,0,31,255,225,73,82,71,16,13,1,36,230,18,1,164,14,87,71,132,
-0,143,0,210,131,96,31,0,211,6,42,23,50,70,1,167,13,18,14,130,36,67,232,46,
-36,29,4,78,69,6,60,226,31,192,7,255,252,24,160,163,11,23,51,130,56,35,193,
-56,100,238,31,6,150,46,103,4,225,147,143,114,27,62,233,241,200,137,182,133,
-42,142,167,216,6,23,216,0,97,28,17,224,39,223,32,80,142,8,240,78,25,56,9,
-248,8,22,39,12,156,123,144,217,240,19,240,18,6,19,154,32,79,194,124,14,134,
-140,151,227,139,226,52,11,88,37,62,33,163,37,248,226,248,141,32,213,184,64,
-89,56,39,49,224,137,60,100,5,96,38,35,249,8,15,18,61,96,17,60,200,6,145,1,
-17,31,206,64,89,45,2,39,161,0,178,122,209,63,74,2,101,64,202,113,67,77,235,
-64,92,221,197,186,196,143,4,9,19,188,1,25,187,139,112,128,178,113,110,177,
-35,193,2,68,239,0,46,110,229,30,242,71,130,4,137,222,4,35,55,113,110,16,22,
-78,81,239,36,120,32,72,157,224,64,147,138,25,237,0,52,72,242,2,126,82,3,74,
-129,148,227,234,66,12,112,28,140,155,104,203,169,158,9,133,158,4,25,36,1,
-61,96,47,181,80,46,132,129,255,255,255,255,255,255,222,254,39,172,67,118,
-170,5,208,144,0,64,0,0,0,0,0,0,51,16,0,0,0,0,0,0,62,31,200,245,238,146,38,
-138,147,105,13,42,26,137,226,0,0,0,0,0,0,7,131,249,30,180,134,4,209,82,109,
-33,165,67,81,60,64,0,0,0,0,0,0,240,255,28,144,155,104,0,0,0,0,0,0,0,0,16,
-117,59,130,48,155,98,48,187,144,3,205,220,42,46,65,237,72,27,55,112,151,
-123,154,70,205,0,94,208,129,115,119,31,18,9,18,67,155,183,34,12,176,96,175,
-4,100,74,228,3,237,38,43,31,192,109,117,171,0,228,164,219,72,0,0,0,0,0,0,
-248,127,196,234,111,0,50,110,224,193,50,114,83,138,26,107,192,131,38,238,
-77,12,39,37,56,161,166,188,11,132,188,12,74,110,226,220,32,44,156,24,38,78,
-74,113,67,77,120,28,148,221,197,184,64,89,57,52,48,156,148,226,134,154,240,
-64,195,94,8,56,123,193,11,85,116,140,45,240,3,152,147,228,208,194,95,0,89,
-137,62,22,139,95,48,64,70,200,67,28,98,79,180,152,139,218,45,124,193,1,27,
-33,16,65,137,62,49,205,153,236,132,81,102,36,251,73,137,157,115,102,123,33,
-24,57,137,62,12,19,37,144,142,40,196,159,105,49,15,160,153,44,132,128,198,
-36,248,48,98,200,73,18,98,79,180,152,135,208,98,200,74,16,98,79,135,117,35,
-43,33,44,89,137,62,210,98,63,93,72,202,200,76,20,98,79,140,67,105,50,74,
-200,77,26,98,79,180,152,153,212,54,147,36,172,132,225,70,36,249,34,9,205,
-28,172,132,241,166,36,251,73,138,93,32,156,209,202,200,80,30,98,79,140,66,
-214,137,16,78,104,229,100,40,146,49,39,218,76,76,234,22,180,72,130,115,71,
-43,33,72,137,137,62,77,12,38,92,210,113,197,44,137,59,64,7,145,39,201,161,
-132,184,64,249,18,124,98,22,180,72,130,115,71,43,101,76,148,137,62,210,98,
-103,80,181,162,68,19,154,57,91,42,130,164,73,242,68,19,154,57,91,95,84,108,
-137,62,210,98,151,72,39,52,114,182,190,176,169,18,124,98,27,73,146,86,223,
-215,27,34,79,180,152,153,212,54,147,36,173,191,176,34,68,159,14,234,70,86,
-231,217,23,34,79,180,152,143,215,82,50,183,62,208,121,18,124,24,38,75,101,
-108,84,137,62,210,98,31,65,50,91,43,130,36,73,241,142,108,207,109,125,209,
-114,36,251,73,137,157,115,102,123,107,239,11,145,39,194,209,107,230,8,8,
-219,127,124,116,137,62,210,98,47,104,181,243,4,4,109,191,192,135,49,39,204,
-16,17,178,24,32,242,36,249,130,2,54,203,7,6,104,14,76,131,140,144,0,0,0,0,
-0,0,0,1,141,207,215,12,78,126,193,46,190,126,192,98,179,246,4,197,231,236,
-10,193,9,114,11,172,64,73,146,83,236,145,169,237,1,6,120,14,78,129,179,40,
-249,18,149,175,207,141,199,27,76,248,156,81,177,207,139,198,9,169,199,129,
-58,136,19,202,11,179,20,240,149,2,248,72,197,209,200,148,162,117,48,39,148,
-151,102,42,228,64,211,19,132,140,93,28,137,74,39,85,2,121,81,118,98,238,68,
-133,36,72,209,19,132,140,93,28,137,74,39,87,2,121,89,118,98,190,75,13,152,
-47,194,70,46,142,68,165,19,172,129,60,176,187,49,79,39,135,147,132,140,93,
-28,137,74,39,91,2,121,105,118,98,142,210,40,39,99,23,71,34,82,135,8,128,
-120,72,13,42,226,145,97,87,224,168,1,58,182,232,232,64,22,85,181,187,177,
-107,2,64,7,213,183,74,7,121,207,215,242,17,119,49,248,94,173,198,210,36,15,
-232,34,182,84,113,95,115,240,221,91,141,163,160,72,1,220,164,194,175,121,
-123,103,224,186,244,64,24,45,68,84,251,33,9,64,15,217,66,51,209,218,210,
-129,154,118,254,205,61,65,204,126,23,178,132,103,165,3,52,237,253,154,122,
-131,216,252,167,224,121,44,48,46,95,203,166,238,74,113,67,77,201,128,219,
-152,164,82,6,0,203,76,64,64,9,210,211,18,4,4,144,221,49,40,64,76,13,211,19,
-5,4,192,221,45,66,1,4,24,207,76,82,2,8,136,94,152,156,24,157,45,49,64,6,75,
-191,76,80,66,149,110,116,116,197,8,41,240,247,79,70,188,6,183,27,76,80,194,
-45,198,210,211,20,144,171,113,180,116,52,197,40,27,1,125,34,240,27,16,221,
-42,240,27,221,109,66,32,104,129,163,115,52,224,5,139,168,209,233,138,32,57,
-33,186,98,138,18,80,140,244,197,24,28,192,221,49,71,11,56,209,162,211,20,
-183,1,66,188,17,145,52,40,9,148,226,134,153,5,198,137,136,32,14,12,30,164,
-140,144,230,192,0,0,0,0,64,136,211,64,182,120,43,135,126,16,68,52,174,195,
-144,12,2,158,4,128,70,22,24,128,101,67,112,163,192,100,104,176,131,192,99,
-32,176,99,192,226,115,30,1,79,4,68,28,16,54,0,0,41,254,232,116,62,204,7,21,
-35,18,54,127,80,28,192,132,28,32,14,96,197,212,243,193,48,188,240,39,130,
-236,224,175,131,117,2,178,112,145,139,163,145,131,114,70,46,142,218,27,182,
-72,197,209,219,56,26,53,161,166,32,128,56,18,2,129,239,94,50,76,130,68,230,
-202,113,160,167,146,94,163,134,66,161,164,227,226,231,146,51,198,249,147,
-71,209,67,73,210,94,24,49,39,199,89,188,124,92,242,70,120,224,201,33,69,15,
-155,163,191,68,28,98,79,143,139,166,233,225,228,227,139,198,37,210,244,208,
-24,137,112,151,153,27,36,5,100,224,146,105,184,100,196,95,18,84,141,159,9,
-121,145,178,67,155,46,33,38,187,168,252,211,243,81,92,2,14,40,16,50,37,202,
-160,150,154,67,152,148,20,28,76,156,89,26,105,158,63,232,16,44,150,129,18,
-146,44,28,96,14,98,216,80,113,50,113,100,105,166,120,255,160,20,28,76,156,
-113,75,34,78,63,236,3,6,133,41,35,31,242,18,195,152,147,226,27,61,138,41,
-140,16,98,79,148,67,103,177,69,45,136,49,39,196,54,122,58,212,83,26,36,196,
-159,40,134,207,71,90,138,92,16,98,79,136,108,244,244,168,166,56,73,137,62,
-81,13,158,158,149,20,186,40,196,159,10,183,2,122,122,84,82,240,163,18,124,
-42,220,9,235,106,81,75,225,228,73,241,13,158,197,54,198,8,145,39,202,33,
-179,216,166,214,196,72,147,226,27,61,29,106,109,141,19,34,79,148,67,103,
-163,173,77,174,8,145,39,196,54,122,122,84,219,28,38,68,159,40,134,207,79,
-74,155,93,21,34,79,133,91,129,61,61,42,109,120,84,137,62,21,110,4,245,181,
-41,181,248,56,224,28,24,80,113,50,113,100,105,166,120,255,160,20,28,76,156,
-113,75,34,78,63,236,3,6,133,41,35,31,242,11,174,254,160,34,84,8,35,16,98,
-146,38,55,32,33,30,135,19,36,182,158,72,237,17,100,97,27,56,0,0,0,0,0,0,30,
-7,230,56,199,161,30,135,19,36,182,158,72,237,17,100,97,27,56,0,0,0,0,0,0,
-30,7,230,55,36,33,30,135,19,36,182,158,72,237,17,100,97,27,56,0,0,0,0,0,0,
-30,7,234,40,11,91,133,199,172,8,111,248,128,239,88,16,222,56,191,242,49,
-198,69,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,240,63,
-49,185,65,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,240,
-63,49,198,77,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,
-240,63,49,185,97,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,
-0,0,64,49,198,85,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,
-0,0,64,49,185,129,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,
-0,0,0,64,49,198,93,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,
-0,0,0,64,49,185,161,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,
-0,0,0,16,64,49,198,101,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,
-0,0,0,0,16,64,49,185,193,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,
-0,0,0,0,0,16,64,49,198,109,8,244,56,153,37,180,242,71,104,139,35,8,217,192,
-0,0,0,0,0,0,16,64,49,185,225,8,244,56,153,37,180,242,71,104,139,35,8,217,
-192,0,0,0,0,0,0,16,64,49,198,117,8,244,56,153,37,180,242,71,104,139,35,8,
-217,192,0,0,0,0,0,0,16,64,49,186,1,8,244,56,153,37,180,242,71,104,139,35,8,
-217,192,0,0,0,0,0,0,32,64,49,198,125,8,244,56,153,37,180,242,71,104,139,35,
-8,217,192,0,0,0,0,0,0,32,64,32,232,130,0,97,57,162,4,245,72,10,68,184,70,
-137,195,67,77,175,32,66,37,192,208,165,36,117,196,10,14,38,78,44,141,52,
-207,169,64,56,156,199,130,36,160,141,146,52,38,32,76,72,1,246,136,235,103,
-177,69,1,17,32,7,196,54,123,20,82,88,200,144,3,237,17,214,207,71,91,171,37,
-20,65,145,32,7,218,35,173,158,142,183,66,74,41,16,92,72,1,241,13,158,142,
-183,86,74,41,48,92,72,1,241,13,158,142,183,66,74,41,80,100,72,1,246,136,
-235,103,167,165,213,146,138,40,200,144,3,237,17,214,207,79,75,161,37,20,
-138,46,36,0,248,134,207,79,75,171,37,20,154,46,36,0,248,134,207,79,75,161,
-37,20,170,46,36,0,248,85,184,19,234,201,69,24,92,72,1,240,171,112,39,208,
-146,138,70,25,18,0,124,27,168,21,147,171,37,20,113,145,32,7,193,186,129,89,
-58,18,81,72,226,162,64,15,180,71,91,62,172,148,90,0,168,144,3,237,17,214,
-207,161,37,22,144,38,36,0,248,134,207,171,37,22,160,38,36,0,248,134,207,
-161,37,22,176,42,209,68,201,218,35,173,158,197,54,4,218,40,153,56,134,207,
-98,155,75,27,104,162,100,237,17,214,207,71,91,171,37,54,65,182,138,38,78,
-209,29,108,244,117,186,18,83,104,131,45,20,76,156,67,103,163,173,213,146,
-155,76,25,104,162,100,226,27,61,29,110,132,148,218,160,219,69,19,39,104,
-142,182,122,122,93,89,41,178,141,180,81,50,118,136,235,103,167,165,208,146,
-155,69,25,104,162,100,226,27,61,61,46,172,148,218,104,203,69,19,39,16,217,
-233,233,116,36,166,213,70,90,40,153,56,85,184,19,234,201,77,152,101,162,
-137,147,133,91,129,62,132,148,218,48,219,69,19,39,6,234,5,100,234,201,77,
-156,109,162,137,147,131,117,2,178,116,36,166,209,197,218,40,153,59,68,117,
-179,234,201,78,32,11,180,81,50,118,136,235,103,208,146,156,72,21,104,162,
-100,226,27,62,172,148,226,128,171,69,19,39,16,217,244,36,167,22,53,59,22,
-53,91,0,2,21,11,94,181,128,196,133,0,185,80,32,56,156,199,130,36,160,72,16,
-78,126,53,144,5,146,208,34,82,72,1,109,20,76,155,40,32,233,0,115,70,130,8,
-209,56,104,105,187,252,193,3,17,162,112,201,242,18,65,211,0,230,149,132,17,
-162,112,208,211,119,248,0,82,130,96,95,127,128,130,80,102,186,36,232,92,
-206,255,1,80,48,200,39,12,158,241,64,
+18,49,116,118,33,26,113,1,29,164,80,78,198,46,142,212,36,68,51,71,232,59,
+147,60,93,110,79,15,39,9,24,186,33,13,63,111,185,16,211,206,251,114,98,17,
+171,160,11,199,197,215,196,66,26,102,38,68,53,212,77,136,104,255,5,114,120,
+121,7,192,70,32,192,67,95,249,59,13,13,127,228,248,134,191,242,133,208,215,
+254,81,204,67,95,249,75,33,13,127,229,61,84,53,255,149,52,80,215,254,85,
+217,67,95,249,91,121,13,90,181,168,134,143,152,95,38,75,207,132,104,156,50,
+70,33,163,225,66,249,50,94,124,25,4,225,146,49,14,24,28,196,0,0,0,0,0,0,15,
+135,252,204,0,0,0,0,0,0,15,7,252,188,72,6,176,77,225,28,24,103,14,33,197,
+138,113,227,28,152,231,46,65,205,19,194,84,11,225,35,23,68,231,138,228,64,
+211,19,132,140,93,19,162,59,145,33,73,18,52,68,225,35,23,68,233,139,228,
+176,217,130,252,36,98,232,157,81,60,158,30,78,18,49,116,78,184,142,210,40,
+39,99,23,68,236,201,59,114,142,224,126,14,138,152,30,67,188,23,143,139,175,
+131,194,135,228,72,85,144,83,60,53,163,208,76,60,68,211,197,78,60,116,243,
+200,80,60,149,19,202,82,60,181,51,204,84,60,213,83,206,86,60,240,190,76,
+151,159,8,209,56,100,137,232,133,242,100,188,248,50,9,195,36,79,73,26,238,
+108,129,15,4,100,78,33,179,207,160,41,224,140,137,194,173,192,158,120,128,
+168,151,26,14,55,58,64,132,75,133,67,81,50,103,8,18,50,9,195,39,105,20,101,
+136,36,50,9,195,39,105,20,11,174,99,220,210,54,121,114,4,145,162,112,201,
+218,69,25,130,9,17,162,112,201,218,69,2,235,152,247,52,141,158,100,128,196,
+144,128,242,102,136,17,70,146,66,3,201,160,32,0,130,225,48,113,137,62,62,
+46,155,167,135,147,142,47,24,147,79,205,68,48,98,79,142,179,120,248,185,
+228,140,241,193,146,66,138,31,55,71,126,129,51,18,124,117,155,199,197,207,
+36,103,142,52,12,36,184,100,129,129,41,32,205,221,175,3,10,36,4,201,188,64,
+112,200,84,52,156,124,92,242,70,120,223,48,64,100,42,26,78,62,46,121,35,52,
+18,91,212,2,72,128,95,20,128,197,137,9,146,113,73,8,190,36,169,27,62,18,
+243,35,100,135,54,92,66,4,34,92,145,0,178,15,132,64,132,75,133,139,178,70,
+240,137,6,34,92,37,230,70,201,1,89,56,36,4,81,49,46,25,5,76,73,241,214,111,
+31,23,60,145,158,57,44,48,46,92,184,100,160,145,46,2,0,201,168,207,198,230,
+144,117,60,176,48,156,160,48,188,192,7,28,18,227,172,222,62,46,121,35,60,
+113,200,26,137,113,241,116,221,60,60,156,113,121,4,20,124,92,242,70,120,
+226,37,194,54,140,36,64,21,147,146,68,24,32,57,0,125,78,84,0,160,123,215,
+140,146,1,4,5,175,40,124,8,20,52,121,51,228,24,96,129,209,46,2,49,6,20,135,
+33,20,53,50,128,194,65,4,12,39,52,64,155,31,48,112,72,6,247,62,16,1,31,73,
+30,25,240,60,73,82,70,68,138,0,89,29,5,156,96,2,201,104,17,35,160,18,78,
+140,228,16,26,79,90,4,73,43,192,244,108,142,130,206,89,240,58,26,50,95,142,
+43,159,65,107,4,167,196,52,100,191,28,87,63,128,15,255,240,164,169,35,136,
+6,128,146,115,9,0,210,7,43,163,194,0,71,128,105,65,176,15,128,105,131,21,
+11,153,35,0,211,134,137,7,65,18,33,244,23,18,14,130,39,34,131,30,113,15,
+224,3,255,254,12,80,81,133,139,153,193,28,17,224,156,50,119,15,131,75,23,
+51,130,112,201,199,185,13,159,116,248,228,68,219,66,149,83,83,238,3,11,238,
+0,48,142,8,240,19,239,144,40,71,4,120,39,12,156,4,252,4,11,19,134,78,61,
+200,108,248,9,248,9,3,9,205,16,39,225,62,7,67,70,75,241,197,241,154,5,172,
+18,159,16,209,146,252,113,124,102,144,106,220,32,44,156,19,152,240,68,158,
+66,2,176,19,17,252,164,7,137,30,176,8,158,116,3,72,128,136,143,232,32,44,
+150,129,19,210,128,89,61,104,159,169,1,50,160,101,56,161,166,246,160,46,
+110,226,221,98,71,130,4,137,222,0,140,221,197,184,64,89,56,183,88,145,224,
+129,34,119,128,23,55,114,143,121,35,193,2,68,239,2,17,155,184,183,8,11,39,
+40,247,146,60,16,36,78,240,32,73,197,12,247,128,26,36,121,1,63,49,2,165,48,
+70,114,229,145,51,250,205,2,8,209,203,150,68,207,235,52,130,16,209,46,131,
+36,188,70,128,210,160,101,56,251,16,131,28,7,35,38,218,50,234,103,130,97,
+103,129,6,73,0,79,88,11,237,84,11,161,32,127,255,255,255,255,255,247,191,
+137,235,16,221,170,129,116,36,0,16,0,0,0,0,0,0,12,196,0,0,0,0,0,0,15,135,
+242,61,123,164,137,162,164,218,67,74,134,162,120,128,0,0,0,0,0,1,224,254,
+71,173,33,129,52,84,155,72,105,80,212,79,16,0,0,0,0,0,0,60,63,199,36,38,
+218,0,0,0,0,0,0,0,0,4,29,78,224,140,38,216,140,46,228,0,243,119,10,139,144,
+123,82,6,205,220,37,222,230,145,179,64,23,180,32,92,221,199,196,130,68,144,
+230,237,200,131,44,24,43,193,25,18,185,0,251,73,138,199,240,27,93,106,192,
+57,41,54,210,0,0,0,0,0,0,62,31,241,58,155,192,12,155,184,48,76,156,148,226,
+134,154,240,32,201,187,147,67,9,201,78,40,105,175,2,225,47,3,18,155,184,
+183,8,11,39,6,9,147,146,156,80,211,94,7,37,55,113,110,16,22,78,77,12,39,37,
+56,161,166,188,16,48,215,130,14,30,240,66,213,93,35,11,124,0,230,36,249,52,
+48,151,192,22,98,79,133,162,215,204,16,17,178,16,199,24,147,237,38,34,246,
+139,95,48,64,70,200,68,16,98,79,140,115,102,123,33,20,89,137,62,210,98,103,
+92,217,158,200,70,14,98,79,131,4,201,100,35,138,49,39,218,76,67,232,38,75,
+33,32,49,137,62,12,24,178,18,68,152,147,237,38,33,244,24,178,18,132,24,147,
+225,221,72,202,200,75,22,98,79,180,152,143,215,82,50,178,19,5,24,147,227,
+16,218,76,146,178,19,70,152,147,237,38,38,117,13,164,201,43,33,56,81,137,
+62,72,130,115,71,43,33,60,105,137,62,210,98,151,72,39,52,114,178,20,7,152,
+147,227,16,181,162,68,19,154,57,89,10,36,140,73,246,147,19,58,133,173,18,
+32,156,209,202,200,82,34,98,79,147,67,9,151,52,156,113,75,34,78,208,1,228,
+73,242,104,97,46,16,62,68,159,24,133,173,18,32,156,209,202,217,83,37,34,79,
+180,152,153,212,45,104,145,4,230,142,86,202,160,169,18,124,145,4,230,142,
+86,215,213,27,34,79,180,152,165,210,9,205,28,173,175,172,42,68,159,24,134,
+210,100,149,183,245,198,200,147,237,38,38,117,13,164,201,43,111,236,8,145,
+39,195,186,145,149,185,246,69,200,147,237,38,35,245,212,140,173,207,180,30,
+68,159,6,9,146,217,91,21,34,79,180,152,135,208,76,150,202,224,137,18,124,
+99,155,51,219,95,116,92,137,62,210,98,103,92,217,158,218,251,194,228,73,
+240,180,90,249,130,2,54,223,223,29,34,79,180,152,139,218,45,124,193,1,27,
+111,240,33,204,73,243,4,4,108,134,8,60,137,62,96,128,141,178,193,193,154,3,
+147,32,227,36,0,0,0,0,0,0,0,0,99,115,245,195,19,159,176,75,175,159,176,24,
+172,253,129,49,121,251,2,176,66,92,130,235,16,18,100,148,251,36,106,123,64,
+65,158,3,147,160,108,202,62,68,165,107,243,227,113,198,211,62,39,20,108,
+115,226,241,130,106,113,224,78,162,4,242,130,236,197,60,37,64,190,18,49,
+116,114,37,40,157,76,9,229,37,217,138,185,16,52,196,225,35,23,71,34,82,137,
+213,64,158,84,93,152,187,145,33,73,18,52,68,225,35,23,71,34,82,137,213,192,
+158,86,93,152,175,146,195,102,11,240,145,139,163,145,41,68,235,32,79,44,46,
+204,83,201,225,228,225,35,23,71,34,82,137,214,192,158,90,93,152,163,180,
+138,9,216,197,209,200,148,161,194,32,30,18,3,74,184,164,88,85,248,42,0,78,
+173,186,58,16,5,149,109,110,236,90,192,144,1,245,109,210,129,222,115,245,
+252,132,93,204,126,23,171,113,180,137,3,250,8,173,149,28,87,220,252,55,86,
+227,104,232,18,0,119,41,48,171,222,94,217,248,46,189,16,6,11,81,21,62,200,
+66,80,3,246,80,140,244,118,180,160,102,157,191,179,79,80,115,31,133,236,
+161,25,233,64,205,59,127,102,158,160,246,63,41,248,30,75,12,11,151,242,233,
+187,146,156,80,211,114,96,54,230,41,20,129,128,50,211,16,16,2,116,180,196,
+129,1,36,55,76,74,16,19,3,116,196,193,65,48,55,75,80,128,65,6,51,211,20,
+128,130,34,23,166,39,6,39,75,76,80,1,146,239,211,20,16,165,91,157,29,49,66,
+10,124,61,211,209,175,1,173,198,211,20,48,139,113,180,180,197,36,42,220,
+109,29,13,49,74,6,192,95,72,188,6,196,55,74,188,6,247,91,80,136,26,32,104,
+220,205,56,1,98,234,52,122,98,136,14,72,110,152,162,132,148,35,61,49,70,7,
+48,55,76,81,194,206,52,104,180,197,45,192,80,175,4,100,77,10,2,101,56,161,
+166,65,113,162,98,8,3,131,7,169,35,36,57,176,0,0,0,0,0,40,116,208,45,158,
+10,225,223,132,17,13,43,176,228,3,0,167,129,32,17,133,134,32,25,80,220,40,
+240,25,26,44,32,240,24,200,44,24,240,56,156,199,128,83,193,17,7,4,13,128,0,
+10,79,202,28,223,195,1,197,72,196,141,159,220,7,48,33,7,8,3,152,49,117,60,
+240,76,47,60,9,224,187,56,43,224,221,64,172,156,36,98,232,228,96,220,145,
+139,163,182,134,237,146,49,116,118,206,6,141,104,105,136,32,14,4,128,160,
+123,215,140,147,32,145,57,178,156,104,41,228,151,168,225,144,168,105,56,
+248,185,228,140,241,190,100,209,244,80,210,116,151,134,12,73,241,214,111,
+31,23,60,145,158,56,50,72,81,67,230,232,239,209,7,24,147,227,226,233,186,
+120,121,56,226,241,137,116,189,52,6,34,92,37,230,70,201,1,89,56,36,154,110,
+25,49,23,196,149,35,103,194,94,100,108,144,230,203,136,73,174,234,63,52,
+252,212,87,0,131,138,4,12,137,114,168,37,166,144,230,37,5,7,19,39,22,70,
+154,103,143,252,4,11,37,160,68,164,139,7,24,3,152,182,20,28,76,156,89,26,
+105,158,63,240,5,7,19,39,28,82,200,147,143,253,0,193,161,74,72,199,253,132,
+176,230,36,248,134,207,98,138,99,4,24,147,229,16,217,236,81,75,98,12,73,
+241,13,158,142,181,20,198,137,49,39,202,33,179,209,214,162,151,4,24,147,
+226,27,61,61,42,41,142,18,98,79,148,67,103,167,165,69,46,138,49,39,194,173,
+192,158,158,149,20,188,40,196,159,10,183,2,122,218,148,82,248,121,18,124,
+67,103,177,77,177,130,36,73,242,136,108,246,41,181,177,18,36,248,134,207,
+71,90,155,99,68,200,147,229,16,217,232,235,83,107,130,36,73,241,13,158,158,
+149,54,199,9,145,39,202,33,179,211,210,166,215,69,72,147,225,86,224,79,79,
+74,155,94,21,34,79,133,91,129,61,109,74,109,126,14,56,7,6,20,28,76,156,89,
+26,105,158,63,240,5,7,19,39,28,82,200,147,143,253,0,193,161,74,72,199,253,
+130,235,191,232,8,149,2,8,196,24,164,137,141,200,8,71,161,196,201,45,167,
+146,59,68,89,24,70,206,0,0,0,0,0,0,7,129,249,142,49,232,71,161,196,201,45,
+167,146,59,68,89,24,70,206,0,0,0,0,0,0,7,129,249,141,201,8,71,161,196,201,
+45,167,146,59,68,89,24,70,206,0,0,0,0,0,0,7,129,250,138,2,214,225,113,235,
+2,27,128,0,10,66,3,189,96,67,120,226,224,0,2,148,140,113,145,66,61,14,38,
+73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,60,15,204,110,80,66,61,14,
+38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,60,15,204,113,147,66,61,
+14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,60,15,204,110,88,66,
+61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,0,16,12,113,149,
+66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,0,16,12,110,96,
+66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,0,16,12,113,
+151,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,0,16,12,
+110,104,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,4,16,
+12,113,153,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,4,
+16,12,110,112,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,0,
+4,16,12,113,155,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,0,
+0,4,16,12,110,120,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,0,
+0,0,4,16,12,113,157,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,
+0,0,0,4,16,12,110,128,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,
+0,0,0,0,8,16,12,113,159,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,
+0,0,0,0,0,8,16,8,58,32,128,24,78,104,129,61,82,2,145,46,17,162,112,208,211,
+107,200,16,137,112,52,41,73,29,113,2,131,137,147,139,35,77,51,234,80,14,39,
+49,224,137,40,35,100,141,9,136,19,18,0,125,162,58,217,236,81,64,68,72,1,
+241,13,158,197,20,150,50,36,0,251,68,117,179,209,214,234,201,69,16,100,72,
+1,246,136,235,103,163,173,208,146,138,68,23,18,0,124,67,103,163,173,213,
+146,138,76,23,18,0,124,67,103,163,173,208,146,138,84,25,18,0,125,162,58,
+217,233,233,117,100,162,138,50,36,0,251,68,117,179,211,210,232,73,69,34,
+139,137,0,62,33,179,211,210,234,201,69,38,139,137,0,62,33,179,211,210,232,
+73,69,42,139,137,0,62,21,110,4,250,178,81,70,23,18,0,124,42,220,9,244,36,
+162,145,134,68,128,31,6,234,5,100,234,201,69,28,100,72,1,240,110,160,86,78,
+132,148,82,56,168,144,3,237,17,214,207,171,37,22,128,42,36,0,251,68,117,
+179,232,73,69,164,9,137,0,62,33,179,234,201,69,168,9,137,0,62,33,179,232,
+73,69,172,10,180,81,50,118,136,235,103,177,77,129,54,138,38,78,33,179,216,
+166,210,198,218,40,153,59,68,117,179,209,214,234,201,77,144,109,162,137,
+147,180,71,91,61,29,110,132,148,218,32,203,69,19,39,16,217,232,235,117,100,
+166,211,6,90,40,153,56,134,207,71,91,161,37,54,168,54,209,68,201,218,35,
+173,158,158,151,86,74,108,163,109,20,76,157,162,58,217,233,233,116,36,166,
+209,70,90,40,153,56,134,207,79,75,171,37,54,154,50,209,68,201,196,54,122,
+122,93,9,41,181,81,150,138,38,78,21,110,4,250,178,83,102,25,104,162,100,
+225,86,224,79,161,37,54,140,54,209,68,201,193,186,129,89,58,178,83,103,27,
+104,162,100,224,221,64,172,157,9,41,180,113,118,138,38,78,209,29,108,250,
+178,83,136,2,237,20,76,157,162,58,217,244,36,167,18,5,90,40,153,56,134,207,
+171,37,56,160,42,209,68,201,196,54,125,9,41,197,141,78,197,141,86,192,0,
+133,66,215,173,96,49,33,64,46,84,8,14,39,49,224,137,40,18,4,19,159,141,100,
+1,100,180,8,148,146,0,91,69,19,38,202,8,58,64,28,209,160,130,52,78,26,26,
+110,255,80,64,196,104,156,50,125,4,144,116,192,57,165,97,4,104,156,52,52,
+221,254,64,20,160,152,23,223,228,32,148,25,174,137,58,23,51,191,200,84,12,
+50,9,195,39,196,80,
 };
 #elif defined(DUK_USE_DOUBLE_BE)
-DUK_INTERNAL const duk_uint8_t duk_builtins_data[3790] = {
+DUK_INTERNAL const duk_uint8_t duk_builtins_data[3819] = {
 144,148,105,221,32,68,52,228,62,12,104,200,165,134,148,248,81,77,61,191,
 135,35,154,103,34,72,6,157,159,197,145,77,245,126,52,130,106,234,163,196,
 52,226,18,51,161,26,113,1,60,37,64,190,18,49,116,116,33,26,113,1,92,136,26,
 98,112,145,139,163,165,8,211,136,14,228,72,82,68,141,17,56,72,197,209,212,
 132,105,196,5,242,88,108,193,126,18,49,116,117,161,26,113,1,60,158,30,78,
-18,49,116,118,33,26,113,1,29,164,80,78,198,46,142,212,36,68,51,71,224,59,
-147,60,93,110,79,15,39,9,24,186,33,13,63,79,185,39,26,121,223,110,77,66,53,
-116,1,120,248,186,248,136,67,76,196,200,134,186,137,177,13,31,192,174,79,
-15,32,248,8,196,24,8,107,254,39,97,161,175,248,159,16,215,252,80,186,26,
-255,138,57,136,107,254,41,100,33,175,248,167,170,134,191,226,166,138,26,
-255,138,187,40,107,254,43,111,33,171,86,181,16,209,241,11,228,201,121,240,
-141,19,134,72,196,52,123,168,95,38,75,207,131,32,156,50,70,33,195,3,152,
-128,255,240,0,0,0,0,0,1,153,128,255,224,0,0,0,0,0,1,151,137,0,214,9,188,35,
-131,12,225,196,56,177,78,60,99,147,28,229,200,57,162,120,74,129,124,36,98,
-232,156,241,92,136,26,98,112,145,139,162,116,71,114,36,41,34,70,136,156,36,
-98,232,157,49,124,150,27,48,95,132,140,93,19,170,39,147,195,201,194,70,46,
-137,215,17,218,69,4,236,98,232,157,153,39,110,81,220,15,193,209,83,3,200,
-119,130,241,241,117,240,120,80,252,137,10,178,10,103,134,180,122,9,135,136,
-154,120,169,199,142,158,121,10,7,146,162,121,74,71,150,166,121,138,135,154,
-170,121,202,199,158,23,201,146,243,225,26,39,12,145,61,16,190,76,151,159,6,
-65,56,100,137,233,35,93,205,144,33,224,140,137,196,54,121,244,5,60,17,145,
-56,85,184,19,207,16,21,18,227,65,198,231,72,16,137,112,168,106,38,76,225,2,
-70,65,56,100,237,34,140,177,4,134,65,56,100,237,34,129,117,204,123,154,70,
-207,46,64,146,52,78,25,59,72,163,48,65,34,52,78,25,59,72,160,93,115,30,230,
-145,179,204,144,24,146,16,30,76,209,2,40,210,72,64,121,52,4,0,156,88,97,5,
-194,96,227,18,124,124,93,55,79,15,39,28,94,49,38,159,154,136,96,196,159,29,
-102,241,241,115,201,25,227,131,36,133,20,62,110,142,253,2,102,36,248,235,
-55,143,139,158,72,207,28,104,24,73,112,201,3,2,82,65,155,187,94,6,20,72,9,
-147,120,128,225,144,168,105,56,248,185,228,140,241,190,96,128,200,84,52,
-156,124,92,242,70,104,36,183,168,4,145,0,190,41,1,139,18,19,36,226,146,17,
-124,73,82,54,124,37,230,70,201,14,108,184,132,8,68,185,34,1,100,31,8,129,8,
-151,11,23,100,141,225,18,12,68,184,75,204,141,146,2,178,112,72,8,162,98,92,
-50,10,152,147,227,172,222,62,46,121,35,60,114,88,96,92,185,112,201,65,34,
-92,4,1,147,81,159,141,205,32,234,121,96,97,57,64,97,121,128,14,56,37,199,
-89,188,124,92,242,70,120,227,144,53,18,227,226,233,186,120,121,56,226,242,
-8,40,248,185,228,140,241,196,75,132,109,24,72,128,43,39,36,136,48,64,114,0,
-250,156,168,1,64,247,175,25,36,2,8,11,94,80,248,16,40,104,242,103,200,48,
-193,3,162,92,4,98,12,41,14,66,40,106,101,1,132,130,8,24,78,104,129,54,62,
-96,224,144,13,238,124,32,2,62,146,60,51,224,120,146,164,140,137,20,0,178,
-58,11,56,192,5,146,208,34,71,64,36,157,25,200,32,52,158,180,8,146,87,129,
-232,217,29,5,156,179,224,116,52,100,191,28,87,62,130,214,9,79,136,104,201,
-126,56,174,127,0,31,255,225,73,82,71,16,13,1,36,230,18,1,164,14,87,71,132,
-0,143,0,210,131,96,31,0,211,6,42,23,50,70,1,167,13,18,14,130,36,67,232,46,
-36,29,4,78,69,6,60,226,31,192,7,255,252,24,160,163,11,23,51,130,56,35,193,
-56,100,238,31,6,150,46,103,4,225,147,143,114,27,62,233,241,200,137,182,133,
-42,142,167,216,6,23,216,0,97,28,17,224,39,223,32,80,142,8,240,78,25,56,9,
-248,8,22,39,12,156,123,144,217,240,19,240,18,6,19,154,32,79,194,124,14,134,
-140,151,227,139,226,52,11,88,37,62,33,163,37,248,226,248,141,32,213,184,64,
-89,56,39,49,224,137,60,100,5,96,38,35,249,8,15,18,61,96,17,60,200,6,145,1,
-17,31,206,64,89,45,2,39,161,0,178,122,209,63,74,2,101,64,202,113,67,77,235,
-64,92,221,197,186,196,143,4,9,19,188,1,25,187,139,112,128,178,113,110,177,
-35,193,2,68,239,0,46,110,229,30,242,71,130,4,137,222,4,35,55,113,110,16,22,
-78,81,239,36,120,32,72,157,224,64,147,138,25,237,0,52,72,242,2,126,82,3,74,
-129,148,227,234,66,12,112,28,140,155,104,203,169,158,9,133,158,4,25,36,1,
-61,96,47,181,80,46,132,128,255,223,255,255,255,255,255,254,39,172,67,118,
-170,5,208,144,0,0,0,0,0,0,0,0,115,16,31,254,0,0,0,0,0,0,8,245,238,146,38,
-138,147,105,13,42,26,137,226,3,255,128,0,0,0,0,0,1,30,180,134,4,209,82,109,
-33,165,67,81,60,64,255,240,0,0,0,0,0,0,28,144,155,104,0,0,0,0,0,0,0,0,16,
-117,59,130,48,155,98,48,187,144,3,205,220,42,46,65,237,72,27,55,112,151,
-123,154,70,205,0,94,208,129,115,119,31,18,9,18,67,155,183,34,12,176,96,175,
-4,100,74,228,3,237,38,43,31,192,109,117,171,0,228,164,219,72,127,248,0,0,0,
-0,0,0,196,234,111,0,50,110,224,193,50,114,83,138,26,107,192,131,38,238,77,
-12,39,37,56,161,166,188,11,132,188,12,74,110,226,220,32,44,156,24,38,78,74,
-113,67,77,120,28,148,221,197,184,64,89,57,52,48,156,148,226,134,154,240,64,
-195,94,8,56,123,193,11,85,116,140,45,240,3,152,147,228,208,194,95,0,89,137,
-62,22,139,95,48,64,70,200,67,28,98,79,180,152,139,218,45,124,193,1,27,33,
-16,65,137,62,49,205,153,236,132,81,102,36,251,73,137,157,115,102,123,33,24,
-57,137,62,12,19,37,144,142,40,196,159,105,49,15,160,153,44,132,128,198,36,
-248,48,98,200,73,18,98,79,180,152,135,208,98,200,74,16,98,79,135,117,35,43,
-33,44,89,137,62,210,98,63,93,72,202,200,76,20,98,79,140,67,105,50,74,200,
-77,26,98,79,180,152,153,212,54,147,36,172,132,225,70,36,249,34,9,205,28,
-172,132,241,166,36,251,73,138,93,32,156,209,202,200,80,30,98,79,140,66,214,
-137,16,78,104,229,100,40,146,49,39,218,76,76,234,22,180,72,130,115,71,43,
-33,72,137,137,62,77,12,38,92,210,113,197,44,137,59,64,7,145,39,201,161,132,
-184,64,249,18,124,98,22,180,72,130,115,71,43,101,76,148,137,62,210,98,103,
-80,181,162,68,19,154,57,91,42,130,164,73,242,68,19,154,57,91,95,84,108,137,
-62,210,98,151,72,39,52,114,182,190,176,169,18,124,98,27,73,146,86,223,215,
-27,34,79,180,152,153,212,54,147,36,173,191,176,34,68,159,14,234,70,86,231,
-217,23,34,79,180,152,143,215,82,50,183,62,208,121,18,124,24,38,75,101,108,
-84,137,62,210,98,31,65,50,91,43,130,36,73,241,142,108,207,109,125,209,114,
-36,251,73,137,157,115,102,123,107,239,11,145,39,194,209,107,230,8,8,219,
-127,124,116,137,62,210,98,47,104,181,243,4,4,109,191,192,135,49,39,204,16,
-17,178,24,32,242,36,249,130,2,54,203,7,6,104,14,76,131,140,144,0,0,0,0,0,0,
-0,1,141,207,215,12,78,126,193,46,190,126,192,98,179,246,4,197,231,236,10,
-193,9,114,11,172,64,73,146,83,236,145,169,237,1,6,120,14,78,129,179,40,249,
-18,149,175,207,141,199,27,76,248,156,81,177,207,139,198,9,169,199,129,58,
-136,19,202,11,179,20,240,149,2,248,72,197,209,200,148,162,117,48,39,148,
-151,102,42,228,64,211,19,132,140,93,28,137,74,39,85,2,121,81,118,98,238,68,
-133,36,72,209,19,132,140,93,28,137,74,39,87,2,121,89,118,98,190,75,13,152,
-47,194,70,46,142,68,165,19,172,129,60,176,187,49,79,39,135,147,132,140,93,
-28,137,74,39,91,2,121,105,118,98,142,210,40,39,99,23,71,34,82,135,8,128,
-120,72,8,0,183,225,81,98,138,237,33,58,182,232,232,64,64,2,107,177,187,181,
-85,22,7,213,183,74,1,255,49,114,23,247,209,207,120,94,173,198,210,36,3,255,
-113,84,118,82,184,47,224,221,91,141,163,160,72,7,251,121,111,98,164,220,
-161,192,186,244,64,64,9,33,251,84,68,45,24,15,217,66,51,209,218,210,128,
-127,205,65,60,204,254,119,154,23,178,132,103,165,0,255,218,130,121,153,252,
-239,52,167,224,121,44,48,46,95,203,166,238,74,113,67,77,201,128,219,152,
-164,82,6,0,203,76,64,64,9,210,211,18,4,4,144,221,49,40,64,76,13,211,19,5,4,
-192,221,45,66,1,4,24,207,76,82,2,8,136,94,152,156,24,157,45,49,64,6,75,191,
-76,80,66,149,110,116,116,197,8,41,240,247,79,70,188,6,183,27,76,80,194,45,
-198,210,211,20,144,171,113,180,116,52,197,40,27,1,125,34,240,27,16,221,42,
-240,27,221,109,66,32,104,129,163,115,52,224,5,139,168,209,233,138,32,57,33,
-186,98,138,18,80,140,244,197,24,28,192,221,49,71,11,56,209,162,211,20,183,
-1,66,188,17,145,52,40,9,148,226,134,153,5,198,137,136,32,14,12,30,164,140,
-144,230,192,64,211,136,64,0,0,0,0,182,120,43,135,126,16,68,52,174,195,144,
-12,2,158,4,128,70,22,24,128,101,67,112,163,192,100,104,176,131,192,99,32,
-176,99,192,226,115,30,1,79,4,68,28,16,54,0,0,41,254,232,116,62,204,7,21,35,
-18,54,127,80,28,192,132,28,32,14,96,197,212,243,193,48,188,240,39,130,236,
-224,175,131,117,2,178,112,145,139,163,145,131,114,70,46,142,218,27,182,72,
-197,209,219,56,26,53,161,166,32,128,56,18,2,129,239,94,50,76,130,68,230,
-202,113,160,167,146,94,163,134,66,161,164,227,226,231,146,51,198,249,147,
-71,209,67,73,210,94,24,49,39,199,89,188,124,92,242,70,120,224,201,33,69,15,
-155,163,191,68,28,98,79,143,139,166,233,225,228,227,139,198,37,210,244,208,
-24,137,112,151,153,27,36,5,100,224,146,105,184,100,196,95,18,84,141,159,9,
-121,145,178,67,155,46,33,38,187,168,252,211,243,81,92,2,14,40,16,50,37,202,
-160,150,154,67,152,148,20,28,76,156,89,26,105,158,63,232,16,44,150,129,18,
-146,44,28,96,14,98,216,80,113,50,113,100,105,166,120,255,160,20,28,76,156,
-113,75,34,78,63,236,3,6,133,41,35,31,242,18,195,152,147,226,27,61,138,41,
-140,16,98,79,148,67,103,177,69,45,136,49,39,196,54,122,58,212,83,26,36,196,
-159,40,134,207,71,90,138,92,16,98,79,136,108,244,244,168,166,56,73,137,62,
-81,13,158,158,149,20,186,40,196,159,10,183,2,122,122,84,82,240,163,18,124,
-42,220,9,235,106,81,75,225,228,73,241,13,158,197,54,198,8,145,39,202,33,
-179,216,166,214,196,72,147,226,27,61,29,106,109,141,19,34,79,148,67,103,
-163,173,77,174,8,145,39,196,54,122,122,84,219,28,38,68,159,40,134,207,79,
-74,155,93,21,34,79,133,91,129,61,61,42,109,120,84,137,62,21,110,4,245,181,
-41,181,248,56,224,28,24,80,113,50,113,100,105,166,120,255,160,20,28,76,156,
-113,75,34,78,63,236,3,6,133,41,35,31,242,11,174,254,160,34,84,8,35,16,98,
-146,38,55,32,33,30,135,19,36,182,158,72,237,17,100,97,27,56,7,254,0,0,0,0,
-0,0,6,56,199,161,30,135,19,36,182,158,72,237,17,100,97,27,56,7,254,0,0,0,0,
-0,0,6,55,36,33,30,135,19,36,182,158,72,237,17,100,97,27,56,7,254,0,0,0,0,0,
-0,10,40,11,91,133,199,172,8,111,248,128,239,88,16,222,56,191,242,49,198,69,
-8,244,56,153,37,180,242,71,104,139,35,8,217,192,63,240,0,0,0,0,0,0,49,185,
-65,8,244,56,153,37,180,242,71,104,139,35,8,217,192,63,240,0,0,0,0,0,0,49,
-198,77,8,244,56,153,37,180,242,71,104,139,35,8,217,192,63,240,0,0,0,0,0,0,
-49,185,97,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,0,0,0,0,0,0,0,
-49,198,85,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,0,0,0,0,0,0,0,
-49,185,129,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,0,0,0,0,0,0,
-0,49,198,93,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,0,0,0,0,0,0,
-0,49,185,161,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,0,0,0,
-0,0,49,198,101,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,0,0,
-0,0,0,49,185,193,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,0,
-0,0,0,0,49,198,109,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,
-0,0,0,0,0,49,185,225,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,
-0,0,0,0,0,0,49,198,117,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,
-16,0,0,0,0,0,0,49,186,1,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,
-32,0,0,0,0,0,0,49,198,125,8,244,56,153,37,180,242,71,104,139,35,8,217,192,
-64,32,0,0,0,0,0,0,32,232,130,0,97,57,162,4,245,72,10,68,184,70,137,195,67,
-77,175,32,66,37,192,208,165,36,117,196,10,14,38,78,44,141,52,207,169,64,56,
-156,199,130,36,160,141,146,52,38,32,76,72,1,246,136,235,103,177,69,1,17,32,
-7,196,54,123,20,82,88,200,144,3,237,17,214,207,71,91,171,37,20,65,145,32,7,
-218,35,173,158,142,183,66,74,41,16,92,72,1,241,13,158,142,183,86,74,41,48,
-92,72,1,241,13,158,142,183,66,74,41,80,100,72,1,246,136,235,103,167,165,
-213,146,138,40,200,144,3,237,17,214,207,79,75,161,37,20,138,46,36,0,248,
-134,207,79,75,171,37,20,154,46,36,0,248,134,207,79,75,161,37,20,170,46,36,
-0,248,85,184,19,234,201,69,24,92,72,1,240,171,112,39,208,146,138,70,25,18,
-0,124,27,168,21,147,171,37,20,113,145,32,7,193,186,129,89,58,18,81,72,226,
-162,64,15,180,71,91,62,172,148,90,0,168,144,3,237,17,214,207,161,37,22,144,
-38,36,0,248,134,207,171,37,22,160,38,36,0,248,134,207,161,37,22,176,42,209,
-68,201,218,35,173,158,197,54,4,218,40,153,56,134,207,98,155,75,27,104,162,
-100,237,17,214,207,71,91,171,37,54,65,182,138,38,78,209,29,108,244,117,186,
-18,83,104,131,45,20,76,156,67,103,163,173,213,146,155,76,25,104,162,100,
-226,27,61,29,110,132,148,218,160,219,69,19,39,104,142,182,122,122,93,89,41,
-178,141,180,81,50,118,136,235,103,167,165,208,146,155,69,25,104,162,100,
-226,27,61,61,46,172,148,218,104,203,69,19,39,16,217,233,233,116,36,166,213,
-70,90,40,153,56,85,184,19,234,201,77,152,101,162,137,147,133,91,129,62,132,
-148,218,48,219,69,19,39,6,234,5,100,234,201,77,156,109,162,137,147,131,117,
-2,178,116,36,166,209,197,218,40,153,59,68,117,179,234,201,78,32,11,180,81,
-50,118,136,235,103,208,146,156,72,21,104,162,100,226,27,62,172,148,226,128,
-171,69,19,39,16,217,244,36,167,22,53,59,22,53,91,0,2,21,11,94,181,128,196,
-133,0,185,80,32,56,156,199,130,36,160,72,16,78,126,53,144,5,146,208,34,82,
-72,1,109,20,76,155,40,32,233,0,115,70,130,8,209,56,104,105,187,252,193,3,
-17,162,112,201,242,18,65,211,0,230,149,132,17,162,112,208,211,119,248,0,82,
-130,96,95,127,128,130,80,102,186,36,232,92,206,255,1,80,48,200,39,12,158,
-241,64,
+18,49,116,118,33,26,113,1,29,164,80,78,198,46,142,212,36,68,51,71,232,59,
+147,60,93,110,79,15,39,9,24,186,33,13,63,111,185,16,211,206,251,114,98,17,
+171,160,11,199,197,215,196,66,26,102,38,68,53,212,77,136,104,255,5,114,120,
+121,7,192,70,32,192,67,95,249,59,13,13,127,228,248,134,191,242,133,208,215,
+254,81,204,67,95,249,75,33,13,127,229,61,84,53,255,149,52,80,215,254,85,
+217,67,95,249,91,121,13,90,181,168,134,143,152,95,38,75,207,132,104,156,50,
+70,33,163,225,66,249,50,94,124,25,4,225,146,49,14,24,28,196,7,255,128,0,0,
+0,0,0,12,204,7,255,0,0,0,0,0,0,12,188,72,6,176,77,225,28,24,103,14,33,197,
+138,113,227,28,152,231,46,65,205,19,194,84,11,225,35,23,68,231,138,228,64,
+211,19,132,140,93,19,162,59,145,33,73,18,52,68,225,35,23,68,233,139,228,
+176,217,130,252,36,98,232,157,81,60,158,30,78,18,49,116,78,184,142,210,40,
+39,99,23,68,236,201,59,114,142,224,126,14,138,152,30,67,188,23,143,139,175,
+131,194,135,228,72,85,144,83,60,53,163,208,76,60,68,211,197,78,60,116,243,
+200,80,60,149,19,202,82,60,181,51,204,84,60,213,83,206,86,60,240,190,76,
+151,159,8,209,56,100,137,232,133,242,100,188,248,50,9,195,36,79,73,26,238,
+108,129,15,4,100,78,33,179,207,160,41,224,140,137,194,173,192,158,120,128,
+168,151,26,14,55,58,64,132,75,133,67,81,50,103,8,18,50,9,195,39,105,20,101,
+136,36,50,9,195,39,105,20,11,174,99,220,210,54,121,114,4,145,162,112,201,
+218,69,25,130,9,17,162,112,201,218,69,2,235,152,247,52,141,158,100,128,196,
+144,128,242,102,136,17,70,146,66,3,201,160,32,0,130,225,48,113,137,62,62,
+46,155,167,135,147,142,47,24,147,79,205,68,48,98,79,142,179,120,248,185,
+228,140,241,193,146,66,138,31,55,71,126,129,51,18,124,117,155,199,197,207,
+36,103,142,52,12,36,184,100,129,129,41,32,205,221,175,3,10,36,4,201,188,64,
+112,200,84,52,156,124,92,242,70,120,223,48,64,100,42,26,78,62,46,121,35,52,
+18,91,212,2,72,128,95,20,128,197,137,9,146,113,73,8,190,36,169,27,62,18,
+243,35,100,135,54,92,66,4,34,92,145,0,178,15,132,64,132,75,133,139,178,70,
+240,137,6,34,92,37,230,70,201,1,89,56,36,4,81,49,46,25,5,76,73,241,214,111,
+31,23,60,145,158,57,44,48,46,92,184,100,160,145,46,2,0,201,168,207,198,230,
+144,117,60,176,48,156,160,48,188,192,7,28,18,227,172,222,62,46,121,35,60,
+113,200,26,137,113,241,116,221,60,60,156,113,121,4,20,124,92,242,70,120,
+226,37,194,54,140,36,64,21,147,146,68,24,32,57,0,125,78,84,0,160,123,215,
+140,146,1,4,5,175,40,124,8,20,52,121,51,228,24,96,129,209,46,2,49,6,20,135,
+33,20,53,50,128,194,65,4,12,39,52,64,155,31,48,112,72,6,247,62,16,1,31,73,
+30,25,240,60,73,82,70,68,138,0,89,29,5,156,96,2,201,104,17,35,160,18,78,
+140,228,16,26,79,90,4,73,43,192,244,108,142,130,206,89,240,58,26,50,95,142,
+43,159,65,107,4,167,196,52,100,191,28,87,63,128,15,255,240,164,169,35,136,
+6,128,146,115,9,0,210,7,43,163,194,0,71,128,105,65,176,15,128,105,131,21,
+11,153,35,0,211,134,137,7,65,18,33,244,23,18,14,130,39,34,131,30,113,15,
+224,3,255,254,12,80,81,133,139,153,193,28,17,224,156,50,119,15,131,75,23,
+51,130,112,201,199,185,13,159,116,248,228,68,219,66,149,83,83,238,3,11,238,
+0,48,142,8,240,19,239,144,40,71,4,120,39,12,156,4,252,4,11,19,134,78,61,
+200,108,248,9,248,9,3,9,205,16,39,225,62,7,67,70,75,241,197,241,154,5,172,
+18,159,16,209,146,252,113,124,102,144,106,220,32,44,156,19,152,240,68,158,
+66,2,176,19,17,252,164,7,137,30,176,8,158,116,3,72,128,136,143,232,32,44,
+150,129,19,210,128,89,61,104,159,169,1,50,160,101,56,161,166,246,160,46,
+110,226,221,98,71,130,4,137,222,0,140,221,197,184,64,89,56,183,88,145,224,
+129,34,119,128,23,55,114,143,121,35,193,2,68,239,2,17,155,184,183,8,11,39,
+40,247,146,60,16,36,78,240,32,73,197,12,247,128,26,36,121,1,63,49,2,165,48,
+70,114,229,145,51,250,205,2,8,209,203,150,68,207,235,52,130,16,209,46,131,
+36,188,70,128,210,160,101,56,251,16,131,28,7,35,38,218,50,234,103,130,97,
+103,129,6,73,0,79,88,11,237,84,11,161,32,63,247,255,255,255,255,255,255,
+137,235,16,221,170,129,116,36,0,0,0,0,0,0,0,0,28,196,7,255,128,0,0,0,0,0,2,
+61,123,164,137,162,164,218,67,74,134,162,120,128,255,224,0,0,0,0,0,0,71,
+173,33,129,52,84,155,72,105,80,212,79,16,63,252,0,0,0,0,0,0,7,36,38,218,0,
+0,0,0,0,0,0,0,4,29,78,224,140,38,216,140,46,228,0,243,119,10,139,144,123,
+82,6,205,220,37,222,230,145,179,64,23,180,32,92,221,199,196,130,68,144,230,
+237,200,131,44,24,43,193,25,18,185,0,251,73,138,199,240,27,93,106,192,57,
+41,54,210,31,254,0,0,0,0,0,0,49,58,155,192,12,155,184,48,76,156,148,226,
+134,154,240,32,201,187,147,67,9,201,78,40,105,175,2,225,47,3,18,155,184,
+183,8,11,39,6,9,147,146,156,80,211,94,7,37,55,113,110,16,22,78,77,12,39,37,
+56,161,166,188,16,48,215,130,14,30,240,66,213,93,35,11,124,0,230,36,249,52,
+48,151,192,22,98,79,133,162,215,204,16,17,178,16,199,24,147,237,38,34,246,
+139,95,48,64,70,200,68,16,98,79,140,115,102,123,33,20,89,137,62,210,98,103,
+92,217,158,200,70,14,98,79,131,4,201,100,35,138,49,39,218,76,67,232,38,75,
+33,32,49,137,62,12,24,178,18,68,152,147,237,38,33,244,24,178,18,132,24,147,
+225,221,72,202,200,75,22,98,79,180,152,143,215,82,50,178,19,5,24,147,227,
+16,218,76,146,178,19,70,152,147,237,38,38,117,13,164,201,43,33,56,81,137,
+62,72,130,115,71,43,33,60,105,137,62,210,98,151,72,39,52,114,178,20,7,152,
+147,227,16,181,162,68,19,154,57,89,10,36,140,73,246,147,19,58,133,173,18,
+32,156,209,202,200,82,34,98,79,147,67,9,151,52,156,113,75,34,78,208,1,228,
+73,242,104,97,46,16,62,68,159,24,133,173,18,32,156,209,202,217,83,37,34,79,
+180,152,153,212,45,104,145,4,230,142,86,202,160,169,18,124,145,4,230,142,
+86,215,213,27,34,79,180,152,165,210,9,205,28,173,175,172,42,68,159,24,134,
+210,100,149,183,245,198,200,147,237,38,38,117,13,164,201,43,111,236,8,145,
+39,195,186,145,149,185,246,69,200,147,237,38,35,245,212,140,173,207,180,30,
+68,159,6,9,146,217,91,21,34,79,180,152,135,208,76,150,202,224,137,18,124,
+99,155,51,219,95,116,92,137,62,210,98,103,92,217,158,218,251,194,228,73,
+240,180,90,249,130,2,54,223,223,29,34,79,180,152,139,218,45,124,193,1,27,
+111,240,33,204,73,243,4,4,108,134,8,60,137,62,96,128,141,178,193,193,154,3,
+147,32,227,36,0,0,0,0,0,0,0,0,99,115,245,195,19,159,176,75,175,159,176,24,
+172,253,129,49,121,251,2,176,66,92,130,235,16,18,100,148,251,36,106,123,64,
+65,158,3,147,160,108,202,62,68,165,107,243,227,113,198,211,62,39,20,108,
+115,226,241,130,106,113,224,78,162,4,242,130,236,197,60,37,64,190,18,49,
+116,114,37,40,157,76,9,229,37,217,138,185,16,52,196,225,35,23,71,34,82,137,
+213,64,158,84,93,152,187,145,33,73,18,52,68,225,35,23,71,34,82,137,213,192,
+158,86,93,152,175,146,195,102,11,240,145,139,163,145,41,68,235,32,79,44,46,
+204,83,201,225,228,225,35,23,71,34,82,137,214,192,158,90,93,152,163,180,
+138,9,216,197,209,200,148,161,194,32,30,18,2,0,45,248,84,88,162,187,72,78,
+173,186,58,16,16,0,154,236,110,237,85,69,129,245,109,210,128,127,204,92,
+133,253,244,115,222,23,171,113,180,137,0,255,220,85,29,148,174,11,248,55,
+86,227,104,232,18,1,254,222,91,216,169,55,40,112,46,189,16,16,2,72,126,213,
+17,11,70,3,246,80,140,244,118,180,160,31,243,80,79,51,63,157,230,133,236,
+161,25,233,64,63,246,160,158,102,127,59,205,41,248,30,75,12,11,151,242,233,
+187,146,156,80,211,114,96,54,230,41,20,129,128,50,211,16,16,2,116,180,196,
+129,1,36,55,76,74,16,19,3,116,196,193,65,48,55,75,80,128,65,6,51,211,20,
+128,130,34,23,166,39,6,39,75,76,80,1,146,239,211,20,16,165,91,157,29,49,66,
+10,124,61,211,209,175,1,173,198,211,20,48,139,113,180,180,197,36,42,220,
+109,29,13,49,74,6,192,95,72,188,6,196,55,74,188,6,247,91,80,136,26,32,104,
+220,205,56,1,98,234,52,122,98,136,14,72,110,152,162,132,148,35,61,49,70,7,
+48,55,76,81,194,206,52,104,180,197,45,192,80,175,4,100,77,10,2,101,56,161,
+166,65,113,162,98,8,3,131,7,169,35,36,57,176,16,52,232,64,0,0,0,0,45,158,
+10,225,223,132,17,13,43,176,228,3,0,167,129,32,17,133,134,32,25,80,220,40,
+240,25,26,44,32,240,24,200,44,24,240,56,156,199,128,83,193,17,7,4,13,128,0,
+10,79,202,28,223,195,1,197,72,196,141,159,220,7,48,33,7,8,3,152,49,117,60,
+240,76,47,60,9,224,187,56,43,224,221,64,172,156,36,98,232,228,96,220,145,
+139,163,182,134,237,146,49,116,118,206,6,141,104,105,136,32,14,4,128,160,
+123,215,140,147,32,145,57,178,156,104,41,228,151,168,225,144,168,105,56,
+248,185,228,140,241,190,100,209,244,80,210,116,151,134,12,73,241,214,111,
+31,23,60,145,158,56,50,72,81,67,230,232,239,209,7,24,147,227,226,233,186,
+120,121,56,226,241,137,116,189,52,6,34,92,37,230,70,201,1,89,56,36,154,110,
+25,49,23,196,149,35,103,194,94,100,108,144,230,203,136,73,174,234,63,52,
+252,212,87,0,131,138,4,12,137,114,168,37,166,144,230,37,5,7,19,39,22,70,
+154,103,143,252,4,11,37,160,68,164,139,7,24,3,152,182,20,28,76,156,89,26,
+105,158,63,240,5,7,19,39,28,82,200,147,143,253,0,193,161,74,72,199,253,132,
+176,230,36,248,134,207,98,138,99,4,24,147,229,16,217,236,81,75,98,12,73,
+241,13,158,142,181,20,198,137,49,39,202,33,179,209,214,162,151,4,24,147,
+226,27,61,61,42,41,142,18,98,79,148,67,103,167,165,69,46,138,49,39,194,173,
+192,158,158,149,20,188,40,196,159,10,183,2,122,218,148,82,248,121,18,124,
+67,103,177,77,177,130,36,73,242,136,108,246,41,181,177,18,36,248,134,207,
+71,90,155,99,68,200,147,229,16,217,232,235,83,107,130,36,73,241,13,158,158,
+149,54,199,9,145,39,202,33,179,211,210,166,215,69,72,147,225,86,224,79,79,
+74,155,94,21,34,79,133,91,129,61,109,74,109,126,14,56,7,6,20,28,76,156,89,
+26,105,158,63,240,5,7,19,39,28,82,200,147,143,253,0,193,161,74,72,199,253,
+130,235,191,232,8,149,2,8,196,24,164,137,141,200,8,71,161,196,201,45,167,
+146,59,68,89,24,70,206,1,255,128,0,0,0,0,0,1,142,49,232,71,161,196,201,45,
+167,146,59,68,89,24,70,206,1,255,128,0,0,0,0,0,1,141,201,8,71,161,196,201,
+45,167,146,59,68,89,24,70,206,1,255,128,0,0,0,0,0,2,138,2,214,225,113,235,
+2,27,128,0,10,66,3,189,96,67,120,226,224,0,2,148,140,113,145,66,61,14,38,
+73,109,60,145,218,34,200,194,54,112,15,252,0,0,0,0,0,0,12,110,80,66,61,14,
+38,73,109,60,145,218,34,200,194,54,112,15,252,0,0,0,0,0,0,12,113,147,66,61,
+14,38,73,109,60,145,218,34,200,194,54,112,15,252,0,0,0,0,0,0,12,110,88,66,
+61,14,38,73,109,60,145,218,34,200,194,54,112,16,0,0,0,0,0,0,0,12,113,149,
+66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,0,0,0,0,0,0,0,12,110,96,
+66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,0,0,0,0,0,0,0,12,113,
+151,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,0,0,0,0,0,0,0,12,
+110,104,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,4,0,0,0,0,0,0,
+12,113,153,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,4,0,0,0,0,0,
+0,12,110,112,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,4,0,0,0,0,
+0,0,12,113,155,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,4,0,0,0,
+0,0,0,12,110,120,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,4,0,0,
+0,0,0,0,12,113,157,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,4,0,
+0,0,0,0,0,12,110,128,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,8,
+0,0,0,0,0,0,12,113,159,66,61,14,38,73,109,60,145,218,34,200,194,54,112,16,
+8,0,0,0,0,0,0,8,58,32,128,24,78,104,129,61,82,2,145,46,17,162,112,208,211,
+107,200,16,137,112,52,41,73,29,113,2,131,137,147,139,35,77,51,234,80,14,39,
+49,224,137,40,35,100,141,9,136,19,18,0,125,162,58,217,236,81,64,68,72,1,
+241,13,158,197,20,150,50,36,0,251,68,117,179,209,214,234,201,69,16,100,72,
+1,246,136,235,103,163,173,208,146,138,68,23,18,0,124,67,103,163,173,213,
+146,138,76,23,18,0,124,67,103,163,173,208,146,138,84,25,18,0,125,162,58,
+217,233,233,117,100,162,138,50,36,0,251,68,117,179,211,210,232,73,69,34,
+139,137,0,62,33,179,211,210,234,201,69,38,139,137,0,62,33,179,211,210,232,
+73,69,42,139,137,0,62,21,110,4,250,178,81,70,23,18,0,124,42,220,9,244,36,
+162,145,134,68,128,31,6,234,5,100,234,201,69,28,100,72,1,240,110,160,86,78,
+132,148,82,56,168,144,3,237,17,214,207,171,37,22,128,42,36,0,251,68,117,
+179,232,73,69,164,9,137,0,62,33,179,234,201,69,168,9,137,0,62,33,179,232,
+73,69,172,10,180,81,50,118,136,235,103,177,77,129,54,138,38,78,33,179,216,
+166,210,198,218,40,153,59,68,117,179,209,214,234,201,77,144,109,162,137,
+147,180,71,91,61,29,110,132,148,218,32,203,69,19,39,16,217,232,235,117,100,
+166,211,6,90,40,153,56,134,207,71,91,161,37,54,168,54,209,68,201,218,35,
+173,158,158,151,86,74,108,163,109,20,76,157,162,58,217,233,233,116,36,166,
+209,70,90,40,153,56,134,207,79,75,171,37,54,154,50,209,68,201,196,54,122,
+122,93,9,41,181,81,150,138,38,78,21,110,4,250,178,83,102,25,104,162,100,
+225,86,224,79,161,37,54,140,54,209,68,201,193,186,129,89,58,178,83,103,27,
+104,162,100,224,221,64,172,157,9,41,180,113,118,138,38,78,209,29,108,250,
+178,83,136,2,237,20,76,157,162,58,217,244,36,167,18,5,90,40,153,56,134,207,
+171,37,56,160,42,209,68,201,196,54,125,9,41,197,141,78,197,141,86,192,0,
+133,66,215,173,96,49,33,64,46,84,8,14,39,49,224,137,40,18,4,19,159,141,100,
+1,100,180,8,148,146,0,91,69,19,38,202,8,58,64,28,209,160,130,52,78,26,26,
+110,255,80,64,196,104,156,50,125,4,144,116,192,57,165,97,4,104,156,52,52,
+221,254,64,20,160,152,23,223,228,32,148,25,174,137,58,23,51,191,200,84,12,
+50,9,195,39,196,80,
 };
 #elif defined(DUK_USE_DOUBLE_ME)
-DUK_INTERNAL const duk_uint8_t duk_builtins_data[3790] = {
+DUK_INTERNAL const duk_uint8_t duk_builtins_data[3819] = {
 144,148,105,221,32,68,52,228,62,12,104,200,165,134,148,248,81,77,61,191,
 135,35,154,103,34,72,6,157,159,197,145,77,245,126,52,130,106,234,163,196,
 52,226,18,51,161,26,113,1,60,37,64,190,18,49,116,116,33,26,113,1,92,136,26,
 98,112,145,139,163,165,8,211,136,14,228,72,82,68,141,17,56,72,197,209,212,
 132,105,196,5,242,88,108,193,126,18,49,116,117,161,26,113,1,60,158,30,78,
-18,49,116,118,33,26,113,1,29,164,80,78,198,46,142,212,36,68,51,71,224,59,
-147,60,93,110,79,15,39,9,24,186,33,13,63,79,185,39,26,121,223,110,77,66,53,
-116,1,120,248,186,248,136,67,76,196,200,134,186,137,177,13,31,192,174,79,
-15,32,248,8,196,24,8,107,254,39,97,161,175,248,159,16,215,252,80,186,26,
-255,138,57,136,107,254,41,100,33,175,248,167,170,134,191,226,166,138,26,
-255,138,187,40,107,254,43,111,33,171,86,181,16,209,241,11,228,201,121,240,
-141,19,134,72,196,52,123,168,95,38,75,207,131,32,156,50,70,33,195,3,152,
-128,0,1,240,254,0,0,0,1,153,128,0,1,224,254,0,0,0,1,151,137,0,214,9,188,35,
-131,12,225,196,56,177,78,60,99,147,28,229,200,57,162,120,74,129,124,36,98,
-232,156,241,92,136,26,98,112,145,139,162,116,71,114,36,41,34,70,136,156,36,
-98,232,157,49,124,150,27,48,95,132,140,93,19,170,39,147,195,201,194,70,46,
-137,215,17,218,69,4,236,98,232,157,153,39,110,81,220,15,193,209,83,3,200,
-119,130,241,241,117,240,120,80,252,137,10,178,10,103,134,180,122,9,135,136,
-154,120,169,199,142,158,121,10,7,146,162,121,74,71,150,166,121,138,135,154,
-170,121,202,199,158,23,201,146,243,225,26,39,12,145,61,16,190,76,151,159,6,
-65,56,100,137,233,35,93,205,144,33,224,140,137,196,54,121,244,5,60,17,145,
-56,85,184,19,207,16,21,18,227,65,198,231,72,16,137,112,168,106,38,76,225,2,
-70,65,56,100,237,34,140,177,4,134,65,56,100,237,34,129,117,204,123,154,70,
-207,46,64,146,52,78,25,59,72,163,48,65,34,52,78,25,59,72,160,93,115,30,230,
-145,179,204,144,24,146,16,30,76,209,2,40,210,72,64,121,52,4,0,156,88,97,5,
-194,96,227,18,124,124,93,55,79,15,39,28,94,49,38,159,154,136,96,196,159,29,
-102,241,241,115,201,25,227,131,36,133,20,62,110,142,253,2,102,36,248,235,
-55,143,139,158,72,207,28,104,24,73,112,201,3,2,82,65,155,187,94,6,20,72,9,
-147,120,128,225,144,168,105,56,248,185,228,140,241,190,96,128,200,84,52,
-156,124,92,242,70,104,36,183,168,4,145,0,190,41,1,139,18,19,36,226,146,17,
-124,73,82,54,124,37,230,70,201,14,108,184,132,8,68,185,34,1,100,31,8,129,8,
-151,11,23,100,141,225,18,12,68,184,75,204,141,146,2,178,112,72,8,162,98,92,
-50,10,152,147,227,172,222,62,46,121,35,60,114,88,96,92,185,112,201,65,34,
-92,4,1,147,81,159,141,205,32,234,121,96,97,57,64,97,121,128,14,56,37,199,
-89,188,124,92,242,70,120,227,144,53,18,227,226,233,186,120,121,56,226,242,
-8,40,248,185,228,140,241,196,75,132,109,24,72,128,43,39,36,136,48,64,114,0,
-250,156,168,1,64,247,175,25,36,2,8,11,94,80,248,16,40,104,242,103,200,48,
-193,3,162,92,4,98,12,41,14,66,40,106,101,1,132,130,8,24,78,104,129,54,62,
-96,224,144,13,238,124,32,2,62,146,60,51,224,120,146,164,140,137,20,0,178,
-58,11,56,192,5,146,208,34,71,64,36,157,25,200,32,52,158,180,8,146,87,129,
-232,217,29,5,156,179,224,116,52,100,191,28,87,62,130,214,9,79,136,104,201,
-126,56,174,127,0,31,255,225,73,82,71,16,13,1,36,230,18,1,164,14,87,71,132,
-0,143,0,210,131,96,31,0,211,6,42,23,50,70,1,167,13,18,14,130,36,67,232,46,
-36,29,4,78,69,6,60,226,31,192,7,255,252,24,160,163,11,23,51,130,56,35,193,
-56,100,238,31,6,150,46,103,4,225,147,143,114,27,62,233,241,200,137,182,133,
-42,142,167,216,6,23,216,0,97,28,17,224,39,223,32,80,142,8,240,78,25,56,9,
-248,8,22,39,12,156,123,144,217,240,19,240,18,6,19,154,32,79,194,124,14,134,
-140,151,227,139,226,52,11,88,37,62,33,163,37,248,226,248,141,32,213,184,64,
-89,56,39,49,224,137,60,100,5,96,38,35,249,8,15,18,61,96,17,60,200,6,145,1,
-17,31,206,64,89,45,2,39,161,0,178,122,209,63,74,2,101,64,202,113,67,77,235,
-64,92,221,197,186,196,143,4,9,19,188,1,25,187,139,112,128,178,113,110,177,
-35,193,2,68,239,0,46,110,229,30,242,71,130,4,137,222,4,35,55,113,110,16,22,
-78,81,239,36,120,32,72,157,224,64,147,138,25,237,0,52,72,242,2,126,82,3,74,
-129,148,227,234,66,12,112,28,140,155,104,203,169,158,9,133,158,4,25,36,1,
-61,96,47,181,80,46,132,129,255,255,222,255,255,255,255,254,39,172,67,118,
-170,5,208,144,0,0,0,0,0,64,0,0,51,16,0,0,62,31,192,0,0,0,8,245,238,146,38,
-138,147,105,13,42,26,137,226,0,0,7,131,248,0,0,0,1,30,180,134,4,209,82,109,
-33,165,67,81,60,64,0,0,240,255,0,0,0,0,28,144,155,104,0,0,0,0,0,0,0,0,16,
-117,59,130,48,155,98,48,187,144,3,205,220,42,46,65,237,72,27,55,112,151,
-123,154,70,205,0,94,208,129,115,119,31,18,9,18,67,155,183,34,12,176,96,175,
-4,100,74,228,3,237,38,43,31,192,109,117,171,0,228,164,219,72,0,0,248,127,0,
-0,0,0,196,234,111,0,50,110,224,193,50,114,83,138,26,107,192,131,38,238,77,
-12,39,37,56,161,166,188,11,132,188,12,74,110,226,220,32,44,156,24,38,78,74,
-113,67,77,120,28,148,221,197,184,64,89,57,52,48,156,148,226,134,154,240,64,
-195,94,8,56,123,193,11,85,116,140,45,240,3,152,147,228,208,194,95,0,89,137,
-62,22,139,95,48,64,70,200,67,28,98,79,180,152,139,218,45,124,193,1,27,33,
-16,65,137,62,49,205,153,236,132,81,102,36,251,73,137,157,115,102,123,33,24,
-57,137,62,12,19,37,144,142,40,196,159,105,49,15,160,153,44,132,128,198,36,
-248,48,98,200,73,18,98,79,180,152,135,208,98,200,74,16,98,79,135,117,35,43,
-33,44,89,137,62,210,98,63,93,72,202,200,76,20,98,79,140,67,105,50,74,200,
-77,26,98,79,180,152,153,212,54,147,36,172,132,225,70,36,249,34,9,205,28,
-172,132,241,166,36,251,73,138,93,32,156,209,202,200,80,30,98,79,140,66,214,
-137,16,78,104,229,100,40,146,49,39,218,76,76,234,22,180,72,130,115,71,43,
-33,72,137,137,62,77,12,38,92,210,113,197,44,137,59,64,7,145,39,201,161,132,
-184,64,249,18,124,98,22,180,72,130,115,71,43,101,76,148,137,62,210,98,103,
-80,181,162,68,19,154,57,91,42,130,164,73,242,68,19,154,57,91,95,84,108,137,
-62,210,98,151,72,39,52,114,182,190,176,169,18,124,98,27,73,146,86,223,215,
-27,34,79,180,152,153,212,54,147,36,173,191,176,34,68,159,14,234,70,86,231,
-217,23,34,79,180,152,143,215,82,50,183,62,208,121,18,124,24,38,75,101,108,
-84,137,62,210,98,31,65,50,91,43,130,36,73,241,142,108,207,109,125,209,114,
-36,251,73,137,157,115,102,123,107,239,11,145,39,194,209,107,230,8,8,219,
-127,124,116,137,62,210,98,47,104,181,243,4,4,109,191,192,135,49,39,204,16,
-17,178,24,32,242,36,249,130,2,54,203,7,6,104,14,76,131,140,144,0,0,0,0,0,0,
-0,1,141,207,215,12,78,126,193,46,190,126,192,98,179,246,4,197,231,236,10,
-193,9,114,11,172,64,73,146,83,236,145,169,237,1,6,120,14,78,129,179,40,249,
-18,149,175,207,141,199,27,76,248,156,81,177,207,139,198,9,169,199,129,58,
-136,19,202,11,179,20,240,149,2,248,72,197,209,200,148,162,117,48,39,148,
-151,102,42,228,64,211,19,132,140,93,28,137,74,39,85,2,121,81,118,98,238,68,
-133,36,72,209,19,132,140,93,28,137,74,39,87,2,121,89,118,98,190,75,13,152,
-47,194,70,46,142,68,165,19,172,129,60,176,187,49,79,39,135,147,132,140,93,
-28,137,74,39,91,2,121,105,118,98,142,210,40,39,99,23,71,34,82,135,8,128,
-120,72,1,87,224,168,13,42,226,145,97,58,182,232,232,64,177,107,2,64,22,85,
-181,187,7,213,183,74,2,17,119,49,255,121,207,215,240,94,173,198,210,36,4,
-113,95,115,255,232,34,182,80,221,91,141,163,160,72,15,121,123,103,225,220,
-164,194,160,186,244,64,251,33,9,64,24,45,68,84,15,217,66,51,209,218,210,
-129,61,65,204,127,154,118,254,204,23,178,132,103,165,2,122,131,216,255,52,
-237,253,152,167,224,121,44,48,46,95,203,166,238,74,113,67,77,201,128,219,
-152,164,82,6,0,203,76,64,64,9,210,211,18,4,4,144,221,49,40,64,76,13,211,19,
-5,4,192,221,45,66,1,4,24,207,76,82,2,8,136,94,152,156,24,157,45,49,64,6,75,
-191,76,80,66,149,110,116,116,197,8,41,240,247,79,70,188,6,183,27,76,80,194,
-45,198,210,211,20,144,171,113,180,116,52,197,40,27,1,125,34,240,27,16,221,
-42,240,27,221,109,66,32,104,129,163,115,52,224,5,139,168,209,233,138,32,57,
-33,186,98,138,18,80,140,244,197,24,28,192,221,49,71,11,56,209,162,211,20,
-183,1,66,188,17,145,52,40,9,148,226,134,153,5,198,137,136,32,14,12,30,164,
-140,144,230,192,64,136,211,64,0,0,0,0,182,120,43,135,126,16,68,52,174,195,
-144,12,2,158,4,128,70,22,24,128,101,67,112,163,192,100,104,176,131,192,99,
-32,176,99,192,226,115,30,1,79,4,68,28,16,54,0,0,41,254,232,116,62,204,7,21,
-35,18,54,127,80,28,192,132,28,32,14,96,197,212,243,193,48,188,240,39,130,
-236,224,175,131,117,2,178,112,145,139,163,145,131,114,70,46,142,218,27,182,
-72,197,209,219,56,26,53,161,166,32,128,56,18,2,129,239,94,50,76,130,68,230,
-202,113,160,167,146,94,163,134,66,161,164,227,226,231,146,51,198,249,147,
-71,209,67,73,210,94,24,49,39,199,89,188,124,92,242,70,120,224,201,33,69,15,
-155,163,191,68,28,98,79,143,139,166,233,225,228,227,139,198,37,210,244,208,
-24,137,112,151,153,27,36,5,100,224,146,105,184,100,196,95,18,84,141,159,9,
-121,145,178,67,155,46,33,38,187,168,252,211,243,81,92,2,14,40,16,50,37,202,
-160,150,154,67,152,148,20,28,76,156,89,26,105,158,63,232,16,44,150,129,18,
-146,44,28,96,14,98,216,80,113,50,113,100,105,166,120,255,160,20,28,76,156,
-113,75,34,78,63,236,3,6,133,41,35,31,242,18,195,152,147,226,27,61,138,41,
-140,16,98,79,148,67,103,177,69,45,136,49,39,196,54,122,58,212,83,26,36,196,
-159,40,134,207,71,90,138,92,16,98,79,136,108,244,244,168,166,56,73,137,62,
-81,13,158,158,149,20,186,40,196,159,10,183,2,122,122,84,82,240,163,18,124,
-42,220,9,235,106,81,75,225,228,73,241,13,158,197,54,198,8,145,39,202,33,
-179,216,166,214,196,72,147,226,27,61,29,106,109,141,19,34,79,148,67,103,
-163,173,77,174,8,145,39,196,54,122,122,84,219,28,38,68,159,40,134,207,79,
-74,155,93,21,34,79,133,91,129,61,61,42,109,120,84,137,62,21,110,4,245,181,
-41,181,248,56,224,28,24,80,113,50,113,100,105,166,120,255,160,20,28,76,156,
-113,75,34,78,63,236,3,6,133,41,35,31,242,11,174,254,160,34,84,8,35,16,98,
-146,38,55,32,33,30,135,19,36,182,158,72,237,17,100,97,27,56,0,0,30,7,224,0,
-0,0,6,56,199,161,30,135,19,36,182,158,72,237,17,100,97,27,56,0,0,30,7,224,
-0,0,0,6,55,36,33,30,135,19,36,182,158,72,237,17,100,97,27,56,0,0,30,7,224,
-0,0,0,10,40,11,91,133,199,172,8,111,248,128,239,88,16,222,56,191,242,49,
-198,69,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,240,63,0,0,0,0,
-49,185,65,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,240,63,0,0,0,
-0,49,198,77,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,240,63,0,0,
-0,0,49,185,97,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,64,0,0,
-0,0,49,198,85,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,64,0,0,
-0,0,49,185,129,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,64,0,
-0,0,0,49,198,93,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,64,0,
-0,0,0,49,185,161,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,16,64,
-0,0,0,0,49,198,101,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,16,
-64,0,0,0,0,49,185,193,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,
-16,64,0,0,0,0,49,198,109,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,
-0,16,64,0,0,0,0,49,185,225,8,244,56,153,37,180,242,71,104,139,35,8,217,192,
-0,0,16,64,0,0,0,0,49,198,117,8,244,56,153,37,180,242,71,104,139,35,8,217,
-192,0,0,16,64,0,0,0,0,49,186,1,8,244,56,153,37,180,242,71,104,139,35,8,217,
-192,0,0,32,64,0,0,0,0,49,198,125,8,244,56,153,37,180,242,71,104,139,35,8,
-217,192,0,0,32,64,0,0,0,0,32,232,130,0,97,57,162,4,245,72,10,68,184,70,137,
-195,67,77,175,32,66,37,192,208,165,36,117,196,10,14,38,78,44,141,52,207,
-169,64,56,156,199,130,36,160,141,146,52,38,32,76,72,1,246,136,235,103,177,
-69,1,17,32,7,196,54,123,20,82,88,200,144,3,237,17,214,207,71,91,171,37,20,
-65,145,32,7,218,35,173,158,142,183,66,74,41,16,92,72,1,241,13,158,142,183,
-86,74,41,48,92,72,1,241,13,158,142,183,66,74,41,80,100,72,1,246,136,235,
-103,167,165,213,146,138,40,200,144,3,237,17,214,207,79,75,161,37,20,138,46,
-36,0,248,134,207,79,75,171,37,20,154,46,36,0,248,134,207,79,75,161,37,20,
-170,46,36,0,248,85,184,19,234,201,69,24,92,72,1,240,171,112,39,208,146,138,
-70,25,18,0,124,27,168,21,147,171,37,20,113,145,32,7,193,186,129,89,58,18,
-81,72,226,162,64,15,180,71,91,62,172,148,90,0,168,144,3,237,17,214,207,161,
-37,22,144,38,36,0,248,134,207,171,37,22,160,38,36,0,248,134,207,161,37,22,
-176,42,209,68,201,218,35,173,158,197,54,4,218,40,153,56,134,207,98,155,75,
-27,104,162,100,237,17,214,207,71,91,171,37,54,65,182,138,38,78,209,29,108,
-244,117,186,18,83,104,131,45,20,76,156,67,103,163,173,213,146,155,76,25,
-104,162,100,226,27,61,29,110,132,148,218,160,219,69,19,39,104,142,182,122,
-122,93,89,41,178,141,180,81,50,118,136,235,103,167,165,208,146,155,69,25,
-104,162,100,226,27,61,61,46,172,148,218,104,203,69,19,39,16,217,233,233,
-116,36,166,213,70,90,40,153,56,85,184,19,234,201,77,152,101,162,137,147,
-133,91,129,62,132,148,218,48,219,69,19,39,6,234,5,100,234,201,77,156,109,
-162,137,147,131,117,2,178,116,36,166,209,197,218,40,153,59,68,117,179,234,
-201,78,32,11,180,81,50,118,136,235,103,208,146,156,72,21,104,162,100,226,
-27,62,172,148,226,128,171,69,19,39,16,217,244,36,167,22,53,59,22,53,91,0,2,
-21,11,94,181,128,196,133,0,185,80,32,56,156,199,130,36,160,72,16,78,126,53,
-144,5,146,208,34,82,72,1,109,20,76,155,40,32,233,0,115,70,130,8,209,56,104,
-105,187,252,193,3,17,162,112,201,242,18,65,211,0,230,149,132,17,162,112,
-208,211,119,248,0,82,130,96,95,127,128,130,80,102,186,36,232,92,206,255,1,
-80,48,200,39,12,158,241,64,
+18,49,116,118,33,26,113,1,29,164,80,78,198,46,142,212,36,68,51,71,232,59,
+147,60,93,110,79,15,39,9,24,186,33,13,63,111,185,16,211,206,251,114,98,17,
+171,160,11,199,197,215,196,66,26,102,38,68,53,212,77,136,104,255,5,114,120,
+121,7,192,70,32,192,67,95,249,59,13,13,127,228,248,134,191,242,133,208,215,
+254,81,204,67,95,249,75,33,13,127,229,61,84,53,255,149,52,80,215,254,85,
+217,67,95,249,91,121,13,90,181,168,134,143,152,95,38,75,207,132,104,156,50,
+70,33,163,225,66,249,50,94,124,25,4,225,146,49,14,24,28,196,0,0,15,135,240,
+0,0,0,12,204,0,0,15,7,240,0,0,0,12,188,72,6,176,77,225,28,24,103,14,33,197,
+138,113,227,28,152,231,46,65,205,19,194,84,11,225,35,23,68,231,138,228,64,
+211,19,132,140,93,19,162,59,145,33,73,18,52,68,225,35,23,68,233,139,228,
+176,217,130,252,36,98,232,157,81,60,158,30,78,18,49,116,78,184,142,210,40,
+39,99,23,68,236,201,59,114,142,224,126,14,138,152,30,67,188,23,143,139,175,
+131,194,135,228,72,85,144,83,60,53,163,208,76,60,68,211,197,78,60,116,243,
+200,80,60,149,19,202,82,60,181,51,204,84,60,213,83,206,86,60,240,190,76,
+151,159,8,209,56,100,137,232,133,242,100,188,248,50,9,195,36,79,73,26,238,
+108,129,15,4,100,78,33,179,207,160,41,224,140,137,194,173,192,158,120,128,
+168,151,26,14,55,58,64,132,75,133,67,81,50,103,8,18,50,9,195,39,105,20,101,
+136,36,50,9,195,39,105,20,11,174,99,220,210,54,121,114,4,145,162,112,201,
+218,69,25,130,9,17,162,112,201,218,69,2,235,152,247,52,141,158,100,128,196,
+144,128,242,102,136,17,70,146,66,3,201,160,32,0,130,225,48,113,137,62,62,
+46,155,167,135,147,142,47,24,147,79,205,68,48,98,79,142,179,120,248,185,
+228,140,241,193,146,66,138,31,55,71,126,129,51,18,124,117,155,199,197,207,
+36,103,142,52,12,36,184,100,129,129,41,32,205,221,175,3,10,36,4,201,188,64,
+112,200,84,52,156,124,92,242,70,120,223,48,64,100,42,26,78,62,46,121,35,52,
+18,91,212,2,72,128,95,20,128,197,137,9,146,113,73,8,190,36,169,27,62,18,
+243,35,100,135,54,92,66,4,34,92,145,0,178,15,132,64,132,75,133,139,178,70,
+240,137,6,34,92,37,230,70,201,1,89,56,36,4,81,49,46,25,5,76,73,241,214,111,
+31,23,60,145,158,57,44,48,46,92,184,100,160,145,46,2,0,201,168,207,198,230,
+144,117,60,176,48,156,160,48,188,192,7,28,18,227,172,222,62,46,121,35,60,
+113,200,26,137,113,241,116,221,60,60,156,113,121,4,20,124,92,242,70,120,
+226,37,194,54,140,36,64,21,147,146,68,24,32,57,0,125,78,84,0,160,123,215,
+140,146,1,4,5,175,40,124,8,20,52,121,51,228,24,96,129,209,46,2,49,6,20,135,
+33,20,53,50,128,194,65,4,12,39,52,64,155,31,48,112,72,6,247,62,16,1,31,73,
+30,25,240,60,73,82,70,68,138,0,89,29,5,156,96,2,201,104,17,35,160,18,78,
+140,228,16,26,79,90,4,73,43,192,244,108,142,130,206,89,240,58,26,50,95,142,
+43,159,65,107,4,167,196,52,100,191,28,87,63,128,15,255,240,164,169,35,136,
+6,128,146,115,9,0,210,7,43,163,194,0,71,128,105,65,176,15,128,105,131,21,
+11,153,35,0,211,134,137,7,65,18,33,244,23,18,14,130,39,34,131,30,113,15,
+224,3,255,254,12,80,81,133,139,153,193,28,17,224,156,50,119,15,131,75,23,
+51,130,112,201,199,185,13,159,116,248,228,68,219,66,149,83,83,238,3,11,238,
+0,48,142,8,240,19,239,144,40,71,4,120,39,12,156,4,252,4,11,19,134,78,61,
+200,108,248,9,248,9,3,9,205,16,39,225,62,7,67,70,75,241,197,241,154,5,172,
+18,159,16,209,146,252,113,124,102,144,106,220,32,44,156,19,152,240,68,158,
+66,2,176,19,17,252,164,7,137,30,176,8,158,116,3,72,128,136,143,232,32,44,
+150,129,19,210,128,89,61,104,159,169,1,50,160,101,56,161,166,246,160,46,
+110,226,221,98,71,130,4,137,222,0,140,221,197,184,64,89,56,183,88,145,224,
+129,34,119,128,23,55,114,143,121,35,193,2,68,239,2,17,155,184,183,8,11,39,
+40,247,146,60,16,36,78,240,32,73,197,12,247,128,26,36,121,1,63,49,2,165,48,
+70,114,229,145,51,250,205,2,8,209,203,150,68,207,235,52,130,16,209,46,131,
+36,188,70,128,210,160,101,56,251,16,131,28,7,35,38,218,50,234,103,130,97,
+103,129,6,73,0,79,88,11,237,84,11,161,32,127,255,247,191,255,255,255,255,
+137,235,16,221,170,129,116,36,0,0,0,0,0,16,0,0,12,196,0,0,15,135,240,0,0,0,
+2,61,123,164,137,162,164,218,67,74,134,162,120,128,0,1,224,254,0,0,0,0,71,
+173,33,129,52,84,155,72,105,80,212,79,16,0,0,60,63,192,0,0,0,7,36,38,218,0,
+0,0,0,0,0,0,0,4,29,78,224,140,38,216,140,46,228,0,243,119,10,139,144,123,
+82,6,205,220,37,222,230,145,179,64,23,180,32,92,221,199,196,130,68,144,230,
+237,200,131,44,24,43,193,25,18,185,0,251,73,138,199,240,27,93,106,192,57,
+41,54,210,0,0,62,31,192,0,0,0,49,58,155,192,12,155,184,48,76,156,148,226,
+134,154,240,32,201,187,147,67,9,201,78,40,105,175,2,225,47,3,18,155,184,
+183,8,11,39,6,9,147,146,156,80,211,94,7,37,55,113,110,16,22,78,77,12,39,37,
+56,161,166,188,16,48,215,130,14,30,240,66,213,93,35,11,124,0,230,36,249,52,
+48,151,192,22,98,79,133,162,215,204,16,17,178,16,199,24,147,237,38,34,246,
+139,95,48,64,70,200,68,16,98,79,140,115,102,123,33,20,89,137,62,210,98,103,
+92,217,158,200,70,14,98,79,131,4,201,100,35,138,49,39,218,76,67,232,38,75,
+33,32,49,137,62,12,24,178,18,68,152,147,237,38,33,244,24,178,18,132,24,147,
+225,221,72,202,200,75,22,98,79,180,152,143,215,82,50,178,19,5,24,147,227,
+16,218,76,146,178,19,70,152,147,237,38,38,117,13,164,201,43,33,56,81,137,
+62,72,130,115,71,43,33,60,105,137,62,210,98,151,72,39,52,114,178,20,7,152,
+147,227,16,181,162,68,19,154,57,89,10,36,140,73,246,147,19,58,133,173,18,
+32,156,209,202,200,82,34,98,79,147,67,9,151,52,156,113,75,34,78,208,1,228,
+73,242,104,97,46,16,62,68,159,24,133,173,18,32,156,209,202,217,83,37,34,79,
+180,152,153,212,45,104,145,4,230,142,86,202,160,169,18,124,145,4,230,142,
+86,215,213,27,34,79,180,152,165,210,9,205,28,173,175,172,42,68,159,24,134,
+210,100,149,183,245,198,200,147,237,38,38,117,13,164,201,43,111,236,8,145,
+39,195,186,145,149,185,246,69,200,147,237,38,35,245,212,140,173,207,180,30,
+68,159,6,9,146,217,91,21,34,79,180,152,135,208,76,150,202,224,137,18,124,
+99,155,51,219,95,116,92,137,62,210,98,103,92,217,158,218,251,194,228,73,
+240,180,90,249,130,2,54,223,223,29,34,79,180,152,139,218,45,124,193,1,27,
+111,240,33,204,73,243,4,4,108,134,8,60,137,62,96,128,141,178,193,193,154,3,
+147,32,227,36,0,0,0,0,0,0,0,0,99,115,245,195,19,159,176,75,175,159,176,24,
+172,253,129,49,121,251,2,176,66,92,130,235,16,18,100,148,251,36,106,123,64,
+65,158,3,147,160,108,202,62,68,165,107,243,227,113,198,211,62,39,20,108,
+115,226,241,130,106,113,224,78,162,4,242,130,236,197,60,37,64,190,18,49,
+116,114,37,40,157,76,9,229,37,217,138,185,16,52,196,225,35,23,71,34,82,137,
+213,64,158,84,93,152,187,145,33,73,18,52,68,225,35,23,71,34,82,137,213,192,
+158,86,93,152,175,146,195,102,11,240,145,139,163,145,41,68,235,32,79,44,46,
+204,83,201,225,228,225,35,23,71,34,82,137,214,192,158,90,93,152,163,180,
+138,9,216,197,209,200,148,161,194,32,30,18,0,85,248,42,3,74,184,164,88,78,
+173,186,58,16,44,90,192,144,5,149,109,110,193,245,109,210,128,132,93,204,
+127,222,115,245,252,23,171,113,180,137,1,28,87,220,255,250,8,173,148,55,86,
+227,104,232,18,3,222,94,217,248,119,41,48,168,46,189,16,62,200,66,80,6,11,
+81,21,3,246,80,140,244,118,180,160,79,80,115,31,230,157,191,179,5,236,161,
+25,233,64,158,160,246,63,205,59,127,102,41,248,30,75,12,11,151,242,233,187,
+146,156,80,211,114,96,54,230,41,20,129,128,50,211,16,16,2,116,180,196,129,
+1,36,55,76,74,16,19,3,116,196,193,65,48,55,75,80,128,65,6,51,211,20,128,
+130,34,23,166,39,6,39,75,76,80,1,146,239,211,20,16,165,91,157,29,49,66,10,
+124,61,211,209,175,1,173,198,211,20,48,139,113,180,180,197,36,42,220,109,
+29,13,49,74,6,192,95,72,188,6,196,55,74,188,6,247,91,80,136,26,32,104,220,
+205,56,1,98,234,52,122,98,136,14,72,110,152,162,132,148,35,61,49,70,7,48,
+55,76,81,194,206,52,104,180,197,45,192,80,175,4,100,77,10,2,101,56,161,166,
+65,113,162,98,8,3,131,7,169,35,36,57,176,0,40,116,208,0,0,0,0,45,158,10,
+225,223,132,17,13,43,176,228,3,0,167,129,32,17,133,134,32,25,80,220,40,240,
+25,26,44,32,240,24,200,44,24,240,56,156,199,128,83,193,17,7,4,13,128,0,10,
+79,202,28,223,195,1,197,72,196,141,159,220,7,48,33,7,8,3,152,49,117,60,240,
+76,47,60,9,224,187,56,43,224,221,64,172,156,36,98,232,228,96,220,145,139,
+163,182,134,237,146,49,116,118,206,6,141,104,105,136,32,14,4,128,160,123,
+215,140,147,32,145,57,178,156,104,41,228,151,168,225,144,168,105,56,248,
+185,228,140,241,190,100,209,244,80,210,116,151,134,12,73,241,214,111,31,23,
+60,145,158,56,50,72,81,67,230,232,239,209,7,24,147,227,226,233,186,120,121,
+56,226,241,137,116,189,52,6,34,92,37,230,70,201,1,89,56,36,154,110,25,49,
+23,196,149,35,103,194,94,100,108,144,230,203,136,73,174,234,63,52,252,212,
+87,0,131,138,4,12,137,114,168,37,166,144,230,37,5,7,19,39,22,70,154,103,
+143,252,4,11,37,160,68,164,139,7,24,3,152,182,20,28,76,156,89,26,105,158,
+63,240,5,7,19,39,28,82,200,147,143,253,0,193,161,74,72,199,253,132,176,230,
+36,248,134,207,98,138,99,4,24,147,229,16,217,236,81,75,98,12,73,241,13,158,
+142,181,20,198,137,49,39,202,33,179,209,214,162,151,4,24,147,226,27,61,61,
+42,41,142,18,98,79,148,67,103,167,165,69,46,138,49,39,194,173,192,158,158,
+149,20,188,40,196,159,10,183,2,122,218,148,82,248,121,18,124,67,103,177,77,
+177,130,36,73,242,136,108,246,41,181,177,18,36,248,134,207,71,90,155,99,68,
+200,147,229,16,217,232,235,83,107,130,36,73,241,13,158,158,149,54,199,9,
+145,39,202,33,179,211,210,166,215,69,72,147,225,86,224,79,79,74,155,94,21,
+34,79,133,91,129,61,109,74,109,126,14,56,7,6,20,28,76,156,89,26,105,158,63,
+240,5,7,19,39,28,82,200,147,143,253,0,193,161,74,72,199,253,130,235,191,
+232,8,149,2,8,196,24,164,137,141,200,8,71,161,196,201,45,167,146,59,68,89,
+24,70,206,0,0,7,129,248,0,0,0,1,142,49,232,71,161,196,201,45,167,146,59,68,
+89,24,70,206,0,0,7,129,248,0,0,0,1,141,201,8,71,161,196,201,45,167,146,59,
+68,89,24,70,206,0,0,7,129,248,0,0,0,2,138,2,214,225,113,235,2,27,128,0,10,
+66,3,189,96,67,120,226,224,0,2,148,140,113,145,66,61,14,38,73,109,60,145,
+218,34,200,194,54,112,0,0,60,15,192,0,0,0,12,110,80,66,61,14,38,73,109,60,
+145,218,34,200,194,54,112,0,0,60,15,192,0,0,0,12,113,147,66,61,14,38,73,
+109,60,145,218,34,200,194,54,112,0,0,60,15,192,0,0,0,12,110,88,66,61,14,38,
+73,109,60,145,218,34,200,194,54,112,0,0,0,16,0,0,0,0,12,113,149,66,61,14,
+38,73,109,60,145,218,34,200,194,54,112,0,0,0,16,0,0,0,0,12,110,96,66,61,14,
+38,73,109,60,145,218,34,200,194,54,112,0,0,0,16,0,0,0,0,12,113,151,66,61,
+14,38,73,109,60,145,218,34,200,194,54,112,0,0,0,16,0,0,0,0,12,110,104,66,
+61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,4,16,0,0,0,0,12,113,153,
+66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,4,16,0,0,0,0,12,110,
+112,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,4,16,0,0,0,0,12,
+113,155,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,4,16,0,0,0,0,
+12,110,120,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,4,16,0,0,0,
+0,12,113,157,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,4,16,0,0,
+0,0,12,110,128,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,8,16,0,
+0,0,0,12,113,159,66,61,14,38,73,109,60,145,218,34,200,194,54,112,0,0,8,16,
+0,0,0,0,8,58,32,128,24,78,104,129,61,82,2,145,46,17,162,112,208,211,107,
+200,16,137,112,52,41,73,29,113,2,131,137,147,139,35,77,51,234,80,14,39,49,
+224,137,40,35,100,141,9,136,19,18,0,125,162,58,217,236,81,64,68,72,1,241,
+13,158,197,20,150,50,36,0,251,68,117,179,209,214,234,201,69,16,100,72,1,
+246,136,235,103,163,173,208,146,138,68,23,18,0,124,67,103,163,173,213,146,
+138,76,23,18,0,124,67,103,163,173,208,146,138,84,25,18,0,125,162,58,217,
+233,233,117,100,162,138,50,36,0,251,68,117,179,211,210,232,73,69,34,139,
+137,0,62,33,179,211,210,234,201,69,38,139,137,0,62,33,179,211,210,232,73,
+69,42,139,137,0,62,21,110,4,250,178,81,70,23,18,0,124,42,220,9,244,36,162,
+145,134,68,128,31,6,234,5,100,234,201,69,28,100,72,1,240,110,160,86,78,132,
+148,82,56,168,144,3,237,17,214,207,171,37,22,128,42,36,0,251,68,117,179,
+232,73,69,164,9,137,0,62,33,179,234,201,69,168,9,137,0,62,33,179,232,73,69,
+172,10,180,81,50,118,136,235,103,177,77,129,54,138,38,78,33,179,216,166,
+210,198,218,40,153,59,68,117,179,209,214,234,201,77,144,109,162,137,147,
+180,71,91,61,29,110,132,148,218,32,203,69,19,39,16,217,232,235,117,100,166,
+211,6,90,40,153,56,134,207,71,91,161,37,54,168,54,209,68,201,218,35,173,
+158,158,151,86,74,108,163,109,20,76,157,162,58,217,233,233,116,36,166,209,
+70,90,40,153,56,134,207,79,75,171,37,54,154,50,209,68,201,196,54,122,122,
+93,9,41,181,81,150,138,38,78,21,110,4,250,178,83,102,25,104,162,100,225,86,
+224,79,161,37,54,140,54,209,68,201,193,186,129,89,58,178,83,103,27,104,162,
+100,224,221,64,172,157,9,41,180,113,118,138,38,78,209,29,108,250,178,83,
+136,2,237,20,76,157,162,58,217,244,36,167,18,5,90,40,153,56,134,207,171,37,
+56,160,42,209,68,201,196,54,125,9,41,197,141,78,197,141,86,192,0,133,66,
+215,173,96,49,33,64,46,84,8,14,39,49,224,137,40,18,4,19,159,141,100,1,100,
+180,8,148,146,0,91,69,19,38,202,8,58,64,28,209,160,130,52,78,26,26,110,255,
+80,64,196,104,156,50,125,4,144,116,192,57,165,97,4,104,156,52,52,221,254,
+64,20,160,152,23,223,228,32,148,25,174,137,58,23,51,191,200,84,12,50,9,195,
+39,196,80,
 };
 #else
 #error invalid endianness defines
 #endif
 #endif  /* DUK_USE_ROM_OBJECTS */
+
+/* automatic undefs */
+#undef DUK__REFCINIT
 #line 1 "duk_error_macros.c"
 /*
  *  Error and fatal handling.
@@ -10457,7 +11015,7 @@
 
 #if defined(DUK_USE_VERBOSE_ERRORS)
 
-DUK_INTERNAL void duk_err_handle_error_fmt(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *fmt, ...) {
+DUK_INTERNAL DUK_COLD void duk_err_handle_error_fmt(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *fmt, ...) {
 	va_list ap;
 	char msg[DUK__ERRFMT_BUFSIZE];
 	va_start(ap, fmt);
@@ -10467,13 +11025,13 @@
 	va_end(ap);  /* dead code, but ensures portability (see Linux man page notes) */
 }
 
-DUK_INTERNAL void duk_err_handle_error(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *msg) {
+DUK_INTERNAL DUK_COLD void duk_err_handle_error(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *msg) {
 	duk_err_create_and_throw(thr, (duk_errcode_t) (line_and_code >> 24), msg, filename, (duk_int_t) (line_and_code & 0x00ffffffL));
 }
 
 #else  /* DUK_USE_VERBOSE_ERRORS */
 
-DUK_INTERNAL void duk_err_handle_error(duk_hthread *thr, duk_errcode_t code) {
+DUK_INTERNAL DUK_COLD void duk_err_handle_error(duk_hthread *thr, duk_errcode_t code) {
 	duk_err_create_and_throw(thr, code);
 }
 
@@ -10485,41 +11043,41 @@
 
 #if defined(DUK_USE_VERBOSE_ERRORS)
 #if defined(DUK_USE_PARANOID_ERRORS)
-DUK_INTERNAL void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name) {
+DUK_INTERNAL DUK_COLD void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name) {
 	DUK_ERROR_RAW_FMT3(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, "%s required, found %s (stack index %ld)",
 	                   expect_name, duk_get_type_name((duk_context *) thr, idx), (long) idx);
 }
 #else
-DUK_INTERNAL void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name) {
+DUK_INTERNAL DUK_COLD void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name) {
 	DUK_ERROR_RAW_FMT3(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, "%s required, found %s (stack index %ld)",
 	                   expect_name, duk_push_string_readable((duk_context *) thr, idx), (long) idx);
 }
 #endif
-DUK_INTERNAL void duk_err_error_internal(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
+DUK_INTERNAL DUK_COLD void duk_err_error_internal(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_ERROR, DUK_STR_INTERNAL_ERROR);
 }
-DUK_INTERNAL void duk_err_error_alloc_failed(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
+DUK_INTERNAL DUK_COLD void duk_err_error_alloc_failed(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_ERROR, DUK_STR_ALLOC_FAILED);
 }
-DUK_INTERNAL void duk_err_error(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message) {
+DUK_INTERNAL DUK_COLD void duk_err_error(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_ERROR, message);
 }
-DUK_INTERNAL void duk_err_range(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message) {
+DUK_INTERNAL DUK_COLD void duk_err_range(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_RANGE_ERROR, message);
 }
-DUK_INTERNAL void duk_err_range_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx) {
+DUK_INTERNAL DUK_COLD void duk_err_range_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx) {
 	DUK_ERROR_RAW_FMT1(thr, filename, linenumber, DUK_ERR_RANGE_ERROR, "invalid stack index %ld", (long) (idx));
 }
-DUK_INTERNAL void duk_err_range_push_beyond(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
+DUK_INTERNAL DUK_COLD void duk_err_range_push_beyond(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_RANGE_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
 }
-DUK_INTERNAL void duk_err_type_invalid_args(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
+DUK_INTERNAL DUK_COLD void duk_err_type_invalid_args(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_ARGS);
 }
-DUK_INTERNAL void duk_err_type_invalid_state(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
+DUK_INTERNAL DUK_COLD void duk_err_type_invalid_state(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_STATE);
 }
-DUK_INTERNAL void duk_err_type_invalid_trap_result(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
+DUK_INTERNAL DUK_COLD void duk_err_type_invalid_trap_result(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_TRAP_RESULT);
 }
 #else
@@ -10531,25 +11089,25 @@
 DUK_LOCAL void duk__err_shared(duk_hthread *thr, duk_uint_t code) {
 	DUK_ERROR_RAW(thr, NULL, 0, code, NULL);
 }
-DUK_INTERNAL void duk_err_error(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_error(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_ERROR);
 }
-DUK_INTERNAL void duk_err_range(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_range(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_RANGE_ERROR);
 }
-DUK_INTERNAL void duk_err_eval(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_eval(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_EVAL_ERROR);
 }
-DUK_INTERNAL void duk_err_reference(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_reference(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_REFERENCE_ERROR);
 }
-DUK_INTERNAL void duk_err_syntax(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_syntax(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_SYNTAX_ERROR);
 }
-DUK_INTERNAL void duk_err_type(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_type(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_TYPE_ERROR);
 }
-DUK_INTERNAL void duk_err_uri(duk_hthread *thr) {
+DUK_INTERNAL DUK_COLD void duk_err_uri(duk_hthread *thr) {
 	duk__err_shared(thr, DUK_ERR_URI_ERROR);
 }
 #endif
@@ -10558,7 +11116,7 @@
  *  Default fatal error handler
  */
 
-DUK_INTERNAL void duk_default_fatal_handler(void *udata, const char *msg) {
+DUK_INTERNAL DUK_COLD void duk_default_fatal_handler(void *udata, const char *msg) {
 	DUK_UNREF(udata);
 	DUK_UNREF(msg);
 
@@ -12173,87 +12731,6 @@
 	 */
 	return (x > y ? x : y);
 }
-#line 1 "duk_util_hashprime.c"
-/*
- *  Round a number upwards to a prime (not usually the nearest one).
- *
- *  Uses a table of successive 32-bit primes whose ratio is roughly
- *  constant.  This keeps the relative upwards 'rounding error' bounded
- *  and the data size small.  A simple 'predict-correct' compression is
- *  used to compress primes to one byte per prime.  See genhashsizes.py
- *  for details.
- *
- *  The minimum prime returned here must be coordinated with the possible
- *  probe sequence steps in duk_hobject and duk_heap stringtable.
- */
-
-/* #include duk_internal.h -> already included */
-
-/* Awkward inclusion condition: drop out of compilation if not needed by any
- * call site: object hash part or probing stringtable.
- */
-#if defined(DUK_USE_HOBJECT_HASH_PART) || defined(DUK_USE_STRTAB_PROBE)
-
-/* hash size ratio goal, must match genhashsizes.py */
-#define DUK__HASH_SIZE_RATIO   1177  /* floor(1.15 * (1 << 10)) */
-
-/* prediction corrections for prime list (see genhashsizes.py) */
-DUK_LOCAL const duk_int8_t duk__hash_size_corrections[] = {
-	17,  /* minimum prime */
-	4, 3, 4, 1, 4, 1, 1, 2, 2, 2, 2, 1, 6, 6, 9, 5, 1, 2, 2, 5, 1, 3, 3, 3,
-	5, 4, 4, 2, 4, 8, 3, 4, 23, 2, 4, 7, 8, 11, 2, 12, 15, 10, 1, 1, 5, 1, 5,
-	8, 9, 17, 14, 10, 7, 5, 2, 46, 21, 1, 9, 9, 4, 4, 10, 23, 36, 6, 20, 29,
-	18, 6, 19, 21, 16, 11, 5, 5, 48, 9, 1, 39, 14, 8, 4, 29, 9, 1, 15, 48, 12,
-	22, 6, 15, 27, 4, 2, 17, 28, 8, 9, 4, 5, 8, 3, 3, 8, 37, 11, 15, 8, 30,
-	43, 6, 33, 41, 5, 20, 32, 41, 38, 24, 77, 14, 19, 11, 4, 35, 18, 19, 41,
-	10, 23, 16, 9, 2,
-	-1
-};
-
-/* probe steps (see genhashsizes.py), currently assumed to be 32 entries long
- * (DUK_UTIL_GET_HASH_PROBE_STEP macro).
- */
-DUK_INTERNAL duk_uint8_t duk_util_probe_steps[32] = {
-	2, 3, 5, 7, 11, 13, 19, 31, 41, 47, 59, 67, 73, 79, 89, 101, 103, 107,
-	109, 127, 137, 139, 149, 157, 163, 167, 173, 181, 191, 193, 197, 199
-};
-
-DUK_INTERNAL duk_uint32_t duk_util_get_hash_prime(duk_uint32_t size) {
-	const duk_int8_t *p = duk__hash_size_corrections;
-	duk_uint32_t curr;
-
-	curr = (duk_uint32_t) *p++;
-	for (;;) {
-		duk_small_int_t t = (duk_small_int_t) *p++;
-		if (t < 0) {
-			/* may happen if size is very close to 2^32-1 */
-			break;
-		}
-
-		/* prediction: portable variant using doubles if 64-bit values not available */
-#if defined(DUK_USE_64BIT_OPS)
-		curr = (duk_uint32_t) ((((duk_uint64_t) curr) * ((duk_uint64_t) DUK__HASH_SIZE_RATIO)) >> 10);
-#else
-		/* 32-bit x 11-bit = 43-bit, fits accurately into a double */
-		curr = (duk_uint32_t) DUK_FLOOR(((double) curr) * ((double) DUK__HASH_SIZE_RATIO) / 1024.0);
-#endif
-
-		/* correction */
-		curr += t;
-
-		DUK_DDD(DUK_DDDPRINT("size=%ld, curr=%ld", (long) size, (long) curr));
-
-		if (curr >= size) {
-			return curr;
-		}
-	}
-	return 0;
-}
-
-#endif  /* DUK_USE_HOBJECT_HASH_PART || DUK_USE_STRTAB_PROBE */
-
-/* automatic undefs */
-#undef DUK__HASH_SIZE_RATIO
 #line 1 "duk_hobject_class.c"
 /*
  *  Hobject Ecmascript [[Class]].
@@ -12775,6 +13252,7 @@
 	DUK_RAW_WRITE_U32_BE(p, 0);
 #endif
 	tmp32 = DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) func);  /* masks flags, only duk_hobject flags */
+	tmp32 &= ~(DUK_HOBJECT_FLAG_HAVE_FINALIZER);  /* finalizer flag is lost */
 	DUK_RAW_WRITE_U32_BE(p, tmp32);
 
 	/* Bytecode instructions: endian conversion needed unless
@@ -12954,7 +13432,7 @@
 	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&h_fun->obj));
 	DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(&h_fun->obj));
 	DUK_ASSERT(!DUK_HOBJECT_HAS_NATFUNC(&h_fun->obj));
-	DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(&h_fun->obj));
+	DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(&h_fun->obj));
 	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&h_fun->obj));
 	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&h_fun->obj));
 	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&h_fun->obj));
@@ -13080,14 +13558,23 @@
 		 * Must create a lexical environment on loading to allow
 		 * recursive functions like 'function foo() { foo(); }'.
 		 */
-		duk_hobject *new_env;
-
-		new_env = duk_push_object_helper_proto(ctx,
-		                                       DUK_HOBJECT_FLAG_EXTENSIBLE |
-		                                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
-		                                       func_env);
+		duk_hdecenv *new_env;
+
+		new_env = duk_hdecenv_alloc(thr,
+		                            DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV));
 		DUK_ASSERT(new_env != NULL);
-		func_env = new_env;
+		DUK_ASSERT(new_env->thread == NULL);  /* Closed. */
+		DUK_ASSERT(new_env->varmap == NULL);
+		DUK_ASSERT(new_env->regbase == 0);
+		DUK_ASSERT_HDECENV_VALID(new_env);
+		DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL);
+		DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, func_env);
+		DUK_HOBJECT_INCREF(thr, func_env);
+
+		func_env = (duk_hobject *) new_env;
+
+		duk_push_hobject(ctx, (duk_hobject *) new_env);
 
 		duk_dup_m2(ctx);                                  /* -> [ func funcname env funcname ] */
 		duk_dup(ctx, idx_base);                           /* -> [ func funcname env funcname func ] */
@@ -13741,9 +14228,11 @@
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
 
-	act = duk_hthread_get_current_activation(thr);
-	DUK_ASSERT(act != NULL);  /* because callstack_top > 0 */
-	return ((act->flags & DUK_ACT_FLAG_CONSTRUCT) != 0 ? 1 : 0);
+	act = thr->callstack_curr;
+	if (act != NULL) {
+		return ((act->flags & DUK_ACT_FLAG_CONSTRUCT) != 0 ? 1 : 0);
+	}
+	return 0;
 }
 
 /* XXX: Make this obsolete by adding a function flag for rejecting a
@@ -13772,12 +14261,13 @@
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
 
-	act = duk_hthread_get_current_activation(thr);
-	if (act == NULL) {
+	act = thr->callstack_curr;
+	if (act != NULL) {
+		return ((act->flags & DUK_ACT_FLAG_STRICT) != 0 ? 1 : 0);
+	} else {
 		/* Strict by default. */
 		return 1;
 	}
-	return ((act->flags & DUK_ACT_FLAG_STRICT) != 0 ? 1 : 0);
 }
 
 /*
@@ -13793,7 +14283,7 @@
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
 
-	act = duk_hthread_get_current_activation(thr);
+	act = thr->callstack_curr;
 	if (act) {
 		func = DUK_ACT_GET_FUNC(act);
 		if (!func) {
@@ -13898,7 +14388,10 @@
 
 	DUK_ASSERT(duk_is_valid_index(ctx, idx));  /* checked by caller */
 
-	ptr = duk_get_buffer_data_raw(ctx, idx, out_len, 0 /*throw_flag*/, &isbuffer);
+	/* XXX: with def_ptr set to a stack related pointer, isbuffer could
+	 * be removed from the helper?
+	 */
+	ptr = duk_get_buffer_data_raw(ctx, idx, out_len, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/, &isbuffer);
 	if (isbuffer) {
 		DUK_ASSERT(*out_len == 0 || ptr != NULL);
 		return (const duk_uint8_t *) ptr;
@@ -14090,13 +14583,13 @@
 					t <<= 6;
 				} else {
 					DUK_ASSERT(x == -1);
-					goto error;
+					goto decode_error;
 				}
 			} else {
 				DUK_ASSERT(x >= 0 && x <= 63);
 				if (n_equal > 0) {
 					/* Don't allow actual chars after equal sign. */
-					goto error;
+					goto decode_error;
 				}
 				t = (t << 6) + x;
 			}
@@ -14122,7 +14615,7 @@
 						/* XX== */
 						dst -= 2;
 					} else {
-						goto error;  /* invalid padding */
+						goto decode_error;  /* invalid padding */
 					}
 
 					/* Continue parsing after padding, allows concatenated,
@@ -14146,13 +14639,13 @@
 		 * (e.g. "xxxxyy" instead of "xxxxyy==".  Currently not
 		 * accepted.
 		 */
-		goto error;
+		goto decode_error;
 	}
 
 	*out_dst_final = dst;
 	return 1;
 
- error:
+ decode_error:
 	return 0;
 }
 #else  /* DUK_USE_BASE64_FASTPATH */
@@ -14194,12 +14687,12 @@
 			/* allow basic ASCII whitespace */
 			continue;
 		} else {
-			goto error;
+			goto decode_error;
 		}
 
 		if (n_equal > 0) {
 			/* Don't allow mixed padding and actual chars. */
-			goto error;
+			goto decode_error;
 		}
 		t = (t << 6) + y;
 	 skip_add:
@@ -14218,7 +14711,7 @@
 				} else if (n_equal == 2) {
 					dst -= 2;
 				} else {
-					goto error;  /* invalid padding */
+					goto decode_error;  /* invalid padding */
 				}
 
 				/* Here we can choose either to end parsing and ignore
@@ -14241,13 +14734,13 @@
 		 * (e.g. "xxxxyy" instead of "xxxxyy==".  Currently not
 		 * accepted.
 		 */
-		goto error;
+		goto decode_error;
 	}
 
 	*out_dst_final = dst;
 	return 1;
 
- error:
+ decode_error:
 	return 0;
 }
 #endif  /* DUK_USE_BASE64_FASTPATH */
@@ -14549,7 +15042,6 @@
 
 /* Eval is just a wrapper now. */
 DUK_EXTERNAL duk_int_t duk_eval_raw(duk_context *ctx, const char *src_buffer, duk_size_t src_length, duk_uint_t flags) {
-	duk_uint_t comp_flags;
 	duk_int_t rc;
 
 	DUK_ASSERT_CTX_VALID(ctx);
@@ -14563,9 +15055,7 @@
 
 	/* [ ... source? filename? ] (depends on flags) */
 
-	comp_flags = flags;
-	comp_flags |= DUK_COMPILE_EVAL;
-	rc = duk_compile_raw(ctx, src_buffer, src_length, comp_flags);  /* may be safe, or non-safe depending on flags */
+	rc = duk_compile_raw(ctx, src_buffer, src_length, flags | DUK_COMPILE_EVAL);  /* may be safe, or non-safe depending on flags */
 
 	/* [ ... closure/error ] */
 
@@ -14598,7 +15088,6 @@
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk__compile_raw_args *comp_args;
 	duk_uint_t flags;
-	duk_small_uint_t comp_flags;
 	duk_hcompfunc *h_templ;
 
 	DUK_ASSERT_CTX_VALID(ctx);
@@ -14637,22 +15126,13 @@
 	}
 	DUK_ASSERT(comp_args->src_buffer != NULL);
 
-	/* XXX: unnecessary translation of flags */
-	comp_flags = 0;
-	if (flags & DUK_COMPILE_EVAL) {
-		comp_flags |= DUK_JS_COMPILE_FLAG_EVAL;
-	}
 	if (flags & DUK_COMPILE_FUNCTION) {
-		comp_flags |= DUK_JS_COMPILE_FLAG_EVAL |
-		              DUK_JS_COMPILE_FLAG_FUNCEXPR;
-	}
-	if (flags & DUK_COMPILE_STRICT) {
-		comp_flags |= DUK_JS_COMPILE_FLAG_STRICT;
+		flags |= DUK_COMPILE_EVAL | DUK_COMPILE_FUNCEXPR;
 	}
 
 	/* [ ... source? filename ] */
 
-	duk_js_compile(thr, comp_args->src_buffer, comp_args->src_length, comp_flags);
+	duk_js_compile(thr, comp_args->src_buffer, comp_args->src_length, flags);
 
 	/* [ ... source? func_template ] */
 
@@ -14806,16 +15286,16 @@
 
 	/* Start in paused state. */
 	heap->dbg_processing = 0;
-	DUK_HEAP_SET_DEBUGGER_PAUSED(heap);
-	heap->dbg_state_dirty = 1;
+	heap->dbg_state_dirty = 0;
 	heap->dbg_force_restart = 0;
-	heap->dbg_step_type = 0;
+	heap->dbg_step_type = DUK_STEP_TYPE_NONE;
 	heap->dbg_step_thread = NULL;
 	heap->dbg_step_csindex = 0;
 	heap->dbg_step_startline = 0;
 	heap->dbg_exec_counter = 0;
 	heap->dbg_last_counter = 0;
 	heap->dbg_last_time = 0.0;
+	duk_debug_set_paused(heap);  /* XXX: overlap with fields above */
 
 	/* Send version identification and flush right afterwards.  Note that
 	 * we must write raw, unframed bytes here.
@@ -14855,7 +15335,7 @@
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(thr->heap != NULL);
 
-	if (!DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
+	if (!duk_debug_is_attached(thr->heap)) {
 		return;
 	}
 	if (thr->callstack_top > 0 || thr->heap->dbg_processing) {
@@ -14888,7 +15368,7 @@
 		DUK_ERROR_RANGE(thr, "not enough stack values for notify");
 		return ret;  /* unreachable */
 	}
-	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
+	if (duk_debug_is_attached(thr->heap)) {
 		duk_debug_write_notify(thr, DUK_DBG_CMD_APPNOTIFY);
 		for (idx = top - nvalues; idx < top; idx++) {
 			duk_tval *tv = DUK_GET_TVAL_POSIDX(ctx, idx);
@@ -14901,7 +15381,7 @@
 		 * a transport error was not indicated by the transport write
 		 * callback.  This is not a 100% guarantee of course.
 		 */
-		if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
+		if (duk_debug_is_attached(thr->heap)) {
 			ret = 1;
 		}
 	}
@@ -14920,15 +15400,19 @@
 	DUK_D(DUK_DPRINT("application called duk_debugger_pause()"));
 
 	/* Treat like a debugger statement: ignore when not attached. */
-	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
-		DUK_HEAP_SET_PAUSED(thr->heap);
-
-		/* Pause on the next opcode executed.  This is always safe to do even
-		 * inside the debugger message loop: the interrupt counter will be reset
-		 * to its proper value when the message loop exits.
-		 */
-		thr->interrupt_init = 1;
-		thr->interrupt_counter = 0;
+	if (duk_debug_is_attached(thr->heap)) {
+		if (duk_debug_is_paused(thr->heap)) {
+			DUK_D(DUK_DPRINT("duk_debugger_pause() called when already paused; ignoring"));
+		} else {
+			duk_debug_set_paused(thr->heap);
+
+			/* Pause on the next opcode executed.  This is always safe to do even
+			 * inside the debugger message loop: the interrupt counter will be reset
+			 * to its proper value when the message loop exits.
+			 */
+			thr->interrupt_init = 1;
+			thr->interrupt_counter = 0;
+		}
 	}
 }
 
@@ -15000,7 +15484,7 @@
 
 struct duk_internal_thread_state {
 	duk_ljstate lj;
-	duk_bool_t handling_error;
+	duk_bool_t creating_error;
 	duk_hthread *curr_thread;
 	duk_int_t call_recursion_depth;
 };
@@ -15081,14 +15565,27 @@
 	DUK_ASSERT(thr->heap != NULL);
 	DUK_ASSERT(state != NULL);  /* unvalidated */
 
+	/* Currently not supported when called from within a finalizer.
+	 * If that is done, the finalizer will remain running indefinitely,
+	 * preventing other finalizers from executing.  The assert is a bit
+	 * wider, checking that it would be OK to run pending finalizers.
+	 */
+	DUK_ASSERT(thr->heap->pf_prevent_count == 0);
+
+	/* Currently not supported to duk_suspend() from an errCreate()
+	 * call.
+	 */
+	DUK_ASSERT(thr->heap->creating_error == 0);
+
 	heap = thr->heap;
 	lj = &heap->lj;
 
 	duk_push_tval(ctx, &lj->value1);
 	duk_push_tval(ctx, &lj->value2);
 
+	/* XXX: creating_error == 0 is asserted above, so no need to store. */
 	DUK_MEMCPY((void *) &snapshot->lj, (const void *) lj, sizeof(duk_ljstate));
-	snapshot->handling_error = heap->handling_error;
+	snapshot->creating_error = heap->creating_error;
 	snapshot->curr_thread = heap->curr_thread;
 	snapshot->call_recursion_depth = heap->call_recursion_depth;
 
@@ -15096,7 +15593,7 @@
 	lj->type = DUK_LJ_TYPE_UNKNOWN;
 	DUK_TVAL_SET_UNDEFINED(&lj->value1);
 	DUK_TVAL_SET_UNDEFINED(&lj->value2);
-	heap->handling_error = 0;
+	heap->creating_error = 0;
 	heap->curr_thread = NULL;
 	heap->call_recursion_depth = 0;
 }
@@ -15111,10 +15608,16 @@
 	DUK_ASSERT(thr->heap != NULL);
 	DUK_ASSERT(state != NULL);  /* unvalidated */
 
+	/* Shouldn't be necessary if duk_suspend() is called before
+	 * duk_resume(), but assert in case API sequence is incorrect.
+	 */
+	DUK_ASSERT(thr->heap->pf_prevent_count == 0);
+	DUK_ASSERT(thr->heap->creating_error == 0);
+
 	heap = thr->heap;
 
 	DUK_MEMCPY((void *) &heap->lj, (const void *) &snapshot->lj, sizeof(duk_ljstate));
-	heap->handling_error = snapshot->handling_error;
+	heap->creating_error = snapshot->creating_error;
 	heap->curr_thread = snapshot->curr_thread;
 	heap->call_recursion_depth = snapshot->call_recursion_depth;
 
@@ -15126,7 +15629,7 @@
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_hobject *h_glob;
 	duk_hobject *h_prev_glob;
-	duk_hobject *h_env;
+	duk_hobjenv *h_env;
 	duk_hobject *h_prev_env;
 
 	DUK_D(DUK_DPRINT("replace global object with: %!T", duk_get_tval(ctx, -1)));
@@ -15153,29 +15656,30 @@
 	 *  same (initial) built-ins.
 	 */
 
-	h_env = duk_push_object_helper(ctx,
-	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV),
-	                               -1);  /* no prototype, updated below */
+	h_env = duk_hobjenv_alloc(thr,
+	                          DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                          DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV));
 	DUK_ASSERT(h_env != NULL);
-
-	duk_dup_m2(ctx);
-	duk_dup_m3(ctx);
-	duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
-	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);
-
-	/* [ ... new_glob new_env ] */
+	DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_env) == NULL);
+
+	DUK_ASSERT(h_env->target == NULL);
+	DUK_ASSERT(h_glob != NULL);
+	h_env->target = h_glob;
+	DUK_HOBJECT_INCREF(thr, h_glob);
+	DUK_ASSERT(h_env->has_this == 0);
+
+	/* [ ... new_glob ] */
 
 	h_prev_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
-	thr->builtins[DUK_BIDX_GLOBAL_ENV] = h_env;
-	DUK_HOBJECT_INCREF(thr, h_env);
+	thr->builtins[DUK_BIDX_GLOBAL_ENV] = (duk_hobject *) h_env;
+	DUK_HOBJECT_INCREF(thr, (duk_hobject *) h_env);
 	DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_env);  /* side effects */
 	DUK_UNREF(h_env);  /* without refcounts */
 	DUK_UNREF(h_prev_env);
 
-	/* [ ... new_glob new_env ] */
-
-	duk_pop_2(ctx);
+	/* [ ... new_glob ] */
+
+	duk_pop(ctx);
 
 	/* [ ... ] */
 }
@@ -15248,18 +15752,19 @@
 
 	DUK_UNREF(thr);
 
-	tv = duk_get_tval_or_unused(ctx, idx);
-	h = (DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv) : NULL);
-
 	/* Assume two's complement and set everything to -1. */
 	DUK_MEMSET((void *) &vals, (int) 0xff, sizeof(vals));
 	DUK_ASSERT(vals[DUK__IDX_TYPE] == -1);  /* spot check one */
 
-	duk_push_bare_object(ctx);
+	tv = duk_get_tval_or_unused(ctx, idx);
+	h = (DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv) : NULL);
 
 	vals[DUK__IDX_TYPE] = duk_get_type_tval(tv);
 	vals[DUK__IDX_ITAG] = (duk_uint_t) DUK_TVAL_GET_TAG(tv);
 
+	duk_push_bare_object(ctx);  /* Invalidates 'tv'. */
+	tv = NULL;
+
 	if (h == NULL) {
 		goto finish;
 	}
@@ -16236,9 +16741,31 @@
 }
 
 DUK_EXTERNAL void duk_set_finalizer(duk_context *ctx, duk_idx_t idx) {
-	DUK_ASSERT_CTX_VALID(ctx);
-
+	duk_hobject *h;
+	duk_bool_t callable;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	h = duk_require_hobject(ctx, idx);  /* Get before 'put' so that 'idx' is correct. */
+	callable = duk_is_callable(ctx, -1);
 	duk_put_prop_stridx(ctx, idx, DUK_STRIDX_INT_FINALIZER);
+
+	/* In addition to setting the finalizer property, keep a "have
+	 * finalizer" flag in duk_hobject in sync so that refzero can do
+	 * a very quick finalizer check by walking the prototype chain
+	 * and checking the flag alone.  (Note that this means that just
+	 * setting _Finalizer on an object won't affect finalizer checks.)
+	 *
+	 * NOTE: if the argument is a Proxy object, this flag will be set
+	 * on the Proxy, not the target.  As a result, the target won't get
+	 * a finalizer flag and the Proxy also won't be finalized as there's
+	 * an explicit Proxy check in finalization now.
+	 */
+	if (callable) {
+		DUK_HOBJECT_SET_HAVE_FINALIZER(h);
+	} else {
+		DUK_HOBJECT_CLEAR_HAVE_FINALIZER(h);
+	}
 }
 #else  /* DUK_USE_FINALIZER_SUPPORT */
 DUK_EXTERNAL void duk_get_finalizer(duk_context *ctx, duk_idx_t idx) {
@@ -16332,7 +16859,7 @@
 
 DUK_LOCAL_DECL duk_heaphdr *duk__get_tagged_heaphdr_raw(duk_context *ctx, duk_idx_t idx, duk_uint_t tag);
 
-DUK_LOCAL duk_int_t duk__api_coerce_d2i(duk_context *ctx, duk_idx_t idx, duk_bool_t require) {
+DUK_LOCAL duk_int_t duk__api_coerce_d2i(duk_context *ctx, duk_idx_t idx, duk_int_t def_value, duk_bool_t require) {
 	duk_hthread *thr;
 	duk_tval *tv;
 	duk_small_int_t c;
@@ -16392,10 +16919,11 @@
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "number", DUK_STR_NOT_NUMBER);
 		/* not reachable */
 	}
-	return 0;
-}
-
-DUK_LOCAL duk_uint_t duk__api_coerce_d2ui(duk_context *ctx, duk_idx_t idx, duk_bool_t require) {
+
+	return def_value;
+}
+
+DUK_LOCAL duk_uint_t duk__api_coerce_d2ui(duk_context *ctx, duk_idx_t idx, duk_uint_t def_value, duk_bool_t require) {
 	duk_hthread *thr;
 	duk_tval *tv;
 	duk_small_int_t c;
@@ -16445,7 +16973,8 @@
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "number", DUK_STR_NOT_NUMBER);
 		/* not reachable */
 	}
-	return 0;
+
+	return def_value;
 }
 
 /*
@@ -16612,7 +17141,7 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 	DUK_ASSERT(DUK_INVALID_INDEX < 0);
 
-	if (duk_normalize_index(ctx, idx) < 0) {
+	if (DUK_UNLIKELY(duk_normalize_index(ctx, idx) < 0)) {
 		DUK_ERROR_RANGE_INDEX(thr, idx);
 		return;  /* unreachable */
 	}
@@ -16640,7 +17169,7 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 
 	ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
-	if (ret < min_top) {
+	if (DUK_UNLIKELY(ret < min_top)) {
 		DUK_ERROR_TYPE_INVALID_ARGS(thr);
 	}
 	return ret;
@@ -16852,7 +17381,7 @@
 
 	new_alloc_size = sizeof(duk_tval) * new_size;
 	new_valstack = (duk_tval *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_valstack_ptr, (void *) thr, new_alloc_size);
-	if (!new_valstack) {
+	if (DUK_UNLIKELY(new_valstack == NULL)) {
 		/* Because new_size != 0, if condition doesn't need to be
 		 * (new_valstack != NULL || new_size == 0).
 		 */
@@ -16942,26 +17471,16 @@
 	return 1;
 }
 
-DUK_INTERNAL
-duk_bool_t duk_valstack_resize_raw(duk_context *ctx,
-                                   duk_size_t min_new_size,
-                                   duk_small_uint_t flags) {
+DUK_LOCAL DUK_COLD DUK_NOINLINE duk_bool_t duk__valstack_do_resize(duk_context *ctx,
+                                                                   duk_size_t min_new_size,
+                                                                   duk_small_uint_t flags) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_size_t old_size;
 	duk_size_t new_size;
-	duk_bool_t is_shrink = 0;
-	duk_small_uint_t shrink_flag = (flags & DUK_VSRESIZE_FLAG_SHRINK);
+	duk_bool_t is_shrink;
 	duk_small_uint_t compact_flag = (flags & DUK_VSRESIZE_FLAG_COMPACT);
 	duk_small_uint_t throw_flag = (flags & DUK_VSRESIZE_FLAG_THROW);
 
-	DUK_DDD(DUK_DDDPRINT("check valstack resize: min_new_size=%lu, curr_size=%ld, curr_top=%ld, "
-	                     "curr_bottom=%ld, shrink=%d, compact=%d, throw=%d",
-	                     (unsigned long) min_new_size,
-	                     (long) (thr->valstack_end - thr->valstack),
-	                     (long) (thr->valstack_top - thr->valstack),
-	                     (long) (thr->valstack_bottom - thr->valstack),
-	                     (int) shrink_flag, (int) compact_flag, (int) throw_flag));
-
 	DUK_ASSERT_CTX_VALID(ctx);
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
@@ -16977,11 +17496,8 @@
 
 	if (min_new_size <= old_size) {
 		is_shrink = 1;
-		if (!shrink_flag ||
-		    old_size - min_new_size < DUK_VALSTACK_SHRINK_THRESHOLD) {
-			DUK_DDD(DUK_DDDPRINT("no need to grow or shrink valstack"));
-			return 1;
-		}
+	} else {
+		is_shrink = 0;
 	}
 
 	new_size = min_new_size;
@@ -17000,7 +17516,7 @@
 	                   (unsigned long) old_size, (unsigned long) new_size,
 	                   (unsigned long) min_new_size));
 
-	if (new_size > thr->valstack_max) {
+	if (DUK_UNLIKELY(new_size > thr->valstack_max)) {
 		/* Note: may be triggered even if minimal new_size would not reach the limit,
 		 * plan limit accordingly (taking DUK_VALSTACK_GROW_STEP into account).
 		 */
@@ -17023,7 +17539,7 @@
 	 *  size_t and pointer arithmetic won't wrap in duk__resize_valstack().
 	 */
 
-	if (!duk__resize_valstack(ctx, new_size)) {
+	if (DUK_UNLIKELY(!duk__resize_valstack(ctx, new_size))) {
 		if (is_shrink) {
 			DUK_DD(DUK_DDPRINT("valstack resize failed, but is a shrink, ignore"));
 			return 1;
@@ -17042,6 +17558,44 @@
 	return 1;
 }
 
+DUK_INTERNAL duk_bool_t duk_valstack_resize_raw(duk_context *ctx,
+                                                duk_size_t min_new_size,
+                                                duk_small_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_size_t old_size;
+
+	DUK_DDD(DUK_DDDPRINT("check valstack resize: min_new_size=%lu, curr_size=%ld, curr_top=%ld, "
+	                     "curr_bottom=%ld, flags=%lx",
+	                     (unsigned long) min_new_size,
+	                     (long) (thr->valstack_end - thr->valstack),
+	                     (long) (thr->valstack_top - thr->valstack),
+	                     (long) (thr->valstack_bottom - thr->valstack),
+	                     (unsigned long) flags));
+
+	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+#if defined(DUK_USE_PREFER_SIZE)
+	old_size = (duk_size_t) (thr->valstack_end - thr->valstack);
+#else
+	DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack) == thr->valstack_size);
+	old_size = thr->valstack_size;
+#endif
+
+	if (DUK_LIKELY(min_new_size <= old_size)) {
+		if (DUK_LIKELY((flags & DUK_VSRESIZE_FLAG_SHRINK) == 0 ||
+		               old_size - min_new_size < DUK_VALSTACK_SHRINK_THRESHOLD)) {
+			DUK_DDD(DUK_DDDPRINT("no need to grow or shrink valstack"));
+			return 1;
+		}
+	}
+
+	return duk__valstack_do_resize(ctx, min_new_size, flags);
+}
+
 DUK_EXTERNAL duk_bool_t duk_check_stack(duk_context *ctx, duk_idx_t extra) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_size_t min_new_size;
@@ -17183,7 +17737,7 @@
 	thr = (duk_hthread *) ctx;
 	DUK__CHECK_SPACE();
 
-	if (thr->valstack_top - thr->valstack_bottom <= 0) {
+	if (DUK_UNLIKELY(thr->valstack_top - thr->valstack_bottom <= 0)) {
 		DUK_ERROR_RANGE_INDEX(thr, -1);
 		return;  /* unreachable */
 	}
@@ -17358,27 +17912,27 @@
 	DUK_ASSERT(to_ctx != NULL);
 	DUK_ASSERT(from_ctx != NULL);
 
-	if (to_ctx == from_ctx) {
+	if (DUK_UNLIKELY(to_ctx == from_ctx)) {
 		DUK_ERROR_TYPE(to_thr, DUK_STR_INVALID_CONTEXT);
 		return;
 	}
-	if ((count < 0) ||
-	    (count > (duk_idx_t) to_thr->valstack_max)) {
+	if (DUK_UNLIKELY((count < 0) ||
+	                 (count > (duk_idx_t) to_thr->valstack_max))) {
 		/* Maximum value check ensures 'nbytes' won't wrap below. */
 		DUK_ERROR_RANGE_INVALID_COUNT(to_thr);
 		return;
 	}
 
 	nbytes = sizeof(duk_tval) * count;
-	if (nbytes == 0) {
+	if (DUK_UNLIKELY(nbytes == 0)) {
 		return;
 	}
 	DUK_ASSERT(to_thr->valstack_top <= to_thr->valstack_end);
-	if ((duk_size_t) ((duk_uint8_t *) to_thr->valstack_end - (duk_uint8_t *) to_thr->valstack_top) < nbytes) {
+	if (DUK_UNLIKELY((duk_size_t) ((duk_uint8_t *) to_thr->valstack_end - (duk_uint8_t *) to_thr->valstack_top) < nbytes)) {
 		DUK_ERROR_RANGE_PUSH_BEYOND(to_thr);
 	}
 	src = (void *) ((duk_uint8_t *) from_thr->valstack_top - nbytes);
-	if (src < (void *) from_thr->valstack_bottom) {
+	if (DUK_UNLIKELY(src < (void *) from_thr->valstack_bottom)) {
 		DUK_ERROR_RANGE_INVALID_COUNT(to_thr);
 	}
 
@@ -17413,7 +17967,7 @@
 }
 
 /*
- *  Get/require
+ *  Get/opt/require
  */
 
 DUK_EXTERNAL void duk_require_undefined(duk_context *ctx, duk_idx_t idx) {
@@ -17424,7 +17978,7 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_UNDEFINED(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_UNDEFINED(tv))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "undefined", DUK_STR_NOT_UNDEFINED);
 	}
 }
@@ -17437,13 +17991,13 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_NULL(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_NULL(tv))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "null", DUK_STR_NOT_NULL);
 	}
 }
 
-DUK_EXTERNAL duk_bool_t duk_get_boolean(duk_context *ctx, duk_idx_t idx) {
-	duk_bool_t ret = 0;  /* default: false */
+DUK_LOCAL DUK_ALWAYS_INLINE duk_bool_t duk__get_boolean_raw(duk_context *ctx, duk_idx_t idx, duk_bool_t def_value) {
+	duk_bool_t ret;
 	duk_tval *tv;
 
 	DUK_ASSERT_CTX_VALID(ctx);
@@ -17452,12 +18006,27 @@
 	DUK_ASSERT(tv != NULL);
 	if (DUK_TVAL_IS_BOOLEAN(tv)) {
 		ret = DUK_TVAL_GET_BOOLEAN(tv);
-	}
-
-	DUK_ASSERT(ret == 0 || ret == 1);
+		DUK_ASSERT(ret == 0 || ret == 1);
+	} else {
+		ret = def_value;
+		/* Not guaranteed to be 0 or 1. */
+	}
+
 	return ret;
 }
 
+DUK_EXTERNAL duk_bool_t duk_get_boolean(duk_context *ctx, duk_idx_t idx) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return duk__get_boolean_raw(ctx, idx, 0);  /* default: false */
+}
+
+DUK_EXTERNAL duk_bool_t duk_get_boolean_default(duk_context *ctx, duk_idx_t idx, duk_bool_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return duk__get_boolean_raw(ctx, idx, def_value);
+}
+
 DUK_EXTERNAL duk_bool_t duk_require_boolean(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_tval *tv;
@@ -17467,33 +18036,59 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_BOOLEAN(tv)) {
+	if (DUK_LIKELY(DUK_TVAL_IS_BOOLEAN(tv))) {
+		ret = DUK_TVAL_GET_BOOLEAN(tv);
+		DUK_ASSERT(ret == 0 || ret == 1);
+		return ret;
+	} else {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "boolean", DUK_STR_NOT_BOOLEAN);
 	}
-	ret = DUK_TVAL_GET_BOOLEAN(tv);
-	DUK_ASSERT(ret == 0 || ret == 1);
-	return ret;
+}
+
+DUK_EXTERNAL duk_bool_t duk_opt_boolean(duk_context *ctx, duk_idx_t idx, duk_bool_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_boolean(ctx, idx);
+}
+
+DUK_LOCAL DUK_ALWAYS_INLINE duk_double_t duk__get_number_raw(duk_context *ctx, duk_idx_t idx, duk_double_t def_value) {
+	duk_double_union ret;
+	duk_tval *tv;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	tv = duk_get_tval_or_unused(ctx, idx);
+	DUK_ASSERT(tv != NULL);
+#if defined(DUK_USE_FASTINT)
+	if (DUK_TVAL_IS_FASTINT(tv)) {
+		ret.d = (duk_double_t) DUK_TVAL_GET_FASTINT(tv);  /* XXX: cast trick */
+	}
+	else
+#endif
+	if (DUK_TVAL_IS_DOUBLE(tv)) {
+		/* When using packed duk_tval, number must be in NaN-normalized form
+		 * for it to be a duk_tval, so no need to normalize.  NOP for unpacked
+		 * duk_tval.
+		 */
+		ret.d = DUK_TVAL_GET_DOUBLE(tv);
+		DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&ret));
+	} else {
+		ret.d = def_value;
+		/* Default value (including NaN) may not be normalized. */
+	}
+
+	return ret.d;
 }
 
 DUK_EXTERNAL duk_double_t duk_get_number(duk_context *ctx, duk_idx_t idx) {
-	duk_double_union ret;
-	duk_tval *tv;
-
-	DUK_ASSERT_CTX_VALID(ctx);
-
-	ret.d = DUK_DOUBLE_NAN;  /* default: NaN */
-	tv = duk_get_tval_or_unused(ctx, idx);
-	DUK_ASSERT(tv != NULL);
-	if (DUK_TVAL_IS_NUMBER(tv)) {
-		ret.d = DUK_TVAL_GET_NUMBER(tv);
-	}
-
-	/* When using packed duk_tval, number must be in NaN-normalized form
-	 * for it to be a duk_tval, so no need to normalize.  NOP for unpacked
-	 * duk_tval.
-	 */
-	DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&ret));
-	return ret.d;
+	return duk__get_number_raw(ctx, idx, DUK_DOUBLE_NAN);  /* default: NaN */
+}
+
+DUK_EXTERNAL duk_double_t duk_get_number_default(duk_context *ctx, duk_idx_t idx, duk_double_t def_value) {
+	return duk__get_number_raw(ctx, idx, def_value);
 }
 
 DUK_EXTERNAL duk_double_t duk_require_number(duk_context *ctx, duk_idx_t idx) {
@@ -17505,7 +18100,7 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_NUMBER(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_NUMBER(tv))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "number", DUK_STR_NOT_NUMBER);
 	}
 
@@ -17519,56 +18114,89 @@
 	return ret.d;
 }
 
+DUK_EXTERNAL duk_double_t duk_opt_number(duk_context *ctx, duk_idx_t idx, duk_double_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		/* User provided default is not NaN normalized. */
+		return def_value;
+	}
+	return duk_require_number(ctx, idx);
+}
+
 DUK_EXTERNAL duk_int_t duk_get_int(duk_context *ctx, duk_idx_t idx) {
-	/* Custom coercion for API */
-	DUK_ASSERT_CTX_VALID(ctx);
-	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, 0 /*require*/);
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, 0 /*def_value*/, 0 /*require*/);
 }
 
 DUK_EXTERNAL duk_uint_t duk_get_uint(duk_context *ctx, duk_idx_t idx) {
-	/* Custom coercion for API */
-	DUK_ASSERT_CTX_VALID(ctx);
-	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, 0 /*require*/);
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, 0 /*def_value*/, 0 /*require*/);
+}
+
+DUK_EXTERNAL duk_int_t duk_get_int_default(duk_context *ctx, duk_idx_t idx, duk_int_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, def_value, 0 /*require*/);
+}
+
+DUK_EXTERNAL duk_uint_t duk_get_uint_default(duk_context *ctx, duk_idx_t idx, duk_uint_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, def_value, 0 /*require*/);
 }
 
 DUK_EXTERNAL duk_int_t duk_require_int(duk_context *ctx, duk_idx_t idx) {
-	/* Custom coercion for API */
-	DUK_ASSERT_CTX_VALID(ctx);
-	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, 1 /*require*/);
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, 0 /*def_value*/, 1 /*require*/);
 }
 
 DUK_EXTERNAL duk_uint_t duk_require_uint(duk_context *ctx, duk_idx_t idx) {
-	/* Custom coercion for API */
-	DUK_ASSERT_CTX_VALID(ctx);
-	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, 1 /*require*/);
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, 0 /*def_value*/, 1 /*require*/);
+}
+
+DUK_EXTERNAL duk_int_t duk_opt_int(duk_context *ctx, duk_idx_t idx, duk_int_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_int(ctx, idx);
+}
+
+DUK_EXTERNAL duk_uint_t duk_opt_uint(duk_context *ctx, duk_idx_t idx, duk_uint_t def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_uint(ctx, idx);
 }
 
 DUK_EXTERNAL const char *duk_get_lstring(duk_context *ctx, duk_idx_t idx, duk_size_t *out_len) {
+	duk_hstring *h;
 	const char *ret;
-	duk_tval *tv;
-
-	DUK_ASSERT_CTX_VALID(ctx);
-
-	/* default: NULL, length 0 */
-	ret = NULL;
-	if (out_len) {
-		*out_len = 0;
-	}
-
-	tv = duk_get_tval_or_unused(ctx, idx);
-	DUK_ASSERT(tv != NULL);
-	if (DUK_TVAL_IS_STRING(tv)) {
-		/* Here we rely on duk_hstring instances always being zero
-		 * terminated even if the actual string is not.
-		 */
-		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
-		DUK_ASSERT(h != NULL);
+	duk_size_t len;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	h = duk_get_hstring(ctx, idx);
+	if (h != NULL) {
+		len = DUK_HSTRING_GET_BYTELEN(h);
 		ret = (const char *) DUK_HSTRING_GET_DATA(h);
-		if (out_len) {
-			*out_len = DUK_HSTRING_GET_BYTELEN(h);
-		}
-	}
-
+	} else {
+		len = 0;
+		ret = NULL;
+	}
+
+	if (out_len != NULL) {
+		*out_len = len;
+	}
 	return ret;
 }
 
@@ -17599,9 +18227,72 @@
 }
 
 DUK_EXTERNAL const char *duk_get_string(duk_context *ctx, duk_idx_t idx) {
-	DUK_ASSERT_CTX_VALID(ctx);
-
-	return duk_get_lstring(ctx, idx, NULL);
+	duk_hstring *h;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	h = duk_get_hstring(ctx, idx);
+	if (h != NULL) {
+		return (const char *) DUK_HSTRING_GET_DATA(h);
+	} else {
+		return NULL;
+	}
+}
+
+DUK_EXTERNAL const char *duk_opt_lstring(duk_context *ctx, duk_idx_t idx, duk_size_t *out_len, const char *def_ptr, duk_size_t def_len) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		if (out_len != NULL) {
+			*out_len = def_len;
+		}
+		return def_ptr;
+	}
+	return duk_require_lstring(ctx, idx, out_len);
+}
+
+DUK_EXTERNAL const char *duk_opt_string(duk_context *ctx, duk_idx_t idx, const char *def_ptr) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_ptr;
+	}
+	return duk_require_string(ctx, idx);
+}
+
+DUK_EXTERNAL const char *duk_get_lstring_default(duk_context *ctx, duk_idx_t idx, duk_size_t *out_len, const char *def_ptr, duk_size_t def_len) {
+	duk_hstring *h;
+	const char *ret;
+	duk_size_t len;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	h = duk_get_hstring(ctx, idx);
+	if (h != NULL) {
+		len = DUK_HSTRING_GET_BYTELEN(h);
+		ret = (const char *) DUK_HSTRING_GET_DATA(h);
+	} else {
+		len = def_len;
+		ret = def_ptr;
+	}
+
+	if (out_len != NULL) {
+		*out_len = len;
+	}
+	return ret;
+}
+
+DUK_EXTERNAL const char *duk_get_string_default(duk_context *ctx, duk_idx_t idx, const char *def_value) {
+	duk_hstring *h;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	h = duk_get_hstring(ctx, idx);
+	if (h != NULL) {
+		return (const char *) DUK_HSTRING_GET_DATA(h);
+	} else {
+		return def_value;
+	}
 }
 
 DUK_INTERNAL const char *duk_get_string_notsymbol(duk_context *ctx, duk_idx_t idx) {
@@ -17633,7 +18324,7 @@
 	return (const char *) DUK_HSTRING_GET_DATA(h);
 }
 
-DUK_EXTERNAL void *duk_get_pointer(duk_context *ctx, duk_idx_t idx) {
+DUK_LOCAL void *duk__get_pointer_raw(duk_context *ctx, duk_idx_t idx, void *def_value) {
 	duk_tval *tv;
 	void *p;
 
@@ -17642,13 +18333,30 @@
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
 	if (!DUK_TVAL_IS_POINTER(tv)) {
-		return NULL;
+		return def_value;
 	}
 
 	p = DUK_TVAL_GET_POINTER(tv);  /* may be NULL */
 	return p;
 }
 
+DUK_EXTERNAL void *duk_get_pointer(duk_context *ctx, duk_idx_t idx) {
+	return duk__get_pointer_raw(ctx, idx, NULL /*def_value*/);
+}
+
+DUK_EXTERNAL void *duk_opt_pointer(duk_context *ctx, duk_idx_t idx, void *def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_pointer(ctx, idx);
+}
+
+DUK_EXTERNAL void *duk_get_pointer_default(duk_context *ctx, duk_idx_t idx, void *def_value) {
+	return duk__get_pointer_raw(ctx, idx, def_value);
+}
+
 DUK_EXTERNAL void *duk_require_pointer(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_tval *tv;
@@ -17661,7 +18369,7 @@
 	 */
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_POINTER(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_POINTER(tv))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "pointer", DUK_STR_NOT_POINTER);
 	}
 	p = DUK_TVAL_GET_POINTER(tv);  /* may be NULL */
@@ -17687,10 +18395,12 @@
 }
 #endif
 
-DUK_LOCAL void *duk__get_buffer_helper(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, duk_bool_t throw_flag) {
-	duk_hthread *thr = (duk_hthread *) ctx;
-	duk_tval *tv;
+DUK_LOCAL void *duk__get_buffer_helper(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size, duk_bool_t throw_flag) {
+	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_hbuffer *h;
+	void *ret;
+	duk_size_t len;
+	duk_tval *tv;
 
 	DUK_ASSERT_CTX_VALID(ctx);
 	DUK_UNREF(thr);
@@ -17701,27 +18411,54 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_BUFFER(tv)) {
+	if (DUK_LIKELY(DUK_TVAL_IS_BUFFER(tv))) {
+		h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+
+		len = DUK_HBUFFER_GET_SIZE(h);
+		ret = DUK_HBUFFER_GET_DATA_PTR(thr->heap, h);
+	} else {
 		if (throw_flag) {
 			DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "buffer", DUK_STR_NOT_BUFFER);
 		}
-		return NULL;
-	}
-
-	h = DUK_TVAL_GET_BUFFER(tv);
-	DUK_ASSERT(h != NULL);
-	if (out_size) {
-		*out_size = DUK_HBUFFER_GET_SIZE(h);
-	}
-	return (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h);  /* may be NULL (but only if size is 0) */
+		len = def_size;
+		ret = def_ptr;
+	}
+
+	if (out_size != NULL) {
+		*out_size = len;
+	}
+	return ret;
 }
 
 DUK_EXTERNAL void *duk_get_buffer(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size) {
-	return duk__get_buffer_helper(ctx, idx, out_size, 0 /*throw_flag*/);
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return duk__get_buffer_helper(ctx, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/);
+}
+
+DUK_EXTERNAL void *duk_opt_buffer(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		if (out_size != NULL) {
+			*out_size = def_size;
+		}
+		return def_ptr;
+	}
+	return duk_require_buffer(ctx, idx, out_size);
+}
+
+DUK_EXTERNAL void *duk_get_buffer_default(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_len) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return duk__get_buffer_helper(ctx, idx, out_size, def_ptr, def_len, 0 /*throw_flag*/);
 }
 
 DUK_EXTERNAL void *duk_require_buffer(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size) {
-	return duk__get_buffer_helper(ctx, idx, out_size, 1 /*throw_flag*/);
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	return duk__get_buffer_helper(ctx, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 1 /*throw_flag*/);
 }
 
 /* Get the active buffer data area for a plain buffer or a buffer object.
@@ -17729,7 +18466,7 @@
  * have a NULL data pointer when its size is zero, the optional 'out_isbuffer'
  * argument allows caller to detect this reliably.
  */
-DUK_INTERNAL void *duk_get_buffer_data_raw(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, duk_bool_t throw_flag, duk_bool_t *out_isbuffer) {
+DUK_INTERNAL void *duk_get_buffer_data_raw(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size, duk_bool_t throw_flag, duk_bool_t *out_isbuffer) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_tval *tv;
 
@@ -17740,7 +18477,7 @@
 		*out_isbuffer = 0;
 	}
 	if (out_size != NULL) {
-		*out_size = 0;
+		*out_size = def_size;
 	}
 
 	tv = duk_get_tval_or_unused(ctx, idx);
@@ -17789,15 +18526,31 @@
 	if (throw_flag) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "buffer", DUK_STR_NOT_BUFFER);
 	}
-	return NULL;
+	return def_ptr;
 }
 
 DUK_EXTERNAL void *duk_get_buffer_data(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size) {
-	return duk_get_buffer_data_raw(ctx, idx, out_size, 0 /*throw_flag*/, NULL);
+	return duk_get_buffer_data_raw(ctx, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/, NULL);
+}
+
+DUK_EXTERNAL void *duk_get_buffer_data_default(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size) {
+	return duk_get_buffer_data_raw(ctx, idx, out_size, def_ptr, def_size, 0 /*throw_flag*/, NULL);
+}
+
+DUK_EXTERNAL void *duk_opt_buffer_data(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		if (out_size != NULL) {
+			*out_size = def_size;
+		}
+		return def_ptr;
+	}
+	return duk_require_buffer_data(ctx, idx, out_size);
 }
 
 DUK_EXTERNAL void *duk_require_buffer_data(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size) {
-	return duk_get_buffer_data_raw(ctx, idx, out_size, 1 /*throw_flag*/, NULL);
+	return duk_get_buffer_data_raw(ctx, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 1 /*throw_flag*/, NULL);
 }
 
 /* Raw helper for getting a value from the stack, checking its tag.
@@ -17829,7 +18582,7 @@
 
 DUK_INTERNAL duk_hstring *duk_get_hstring_notsymbol(duk_context *ctx, duk_idx_t idx) {
 	duk_hstring *res = (duk_hstring *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_STRING);
-	if (res && DUK_HSTRING_HAS_SYMBOL(res)) {
+	if (DUK_UNLIKELY(res && DUK_HSTRING_HAS_SYMBOL(res))) {
 		return NULL;
 	}
 	return res;
@@ -17838,7 +18591,7 @@
 DUK_INTERNAL duk_hstring *duk_require_hstring(duk_context *ctx, duk_idx_t idx) {
 	duk_hstring *h;
 	h = (duk_hstring *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_STRING);
-	if (h == NULL) {
+	if (DUK_UNLIKELY(h == NULL)) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, idx, "string", DUK_STR_NOT_STRING);
 	}
 	return h;
@@ -17847,7 +18600,7 @@
 DUK_INTERNAL duk_hstring *duk_require_hstring_notsymbol(duk_context *ctx, duk_idx_t idx) {
 	duk_hstring *h;
 	h = (duk_hstring *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_STRING);
-	if (h == NULL || DUK_HSTRING_HAS_SYMBOL(h)) {
+	if (DUK_UNLIKELY(h == NULL || DUK_HSTRING_HAS_SYMBOL(h))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, idx, "string", DUK_STR_NOT_STRING);
 	}
 	return h;
@@ -17860,7 +18613,7 @@
 DUK_INTERNAL duk_hobject *duk_require_hobject(duk_context *ctx, duk_idx_t idx) {
 	duk_hobject *h;
 	h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (h == NULL) {
+	if (DUK_UNLIKELY(h == NULL)) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, idx, "object", DUK_STR_NOT_OBJECT);
 	}
 	return h;
@@ -17873,7 +18626,7 @@
 DUK_INTERNAL duk_hbuffer *duk_require_hbuffer(duk_context *ctx, duk_idx_t idx) {
 	duk_hbuffer *h;
 	h = (duk_hbuffer *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_BUFFER);
-	if (h == NULL) {
+	if (DUK_UNLIKELY(h == NULL)) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, idx, "buffer", DUK_STR_NOT_BUFFER);
 	}
 	return h;
@@ -17881,7 +18634,7 @@
 
 DUK_INTERNAL duk_hthread *duk_get_hthread(duk_context *ctx, duk_idx_t idx) {
 	duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (h != NULL && !DUK_HOBJECT_IS_THREAD(h)) {
+	if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_IS_THREAD(h))) {
 		h = NULL;
 	}
 	return (duk_hthread *) h;
@@ -17890,7 +18643,7 @@
 DUK_INTERNAL duk_hthread *duk_require_hthread(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (!(h != NULL && DUK_HOBJECT_IS_THREAD(h))) {
+	if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_IS_THREAD(h)))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "thread", DUK_STR_NOT_THREAD);
 	}
 	return (duk_hthread *) h;
@@ -17898,7 +18651,7 @@
 
 DUK_INTERNAL duk_hcompfunc *duk_get_hcompfunc(duk_context *ctx, duk_idx_t idx) {
 	duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (h != NULL && !DUK_HOBJECT_IS_COMPFUNC(h)) {
+	if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_IS_COMPFUNC(h))) {
 		h = NULL;
 	}
 	return (duk_hcompfunc *) h;
@@ -17907,7 +18660,7 @@
 DUK_INTERNAL duk_hcompfunc *duk_require_hcompfunc(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (!(h != NULL && DUK_HOBJECT_IS_COMPFUNC(h))) {
+	if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_IS_COMPFUNC(h)))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "compiledfunction", DUK_STR_NOT_COMPFUNC);
 	}
 	return (duk_hcompfunc *) h;
@@ -17915,7 +18668,7 @@
 
 DUK_INTERNAL duk_hnatfunc *duk_get_hnatfunc(duk_context *ctx, duk_idx_t idx) {
 	duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (h != NULL && !DUK_HOBJECT_IS_NATFUNC(h)) {
+	if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_IS_NATFUNC(h))) {
 		h = NULL;
 	}
 	return (duk_hnatfunc *) h;
@@ -17924,7 +18677,7 @@
 DUK_INTERNAL duk_hnatfunc *duk_require_hnatfunc(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (!(h != NULL && DUK_HOBJECT_IS_NATFUNC(h))) {
+	if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_IS_NATFUNC(h)))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "nativefunction", DUK_STR_NOT_NATFUNC);
 	}
 	return (duk_hnatfunc *) h;
@@ -17939,13 +18692,13 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_OBJECT(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_OBJECT(tv))) {
 		return NULL;
 	}
 	h = DUK_TVAL_GET_OBJECT(tv);
 	DUK_ASSERT(h != NULL);
 
-	if (!DUK_HOBJECT_IS_NATFUNC(h)) {
+	if (DUK_UNLIKELY(!DUK_HOBJECT_IS_NATFUNC(h))) {
 		return NULL;
 	}
 	DUK_ASSERT(DUK_HOBJECT_HAS_NATFUNC(h));
@@ -17954,6 +18707,28 @@
 	return f->func;
 }
 
+DUK_EXTERNAL duk_c_function duk_opt_c_function(duk_context *ctx, duk_idx_t idx, duk_c_function def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_c_function(ctx, idx);
+}
+
+DUK_EXTERNAL duk_c_function duk_get_c_function_default(duk_context *ctx, duk_idx_t idx, duk_c_function def_value) {
+	duk_c_function ret;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	ret = duk_get_c_function(ctx, idx);
+	if (ret != NULL) {
+		return ret;
+	}
+
+	return def_value;
+}
+
 DUK_EXTERNAL duk_c_function duk_require_c_function(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_c_function ret;
@@ -17961,14 +18736,14 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 
 	ret = duk_get_c_function(ctx, idx);
-	if (!ret) {
+	if (DUK_UNLIKELY(!ret)) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "nativefunction", DUK_STR_NOT_NATFUNC);
 	}
 	return ret;
 }
 
 DUK_EXTERNAL void duk_require_function(duk_context *ctx, duk_idx_t idx) {
-	if (!duk_is_function(ctx, idx)) {
+	if (DUK_UNLIKELY(!duk_is_function(ctx, idx))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX((duk_hthread *) ctx, idx, "function", DUK_STR_NOT_FUNCTION);
 	}
 }
@@ -17977,7 +18752,7 @@
 	duk_hobject *h;
 
 	h = duk_require_hobject_accept_mask(ctx, idx, DUK_TYPE_MASK_LIGHTFUNC);
-	if (h != NULL && !DUK_HOBJECT_HAS_CONSTRUCTABLE(h)) {
+	if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_HAS_CONSTRUCTABLE(h))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX((duk_hthread *) ctx, idx, "constructable", DUK_STR_NOT_CONSTRUCTABLE);
 	}
 	/* Lightfuncs (h == NULL) are constructable. */
@@ -17995,6 +18770,28 @@
 	return (duk_context *) duk_require_hthread(ctx, idx);
 }
 
+DUK_EXTERNAL duk_context *duk_opt_context(duk_context *ctx, duk_idx_t idx, duk_context *def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_context(ctx, idx);
+}
+
+DUK_EXTERNAL_DECL duk_context *duk_get_context_default(duk_context *ctx, duk_idx_t idx, duk_context *def_value) {
+	duk_context *ret;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	ret = duk_get_context(ctx, idx);
+	if (ret != NULL) {
+		return ret;
+	}
+
+	return def_value;
+}
+
 DUK_EXTERNAL void *duk_get_heapptr(duk_context *ctx, duk_idx_t idx) {
 	duk_tval *tv;
 	void *ret;
@@ -18003,7 +18800,7 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_HEAP_ALLOCATED(tv))) {
 		return (void *) NULL;
 	}
 
@@ -18012,6 +18809,28 @@
 	return ret;
 }
 
+DUK_EXTERNAL void *duk_opt_heapptr(duk_context *ctx, duk_idx_t idx, void *def_value) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	if (duk_check_type_mask(ctx, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) {
+		return def_value;
+	}
+	return duk_require_heapptr(ctx, idx);
+}
+
+DUK_EXTERNAL_DECL void *duk_get_heapptr_default(duk_context *ctx, duk_idx_t idx, void *def_value) {
+	void *ret;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	ret = duk_get_heapptr(ctx, idx);
+	if (ret != NULL) {
+		return ret;
+	}
+
+	return def_value;
+}
+
 DUK_EXTERNAL void *duk_require_heapptr(duk_context *ctx, duk_idx_t idx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_tval *tv;
@@ -18021,7 +18840,7 @@
 
 	tv = duk_get_tval_or_unused(ctx, idx);
 	DUK_ASSERT(tv != NULL);
-	if (!DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+	if (DUK_UNLIKELY(!DUK_TVAL_IS_HEAP_ALLOCATED(tv))) {
 		DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "heapobject", DUK_STR_UNEXPECTED_TYPE);
 	}
 
@@ -18038,7 +18857,7 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 
 	res = duk_get_hobject(ctx, idx);  /* common case, not promoted */
-	if (res != NULL) {
+	if (DUK_LIKELY(res != NULL)) {
 		DUK_ASSERT(res != NULL);
 		return res;
 	}
@@ -18093,7 +18912,7 @@
 	DUK_ASSERT(classnum <= DUK_HOBJECT_CLASS_MAX);
 
 	h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) != classnum) {
+	if (DUK_UNLIKELY(h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) != classnum)) {
 		h = NULL;
 	}
 	return h;
@@ -18109,7 +18928,7 @@
 	thr = (duk_hthread *) ctx;
 
 	h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, idx, DUK_TAG_OBJECT);
-	if (!(h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) == classnum)) {
+	if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) == classnum))) {
 		duk_hstring *h_class;
 		h_class = DUK_HTHREAD_GET_STRING(thr, DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum));
 		DUK_UNREF(h_class);
@@ -18151,7 +18970,7 @@
 	case DUK_TAG_STRING: {
 		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			return 0;
 		}
 		return (duk_size_t) DUK_HSTRING_GET_CHARLEN(h);
@@ -18453,6 +19272,16 @@
 
 	tv = duk_require_tval(ctx, idx);
 	DUK_ASSERT(tv != NULL);
+
+#if defined(DUK_USE_FASTINT)
+	/* If argument is a fastint, guarantee that it remains one.
+	 * There's no downgrade check for other cases.
+	 */
+	if (DUK_TVAL_IS_FASTINT(tv)) {
+		/* XXX: Unnecessary conversion back and forth. */
+		return (duk_double_t) DUK_TVAL_GET_FASTINT(tv);
+	}
+#endif
 	d = coerce_func(thr, tv);
 
 	/* XXX: fastint? */
@@ -18464,21 +19293,21 @@
 }
 
 DUK_EXTERNAL duk_int_t duk_to_int(duk_context *ctx, duk_idx_t idx) {
-	/* Value coercion (in stack): ToInteger(), E5 Section 9.4
-	 * API return value coercion: custom
+	/* Value coercion (in stack): ToInteger(), E5 Section 9.4,
+	 * API return value coercion: custom.
 	 */
 	DUK_ASSERT_CTX_VALID(ctx);
 	(void) duk__to_int_uint_helper(ctx, idx, duk_js_tointeger);
-	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, 0 /*require*/);
+	return (duk_int_t) duk__api_coerce_d2i(ctx, idx, 0 /*def_value*/, 0 /*require*/);
 }
 
 DUK_EXTERNAL duk_uint_t duk_to_uint(duk_context *ctx, duk_idx_t idx) {
-	/* Value coercion (in stack): ToInteger(), E5 Section 9.4
-	 * API return value coercion: custom
+	/* Value coercion (in stack): ToInteger(), E5 Section 9.4,
+	 * API return value coercion: custom.
 	 */
 	DUK_ASSERT_CTX_VALID(ctx);
 	(void) duk__to_int_uint_helper(ctx, idx, duk_js_tointeger);
-	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, 0 /*require*/);
+	return (duk_uint_t) duk__api_coerce_d2ui(ctx, idx, 0 /*def_value*/, 0 /*require*/);
 }
 
 DUK_EXTERNAL duk_int32_t duk_to_int32(duk_context *ctx, duk_idx_t idx) {
@@ -18682,7 +19511,7 @@
 		duk_hstring *h;
 		h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			stridx = DUK_STRIDX_UC_SYMBOL;
 		} else {
 			stridx = DUK_STRIDX_UC_STRING;
@@ -18840,7 +19669,7 @@
 		duk_hstring *h;
 		h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			DUK_ERROR_TYPE((duk_hthread *) ctx, DUK_STR_CANNOT_STRING_COERCE_SYMBOL);
 		} else {
 			goto skip_replace;
@@ -18920,7 +19749,7 @@
 	duk_hstring *ret;
 	DUK_ASSERT_CTX_VALID(ctx);
 	ret = duk_get_hstring(ctx, idx);
-	if (ret && DUK_HSTRING_HAS_SYMBOL(ret)) {
+	if (DUK_UNLIKELY(ret && DUK_HSTRING_HAS_SYMBOL(ret))) {
 		return ret;
 	}
 	return duk_to_hstring(ctx, idx);
@@ -19073,6 +19902,7 @@
 
 	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
 	        DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+	        DUK_HOBJECT_FLAG_FASTREFS |
 	        DUK_HOBJECT_FLAG_NATFUNC |
 	        DUK_HOBJECT_FLAG_NEWENV |
 	        DUK_HOBJECT_FLAG_STRICT |
@@ -19123,6 +19953,7 @@
 	}
 	case DUK_TAG_BOOLEAN: {
 		flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		        DUK_HOBJECT_FLAG_FASTREFS |
 		        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN);
 		proto = DUK_BIDX_BOOLEAN_PROTOTYPE;
 		goto create_object;
@@ -19131,12 +19962,14 @@
 		duk_hstring *h;
 		h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+			        DUK_HOBJECT_FLAG_FASTREFS |
 			        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_SYMBOL);
 			proto = DUK_BIDX_SYMBOL_PROTOTYPE;
 		} else {
 			flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+			        DUK_HOBJECT_FLAG_FASTREFS |
 			        DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ |
 			        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING);
 			proto = DUK_BIDX_STRING_PROTOTYPE;
@@ -19166,6 +19999,7 @@
 #endif  /* DUK_USE_BUFFEROBJECT_SUPPORT */
 	case DUK_TAG_POINTER: {
 		flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		        DUK_HOBJECT_FLAG_FASTREFS |
 		        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER);
 		proto = DUK_BIDX_POINTER_PROTOTYPE;
 		goto create_object;
@@ -19194,7 +20028,8 @@
 		DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
 		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
 		flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
-		               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER);
+		        DUK_HOBJECT_FLAG_FASTREFS |
+		        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER);
 		proto = DUK_BIDX_NUMBER_PROTOTYPE;
 		goto create_object;
 	}
@@ -19412,7 +20247,7 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
-	if (duk_get_type_mask(ctx, idx) & mask) {
+	if (DUK_LIKELY(duk_get_type_mask(ctx, idx) & mask)) {
 		return 1;
 	}
 	if (mask & DUK_TYPE_MASK_THROW) {
@@ -19538,7 +20373,10 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 	h = duk_get_hstring(ctx, idx);
-	if (h != NULL && DUK_HSTRING_HAS_SYMBOL(h)) {
+	/* Use DUK_LIKELY() here because caller may be more likely to type
+	 * check an expected symbol than not.
+	 */
+	if (DUK_LIKELY(h != NULL && DUK_HSTRING_HAS_SYMBOL(h))) {
 		return 1;
 	}
 	return 0;
@@ -19594,10 +20432,15 @@
 }
 
 DUK_EXTERNAL duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t idx) {
-	DUK_ASSERT_CTX_VALID(ctx);
-	return duk__obj_flag_any_default_false(ctx,
-	                                       idx,
-	                                       DUK_HOBJECT_FLAG_THREAD);
+	duk_hobject *obj;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	obj = duk_get_hobject(ctx, idx);
+	if (obj) {
+		return (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_THREAD ? 1 : 0);
+	}
+	return 0;
 }
 
 DUK_EXTERNAL duk_bool_t duk_is_fixed_buffer(duk_context *ctx, duk_idx_t idx) {
@@ -19868,9 +20711,7 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 
 	/* check stack before interning (avoid hanging temp) */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
+	DUK__CHECK_SPACE();
 
 	/* NULL with zero length represents an empty string; NULL with higher
 	 * length is also now trated like an empty string although it is
@@ -19882,11 +20723,11 @@
 	}
 
 	/* Check for maximum string length */
-	if (len > DUK_HSTRING_MAX_BYTELEN) {
+	if (DUK_UNLIKELY(len > DUK_HSTRING_MAX_BYTELEN)) {
 		DUK_ERROR_RANGE(thr, DUK_STR_STRING_TOO_LONG);
 	}
 
-	h = duk_heap_string_intern_checked(thr, (const duk_uint8_t *) str, (duk_uint32_t) len);
+	h = duk_heap_strtable_intern_checked(thr, (const duk_uint8_t *) str, (duk_uint32_t) len);
 	DUK_ASSERT(h != NULL);
 
 	tv_slot = thr->valstack_top++;
@@ -20004,6 +20845,7 @@
 	thr = (duk_hthread *) ctx;
 
 	DUK_ASSERT(thr->callstack_top > 0);  /* caller required to know */
+	DUK_ASSERT(thr->callstack_curr != NULL);
 	DUK_ASSERT(thr->valstack_bottom > thr->valstack);  /* consequence of above */
 	DUK_ASSERT(thr->valstack_bottom - 1 >= thr->valstack);  /* 'this' binding exists */
 
@@ -20019,8 +20861,8 @@
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
 	DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
 
-	act = duk_hthread_get_current_activation(thr);
-	if (act) {
+	act = thr->callstack_curr;
+	if (act != NULL) {
 		duk_push_tval(ctx, &act->tv_func);
 	} else {
 		duk_push_undefined(ctx);
@@ -20078,7 +20920,7 @@
 DUK_EXTERNAL void duk_push_thread_stash(duk_context *ctx, duk_context *target_ctx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	DUK_ASSERT_CTX_VALID(ctx);
-	if (!target_ctx) {
+	if (DUK_UNLIKELY(target_ctx == NULL)) {
 		DUK_ERROR_TYPE_INVALID_ARGS(thr);
 		return;  /* not reached */
 	}
@@ -20155,7 +20997,7 @@
 
 		/* failed, resize and try again */
 		sz = sz * 2;
-		if (sz >= DUK_PUSH_SPRINTF_SANITY_LIMIT) {
+		if (DUK_UNLIKELY(sz >= DUK_PUSH_SPRINTF_SANITY_LIMIT)) {
 			DUK_ERROR_RANGE(thr, DUK_STR_RESULT_TOO_LONG);
 		}
 	}
@@ -20193,15 +21035,10 @@
 	DUK_ASSERT(prototype_bidx == -1 ||
 	           (prototype_bidx >= 0 && prototype_bidx < DUK_NUM_BUILTINS));
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
-
-	h = duk_hobject_alloc(thr->heap, hobject_flags_and_class);
-	if (!h) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-	}
+	DUK__CHECK_SPACE();
+
+	h = duk_hobject_alloc(thr, hobject_flags_and_class);
+	DUK_ASSERT(h != NULL);
 
 	DUK_DDD(DUK_DDDPRINT("created object with flags: 0x%08lx", (unsigned long) h->hdr.h_flags));
 
@@ -20240,6 +21077,7 @@
 
 	(void) duk_push_object_helper(ctx,
 	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_FLAG_FASTREFS |
 	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                              DUK_BIDX_OBJECT_PROTOTYPE);
 	return duk_get_top_index_unsafe(ctx);
@@ -20255,14 +21093,13 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 
 	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	        DUK_HOBJECT_FLAG_FASTREFS |
 	        DUK_HOBJECT_FLAG_ARRAY_PART |
 	        DUK_HOBJECT_FLAG_EXOTIC_ARRAY |
 	        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAY);
 
-	obj = duk_harray_alloc(thr->heap, flags);
-	if (!obj) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-	}
+	obj = duk_harray_alloc(thr, flags);
+	DUK_ASSERT(obj != NULL);
 
 	/* XXX: since prototype is NULL, could save a check */
 	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_ARRAY_PROTOTYPE]);
@@ -20316,18 +21153,12 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
-
-	obj = duk_hthread_alloc(thr->heap,
+	DUK__CHECK_SPACE();
+
+	obj = duk_hthread_alloc(thr,
 	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                        DUK_HOBJECT_FLAG_THREAD |
 	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD));
-	if (!obj) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-	}
+	DUK_ASSERT(obj != NULL);
 	obj->state = DUK_HTHREAD_STATE_INACTIVE;
 #if defined(DUK_USE_ROM_STRINGS)
 	/* Nothing to initialize, strs[] is in ROM. */
@@ -20348,7 +21179,7 @@
 	thr->valstack_top++;
 
 	/* important to do this *after* pushing, to make the thread reachable for gc */
-	if (!duk_hthread_init_stacks(thr->heap, obj)) {
+	if (DUK_UNLIKELY(!duk_hthread_init_stacks(thr->heap, obj))) {
 		DUK_ERROR_ALLOC_FAILED(thr);
 	}
 
@@ -20379,21 +21210,18 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
+	DUK__CHECK_SPACE();
 
 	/* Template functions are not strictly constructable (they don't
 	 * have a "prototype" property for instance), so leave the
 	 * DUK_HOBJECT_FLAG_CONSRUCTABLE flag cleared here.
 	 */
 
-	obj = duk_hcompfunc_alloc(thr->heap,
+	obj = duk_hcompfunc_alloc(thr,
 	                          DUK_HOBJECT_FLAG_EXTENSIBLE |
 	                          DUK_HOBJECT_FLAG_COMPFUNC |
 	                          DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION));
-	if (!obj) {
+	if (DUK_UNLIKELY(obj == NULL)) {
 		DUK_ERROR_ALLOC_FAILED(thr);
 	}
 
@@ -20418,11 +21246,9 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
-	if (func == NULL) {
+	DUK__CHECK_SPACE();
+
+	if (DUK_UNLIKELY(func == NULL)) {
 		goto api_error;
 	}
 	if (nargs >= 0 && nargs < DUK_HNATFUNC_NARGS_MAX) {
@@ -20433,10 +21259,8 @@
 		goto api_error;
 	}
 
-	obj = duk_hnatfunc_alloc(thr->heap, flags);
-	if (!obj) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-	}
+	obj = duk_hnatfunc_alloc(thr, flags);
+	DUK_ASSERT(obj != NULL);
 
 	obj->func = func;
 	obj->nargs = func_nargs;
@@ -20467,6 +21291,7 @@
 
 	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
 	        DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+	        DUK_HOBJECT_FLAG_FASTREFS |
 	        DUK_HOBJECT_FLAG_NATFUNC |
 	        DUK_HOBJECT_FLAG_NEWENV |
 	        DUK_HOBJECT_FLAG_STRICT |
@@ -20484,6 +21309,7 @@
 
 	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
 	        DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+	        DUK_HOBJECT_FLAG_FASTREFS |
 	        DUK_HOBJECT_FLAG_NATFUNC |
 	        DUK_HOBJECT_FLAG_NEWENV |
 	        DUK_HOBJECT_FLAG_STRICT |
@@ -20499,6 +21325,7 @@
 	DUK_ASSERT_CTX_VALID(ctx);
 
 	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	        DUK_HOBJECT_FLAG_FASTREFS |
 	        DUK_HOBJECT_FLAG_NATFUNC |
 	        DUK_HOBJECT_FLAG_NEWENV |
 	        DUK_HOBJECT_FLAG_STRICT |
@@ -20515,10 +21342,7 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
+	DUK__CHECK_SPACE();
 
 	if (nargs >= DUK_LFUNC_NARGS_MIN && nargs <= DUK_LFUNC_NARGS_MAX) {
 		/* as is */
@@ -20527,10 +21351,10 @@
 	} else {
 		goto api_error;
 	}
-	if (!(length >= DUK_LFUNC_LENGTH_MIN && length <= DUK_LFUNC_LENGTH_MAX)) {
+	if (DUK_UNLIKELY(!(length >= DUK_LFUNC_LENGTH_MIN && length <= DUK_LFUNC_LENGTH_MAX))) {
 		goto api_error;
 	}
-	if (!(magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX)) {
+	if (DUK_UNLIKELY(!(magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX))) {
 		goto api_error;
 	}
 
@@ -20554,15 +21378,10 @@
 	DUK_ASSERT(ctx != NULL);
 	DUK_ASSERT(prototype_bidx >= 0);
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
-
-	obj = duk_hbufobj_alloc(thr->heap, hobject_flags_and_class);
-	if (!obj) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-	}
+	DUK__CHECK_SPACE();
+
+	obj = duk_hbufobj_alloc(thr, hobject_flags_and_class);
+	DUK_ASSERT(obj != NULL);
 
 	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[prototype_bidx]);
 	DUK_ASSERT_HBUFOBJ_VALID(obj);
@@ -20622,19 +21441,19 @@
 	uint_offset = (duk_uint_t) byte_offset;
 	uint_length = (duk_uint_t) byte_length;
 	if (sizeof(duk_size_t) != sizeof(duk_uint_t)) {
-		if ((duk_size_t) uint_offset != byte_offset || (duk_size_t) uint_length != byte_length) {
+		if (DUK_UNLIKELY((duk_size_t) uint_offset != byte_offset || (duk_size_t) uint_length != byte_length)) {
 			goto range_error;
 		}
 	}
 	uint_added = uint_offset + uint_length;
-	if (uint_added < uint_offset) {
+	if (DUK_UNLIKELY(uint_added < uint_offset)) {
 		goto range_error;
 	}
 	DUK_ASSERT(uint_added >= uint_offset && uint_added >= uint_length);
 
 	DUK_ASSERT_DISABLE(flags >= 0);  /* flags is unsigned */
-	lookupidx = flags & 0x0f;  /* 4 low bits */
-	if (lookupidx >= sizeof(duk__bufobj_flags_lookup) / sizeof(duk_uint32_t)) {
+	lookupidx = flags;
+	if (DUK_UNLIKELY(lookupidx >= sizeof(duk__bufobj_flags_lookup) / sizeof(duk_uint32_t))) {
 		goto arg_error;
 	}
 	tmp = duk__bufobj_flags_lookup[lookupidx];
@@ -20663,39 +21482,9 @@
 	/* TypedArray views need an automatic ArrayBuffer which must be
 	 * provided as .buffer property of the view.  The ArrayBuffer is
 	 * referenced via duk_hbufobj->buf_prop and an inherited .buffer
-	 * accessor returns it.
-	 *
-	 * The ArrayBuffer offset is always set to zero, so that if one
-	 * accesses the ArrayBuffer at the view's .byteOffset, the value
-	 * matches the view at index 0.
-	 */
-	if (flags & DUK_BUFOBJ_CREATE_ARRBUF) {
-		duk_hbufobj *h_arrbuf;
-
-		h_arrbuf = duk_push_bufobj_raw(ctx,
-		                               DUK_HOBJECT_FLAG_EXTENSIBLE |
-		                               DUK_HOBJECT_FLAG_BUFOBJ |
-		                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER),
-		                               DUK_BIDX_ARRAYBUFFER_PROTOTYPE);
-		DUK_ASSERT(h_arrbuf != NULL);
-
-		h_arrbuf->buf = h_val;
-		DUK_HBUFFER_INCREF(thr, h_val);
-		h_arrbuf->offset = 0;
-		h_arrbuf->length = uint_offset + uint_length;  /* Wrap checked above. */
-		DUK_ASSERT(h_arrbuf->shift == 0);
-		h_arrbuf->elem_type = DUK_HBUFOBJ_ELEM_UINT8;
-		DUK_ASSERT(h_arrbuf->is_typedarray == 0);
-		DUK_ASSERT_HBUFOBJ_VALID(h_arrbuf);
-		DUK_ASSERT(h_arrbuf->buf_prop == NULL);
-
-		DUK_ASSERT(h_bufobj->buf_prop == NULL);
-		h_bufobj->buf_prop = (duk_hobject *) h_arrbuf;
-		DUK_HBUFOBJ_INCREF(thr, h_arrbuf);  /* Now reachable and accounted for. */
-
-		duk_pop(ctx);
-	}
-
+	 * accessor returns it.  The ArrayBuffer is created lazily on first
+	 * access so we don't need to do anything more here.
+	 */
 	return;
 
  range_error:
@@ -20738,6 +21527,7 @@
 	proto = duk_error_prototype_from_code(thr, err_code);
 	(void) duk_push_object_helper_proto(ctx,
 	                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                    DUK_HOBJECT_FLAG_FASTREFS |
 	                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR),
 	                                    proto);
 
@@ -20805,18 +21595,15 @@
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
-	/* check stack first */
-	if (thr->valstack_top >= thr->valstack_end) {
-		DUK_ERROR_RANGE_PUSH_BEYOND(thr);
-	}
+	DUK__CHECK_SPACE();
 
 	/* Check for maximum buffer length. */
-	if (size > DUK_HBUFFER_MAX_BYTELEN) {
+	if (DUK_UNLIKELY(size > DUK_HBUFFER_MAX_BYTELEN)) {
 		DUK_ERROR_RANGE(thr, DUK_STR_BUFFER_TOO_LONG);
 	}
 
 	h = duk_hbuffer_alloc(thr->heap, size, flags, &buf_data);
-	if (!h) {
+	if (DUK_UNLIKELY(h == NULL)) {
 		DUK_ERROR_ALLOC_FAILED(thr);
 	}
 
@@ -20870,46 +21657,66 @@
 	 * by seeing that X's FINALIZED flag is set (which is done before
 	 * the finalizer starts executing).
 	 */
+#if defined(DUK_USE_FINALIZER_SUPPORT)
 	for (curr = thr->heap->finalize_list;
 	     curr != NULL;
 	     curr = DUK_HEAPHDR_GET_NEXT(thr->heap, curr)) {
+		/* FINALIZABLE is set for all objects on finalize_list
+		 * except for an object being finalized right now.  So
+		 * can't assert here.
+		 */
+#if 0
+		DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE(curr));
+#endif
+
 		if (curr == h) {
 			if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h)) {
 				/* Object is currently being finalized. */
 				DUK_ASSERT(found == 0);  /* Would indicate corrupted lists. */
 				found = 1;
 			} else {
-				DUK_ASSERT(0);
-			}
-		}
-	}
-
-	/* Also check for the refzero_list; must not be there unless it is
-	 * being finalized when duk_push_heapptr() is called.
-	 *
-	 * Corner case: similar to finalize_list.
-	 */
-#if defined(DUK_USE_REFERENCE_COUNTING)
-	for (curr = thr->heap->refzero_list;
-	     curr != NULL;
-	     curr = DUK_HEAPHDR_GET_NEXT(thr->heap, curr)) {
-		if (curr == h) {
-			if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h)) {
-				/* Object is currently being finalized. */
+				/* Not being finalized but on finalize_list,
+				 * allowed since Duktape 2.1.
+				 */
 				DUK_ASSERT(found == 0);  /* Would indicate corrupted lists. */
 				found = 1;
-			} else {
-				DUK_ASSERT(0);
-			}
-		}
-	}
-#endif
-
-	/* If not present in finalize_list or refzero_list, the pointer
+			}
+		}
+	}
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	/* Because refzero_list is now processed to completion inline with
+	 * no side effects, it's always empty here.
+	 */
+	DUK_ASSERT(thr->heap->refzero_list == NULL);
+#endif
+
+	/* If not present in finalize_list (or refzero_list), it
 	 * must be either in heap_allocated or the string table.
 	 */
-	if (DUK_HEAPHDR_GET_TYPE(h) == DUK_HTYPE_STRING) {
-		/* Omitted from Duktape 2.0.x maintenance backport. */
+	if (DUK_HEAPHDR_IS_STRING(h)) {
+		duk_uint32_t i;
+		duk_hstring *str;
+		duk_heap *heap = thr->heap;
+
+		DUK_ASSERT(found == 0);
+		for (i = 0; i < heap->st_size; i++) {
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+			str = DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, heap->strtable16[i]);
+#else
+			str = heap->strtable[i];
+#endif
+			while (str != NULL) {
+				if (str == (duk_hstring *) h) {
+					DUK_ASSERT(found == 0);  /* Would indicate corrupted lists. */
+					found = 1;
+					break;
+				}
+				str = str->hdr.h_next;
+			}
+		}
+		DUK_ASSERT(found != 0);
 	} else {
 		for (curr = thr->heap->heap_allocated;
 		     curr != NULL;
@@ -20927,6 +21734,7 @@
 DUK_EXTERNAL duk_idx_t duk_push_heapptr(duk_context *ctx, void *ptr) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_idx_t ret;
+	duk_tval *tv;
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
@@ -20941,29 +21749,77 @@
 	duk__validate_push_heapptr(ctx, ptr);
 #endif
 
+	DUK__CHECK_SPACE();
+
 	ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	tv = thr->valstack_top++;
 
 	if (ptr == NULL) {
-		goto push_undefined;
+		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv));
+		return ret;
+	}
+
+	DUK_ASSERT_HEAPHDR_VALID((duk_heaphdr *) ptr);
+
+	/* If the argument is on finalize_list it has technically been
+	 * unreachable before duk_push_heapptr() but it's still safe to
+	 * push it.  Starting from Duktape 2.1 allow application code to
+	 * do so.  There are two main cases:
+	 *
+	 *   (1) The object is on the finalize_list and we're called by
+	 *       the finalizer for the object being finalized.  In this
+	 *       case do nothing: finalize_list handling will deal with
+	 *       the object queueing.  This is detected by the object not
+	 *       having a FINALIZABLE flag despite being on the finalize_list;
+	 *       the flag is cleared for the object being finalized only.
+	 *
+	 *   (2) The object is on the finalize_list but is not currently
+	 *       being processed.  In this case the object can be queued
+	 *       back to heap_allocated with a few flags cleared, in effect
+	 *       cancelling the finalizer.
+	 */
+	if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) ptr))) {
+		duk_heaphdr *curr;
+
+		DUK_D(DUK_DPRINT("duk_push_heapptr() with a pointer on finalize_list, autorescue"));
+
+		curr = (duk_heaphdr *) ptr;
+		DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
+
+		/* Because FINALIZED is set prior to finalizer call, it will
+		 * be set for the object being currently finalized, but not
+		 * for other objects on finalize_list.
+		 */
+		DUK_HEAPHDR_CLEAR_FINALIZED(curr);
+
+		/* Dequeue object from finalize_list and queue it back to
+		 * heap_allocated.
+		 */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) >= 1);  /* Preincremented on finalize_list insert. */
+		DUK_HEAPHDR_PREDEC_REFCOUNT(curr);
+#endif
+		DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(thr->heap, curr);
+		DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(thr->heap, curr);
+
+		/* Continue with the rest. */
 	}
 
 	switch (DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) ptr)) {
 	case DUK_HTYPE_STRING:
-		duk_push_hstring(ctx, (duk_hstring *) ptr);
+		DUK_TVAL_SET_STRING(tv, (duk_hstring *) ptr);
 		break;
 	case DUK_HTYPE_OBJECT:
-		duk_push_hobject(ctx, (duk_hobject *) ptr);
-		break;
-	case DUK_HTYPE_BUFFER:
-		duk_push_hbuffer(ctx, (duk_hbuffer *) ptr);
+		DUK_TVAL_SET_OBJECT(tv, (duk_hobject *) ptr);
 		break;
 	default:
-		goto push_undefined;
-	}
-	return ret;
-
- push_undefined:
-	duk_push_undefined(ctx);
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) ptr) == DUK_HTYPE_BUFFER);
+		DUK_TVAL_SET_BUFFER(tv, (duk_hbuffer *) ptr);
+		break;
+	}
+
+	DUK_HEAPHDR_INCREF(thr, (duk_heaphdr *) ptr);
+
 	return ret;
 }
 
@@ -20971,6 +21827,7 @@
 DUK_EXTERNAL duk_idx_t duk_push_bare_object(duk_context *ctx) {
 	(void) duk_push_object_helper(ctx,
 	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_FLAG_FASTREFS |
 	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                              -1);  /* no prototype */
 	return duk_get_top_index_unsafe(ctx);
@@ -21034,29 +21891,17 @@
 #endif
 
 	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
 
 	if (DUK_UNLIKELY(count < 0)) {
 		DUK_ERROR_RANGE_INVALID_COUNT(thr);
 		return;
 	}
 
-	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
 	if (DUK_UNLIKELY((duk_size_t) (thr->valstack_top - thr->valstack_bottom) < (duk_size_t) count)) {
 		DUK_ERROR_RANGE_INVALID_COUNT(thr);
 	}
 
-	/*
-	 *  Must be very careful here, every DECREF may cause reallocation
-	 *  of our valstack.
-	 */
-
-	/* XXX: inlined DECREF macro would be nice here: no NULL check,
-	 * refzero queueing but no refzero algorithm run (= no pointer
-	 * instability), inline code.
-	 */
-
-	/* XXX: optimize loops */
-
 #if defined(DUK_USE_REFERENCE_COUNTING)
 	tv = thr->valstack_top;
 	tv_end = tv - count;
@@ -21081,6 +21926,70 @@
 	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
 }
 
+DUK_INTERNAL void duk_pop_n_unsafe(duk_context *ctx, duk_idx_t count) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	duk_tval *tv_end;
+#endif
+
+	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(count >= 0);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) count);
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	tv = thr->valstack_top;
+	tv_end = tv - count;
+	while (tv != tv_end) {
+		tv--;
+		DUK_ASSERT(tv >= thr->valstack_bottom);
+		DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ(thr, tv);
+	}
+	thr->valstack_top = tv;
+	DUK_REFZERO_CHECK_FAST(thr);
+#else
+	tv = thr->valstack_top;
+	while (count > 0) {
+		count--;
+		tv--;
+		DUK_ASSERT(tv >= thr->valstack_bottom);
+		DUK_TVAL_SET_UNDEFINED(tv);
+	}
+	thr->valstack_top = tv;
+#endif
+
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+}
+
+/* Pop N elements without DECREF (in effect "stealing" the refcounts). */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+DUK_INTERNAL void duk_pop_n_nodecref_unsafe(duk_context *ctx, duk_idx_t count) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(count >= 0);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) count);
+
+	tv = thr->valstack_top;
+	while (count > 0) {
+		count--;
+		tv--;
+		DUK_ASSERT(tv >= thr->valstack_bottom);
+		DUK_TVAL_SET_UNDEFINED(tv);
+	}
+	thr->valstack_top = tv;
+
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+}
+#else  /* DUK_USE_REFERENCE_COUNTING */
+DUK_INTERNAL void duk_pop_n_nodecref_unsafe(duk_context *ctx, duk_idx_t count) {
+	duk_pop_n_unsafe(ctx, count);
+}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
 /* Popping one element is called so often that when footprint is not an issue,
  * compile a specialized function for it.
  */
@@ -21117,16 +22026,17 @@
 #if defined(DUK_USE_PREFER_SIZE)
 DUK_INTERNAL void duk_pop_unsafe(duk_context *ctx) {
 	DUK_ASSERT_CTX_VALID(ctx);
-	duk_pop_n(ctx, 1);
+	duk_pop_n_unsafe(ctx, 1);
 }
 #else
 DUK_INTERNAL void duk_pop_unsafe(duk_context *ctx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_tval *tv;
-	DUK_ASSERT_CTX_VALID(ctx);
-
+
+	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(thr->valstack_top != thr->valstack_bottom);
 	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
-	DUK_ASSERT(thr->valstack_top != thr->valstack_bottom);
+	DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) 1);
 
 	tv = --thr->valstack_top;  /* tv points to element just below prev top */
 	DUK_ASSERT(tv >= thr->valstack_bottom);
@@ -21135,6 +22045,7 @@
 #else
 	DUK_TVAL_SET_UNDEFINED(tv);
 #endif
+
 	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
 }
 #endif  /* !DUK_USE_PREFER_SIZE */
@@ -21167,7 +22078,7 @@
 	thr = (duk_hthread *) ctx;
 
 	top = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
-	if (count < 0 || count > top) {
+	if (DUK_UNLIKELY(count < 0 || count > top)) {
 		DUK_ERROR_RANGE_INVALID_COUNT(thr);
 		return;
 	}
@@ -21230,12 +22141,13 @@
 
 DUK_EXTERNAL void duk_throw_raw(duk_context *ctx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_val;
 
 	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
 	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
 	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
 
-	if (thr->valstack_top == thr->valstack_bottom) {
+	if (DUK_UNLIKELY(thr->valstack_top == thr->valstack_bottom)) {
 		DUK_ERROR_TYPE_INVALID_ARGS(thr);
 	}
 
@@ -21256,7 +22168,11 @@
 #endif
 	DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (after throw augment)", (duk_tval *) duk_get_tval(ctx, -1)));
 
-	duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
+	tv_val = DUK_GET_TVAL_NEGIDX(ctx, -1);
+	duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, tv_val);
+#if defined(DUK_USE_DEBUGGER_SUPPORT)
+	duk_err_check_debugger_integration(thr);
+#endif
 
 	/* thr->heap->lj.jmpbuf_ptr is checked by duk_err_longjmp() so we don't
 	 * need to check that here.  If the value is NULL, a fatal error occurs
@@ -21400,7 +22316,7 @@
 	return duk_js_strict_equals(tv1, tv2);
 }
 
-DUK_EXTERNAL_DECL duk_bool_t duk_samevalue(duk_context *ctx, duk_idx_t idx1, duk_idx_t idx2) {
+DUK_EXTERNAL duk_bool_t duk_samevalue(duk_context *ctx, duk_idx_t idx1, duk_idx_t idx2) {
 	duk_tval *tv1, *tv2;
 
 	DUK_ASSERT_CTX_VALID(ctx);
@@ -21899,6 +22815,7 @@
 	duk_hstring *res;
 	duk_size_t start_byte_offset;
 	duk_size_t end_byte_offset;
+	duk_size_t charlen;
 
 	DUK_ASSERT_CTX_VALID(ctx);
 
@@ -21906,8 +22823,9 @@
 	h = duk_require_hstring(ctx, idx);
 	DUK_ASSERT(h != NULL);
 
-	if (end_offset >= DUK_HSTRING_GET_CHARLEN(h)) {
-		end_offset = DUK_HSTRING_GET_CHARLEN(h);
+	charlen = DUK_HSTRING_GET_CHARLEN(h);
+	if (end_offset >= charlen) {
+		end_offset = charlen;
 	}
 	if (start_offset > end_offset) {
 		start_offset = end_offset;
@@ -21929,9 +22847,9 @@
 	DUK_ASSERT(end_byte_offset - start_byte_offset <= DUK_UINT32_MAX);  /* Guaranteed by string limits. */
 
 	/* No size check is necessary. */
-	res = duk_heap_string_intern_checked(thr,
-	                                     DUK_HSTRING_GET_DATA(h) + start_byte_offset,
-	                                     (duk_uint32_t) (end_byte_offset - start_byte_offset));
+	res = duk_heap_strtable_intern_checked(thr,
+	                                       DUK_HSTRING_GET_DATA(h) + start_byte_offset,
+	                                       (duk_uint32_t) (end_byte_offset - start_byte_offset));
 
 	duk_push_hstring(ctx, res);
 	duk_replace(ctx, idx);
@@ -24053,26 +24971,6 @@
 	DUK_ASSERT_HBUFOBJ_VALID(h_bufobj);
 }
 
-DUK_LOCAL duk_hbufobj *duk__push_arraybuffer_with_length(duk_context *ctx, duk_uint_t len) {
-	duk_hbuffer *h_val;
-	duk_hbufobj *h_bufobj;
-
-	(void) duk_push_fixed_buffer_zero(ctx, (duk_size_t) len);
-	h_val = (duk_hbuffer *) duk_known_hbuffer(ctx, -1);
-
-	h_bufobj = duk_push_bufobj_raw(ctx,
-	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                               DUK_HOBJECT_FLAG_BUFOBJ |
-	                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER),
-	                               DUK_BIDX_ARRAYBUFFER_PROTOTYPE);
-	DUK_ASSERT(h_bufobj != NULL);
-
-	duk__set_bufobj_buffer(ctx, h_bufobj, h_val);
-	DUK_ASSERT_HBUFOBJ_VALID(h_bufobj);
-
-	return h_bufobj;
-}
-
 /* Shared offset/length coercion helper. */
 DUK_LOCAL void duk__resolve_offset_opt_length(duk_context *ctx,
                                               duk_hbufobj *h_bufarg,
@@ -24526,7 +25424,6 @@
 	duk_tval *tv;
 	duk_hobject *h_obj;
 	duk_hbufobj *h_bufobj = NULL;
-	duk_hbufobj *h_bufarr = NULL;
 	duk_hbufobj *h_bufarg = NULL;
 	duk_hbuffer *h_val;
 	duk_small_uint_t magic;
@@ -24739,15 +25636,17 @@
 
 	/* ArrayBuffer argument is handled specially above; the rest of the
 	 * argument variants are handled by shared code below.
-	 */
-
-	/* Push a new ArrayBuffer (becomes view .buffer) */
-	h_bufarr = duk__push_arraybuffer_with_length(ctx, byte_length);
-	DUK_ASSERT(h_bufarr != NULL);
-	h_val = h_bufarr->buf;
+	 *
+	 * ArrayBuffer in h_bufobj->buf_prop is intentionally left unset.
+	 * It will be automatically created by the .buffer accessor on
+	 * first access.
+	 */
+
+	/* Push the resulting view object on top of a plain fixed buffer. */
+	(void) duk_push_fixed_buffer(ctx, byte_length);
+	h_val = duk_known_hbuffer(ctx, -1);
 	DUK_ASSERT(h_val != NULL);
 
-	/* Push the resulting view object and attach the ArrayBuffer. */
 	h_bufobj = duk_push_bufobj_raw(ctx,
 	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
 	                               DUK_HOBJECT_FLAG_BUFOBJ |
@@ -24763,12 +25662,6 @@
 	h_bufobj->is_typedarray = 1;
 	DUK_ASSERT_HBUFOBJ_VALID(h_bufobj);
 
-	/* Set .buffer */
-	DUK_ASSERT(h_bufobj->buf_prop == NULL);
-	h_bufobj->buf_prop = (duk_hobject *) h_bufarr;
-	DUK_ASSERT(h_bufarr != NULL);
-	DUK_HBUFOBJ_INCREF(thr, h_bufarr);
-
 	/* Copy values, the copy method depends on the arguments.
 	 *
 	 * Copy mode decision may depend on the validity of the underlying
@@ -26683,30 +27576,63 @@
  */
 
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
+DUK_LOCAL duk_hbufobj *duk__autospawn_arraybuffer(duk_context *ctx, duk_hbuffer *h_buf) {
+	duk_hbufobj *h_res;
+
+	h_res = duk_push_bufobj_raw(ctx,
+	                            DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                            DUK_HOBJECT_FLAG_BUFOBJ |
+	                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER),
+	                            DUK_BIDX_ARRAYBUFFER_PROTOTYPE);
+	DUK_ASSERT(h_res != NULL);
+	DUK_UNREF(h_res);
+
+	duk__set_bufobj_buffer(ctx, h_res, h_buf);
+	DUK_ASSERT_HBUFOBJ_VALID(h_res);
+	DUK_ASSERT(h_res->buf_prop == NULL);
+	return h_res;
+}
+
 DUK_INTERNAL duk_ret_t duk_bi_typedarray_buffer_getter(duk_context *ctx) {
 	duk_hbufobj *h_bufobj;
 
 	h_bufobj = (duk_hbufobj *) duk__getrequire_bufobj_this(ctx, DUK__BUFOBJ_FLAG_THROW /*flags*/);
 	DUK_ASSERT(h_bufobj != NULL);
 	if (DUK_HEAPHDR_IS_BUFFER((duk_heaphdr *) h_bufobj)) {
-		duk_hbufobj *h_res;
-		duk_hbuffer *h_buf;
-
-		h_buf = (duk_hbuffer *) h_bufobj;
-		h_res = duk_push_bufobj_raw(ctx,
-		                            DUK_HOBJECT_FLAG_EXTENSIBLE |
-		                            DUK_HOBJECT_FLAG_BUFOBJ |
-		                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER),
-		                            DUK_BIDX_ARRAYBUFFER_PROTOTYPE);
-		DUK_ASSERT(h_res != NULL);
-		DUK_UNREF(h_res);
-
-		duk__set_bufobj_buffer(ctx, h_res, h_buf);
-		DUK_ASSERT_HBUFOBJ_VALID(h_res);
-
-		DUK_DD(DUK_DDPRINT("autospawned .buffer ArrayBuffer: %!iT", duk_get_tval(ctx, -1)));
+		DUK_DD(DUK_DDPRINT("autospawn ArrayBuffer for plain buffer"));
+		(void) duk__autospawn_arraybuffer(ctx, (duk_hbuffer *) h_bufobj);
 		return 1;
 	} else {
+		if (h_bufobj->buf_prop == NULL &&
+		    DUK_HOBJECT_GET_CLASS_NUMBER((duk_hobject *) h_bufobj) != DUK_HOBJECT_CLASS_ARRAYBUFFER &&
+		    h_bufobj->buf != NULL) {
+			duk_hbufobj *h_arrbuf;
+
+			DUK_DD(DUK_DDPRINT("autospawn ArrayBuffer for typed array or DataView"));
+			h_arrbuf = duk__autospawn_arraybuffer(ctx, h_bufobj->buf);
+
+			if (h_bufobj->buf_prop == NULL) {
+				/* Must recheck buf_prop, in case ArrayBuffer
+				 * alloc had a side effect which already filled
+				 * it!
+				 */
+
+				/* Set ArrayBuffer's .byteOffset and .byteLength based
+				 * on the view so that Arraybuffer[view.byteOffset]
+				 * matches view[0].
+				 */
+				h_arrbuf->offset = 0;
+				DUK_ASSERT(h_bufobj->offset + h_bufobj->length >= h_bufobj->offset);  /* Wrap check on creation. */
+				h_arrbuf->length = h_bufobj->offset + h_bufobj->length;
+				DUK_ASSERT(h_arrbuf->buf_prop == NULL);
+
+				DUK_ASSERT(h_bufobj->buf_prop == NULL);
+				h_bufobj->buf_prop = (duk_hobject *) h_arrbuf;
+				DUK_HBUFOBJ_INCREF(thr, h_arrbuf);  /* Now reachable and accounted for. */
+			}
+
+			/* Left on stack; pushed for the second time below (OK). */
+		}
 		if (h_bufobj->buf_prop) {
 			duk_push_hobject(ctx, h_bufobj->buf_prop);
 			return 1;
@@ -28210,6 +29136,7 @@
 
 	(void) duk_push_object_helper(ctx,
 	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_FLAG_FASTREFS |
 	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DATE),
 	                              DUK_BIDX_DATE_PROTOTYPE);
 
@@ -28723,7 +29650,7 @@
 		 * an mktime() error return is the cast above.  See e.g.:
 		 * http://pubs.opengroup.org/onlinepubs/009695299/functions/mktime.html
 		 */
-		goto error;
+		goto mktime_error;
 	}
 	DUK_DDD(DUK_DDDPRINT("t1=%ld (utc), t2=%ld (local)", (long) t1, (long) t2));
 
@@ -28739,7 +29666,7 @@
 #endif
 	return (duk_int_t) difftime(t2, t1);
 
- error:
+ mktime_error:
 	/* XXX: return something more useful, so that caller can throw? */
 	DUK_D(DUK_DPRINT("mktime() failed, d=%lf", (double) d));
 	return 0;
@@ -28962,6 +29889,38 @@
 	return (duk_int_t) (((LONGLONG) tmp3.QuadPart - (LONGLONG) tmp2.QuadPart) / 10000000LL);  /* seconds */
 }
 #endif  /* DUK_USE_DATE_TZO_WINDOWS */
+
+#if defined(DUK_USE_DATE_TZO_WINDOWS_NO_DST)
+DUK_INTERNAL_DECL duk_int_t duk_bi_date_get_local_tzoffset_windows_no_dst(duk_double_t d) {
+	SYSTEMTIME st1;
+	SYSTEMTIME st2;
+	FILETIME ft1;
+	FILETIME ft2;
+	ULARGE_INTEGER tmp1;
+	ULARGE_INTEGER tmp2;
+
+	/* Do a similar computation to duk_bi_date_get_local_tzoffset_windows
+	 * but without accounting for daylight savings time.  Use this on
+	 * Windows platforms (like Durango) that don't support the
+	 * SystemTimeToTzSpecificLocalTime() call.
+	 */
+
+	/* current time not needed for this computation */
+	DUK_UNREF(d);
+
+	duk__set_systime_jan1970(&st1);
+	duk__convert_systime_to_ularge((const SYSTEMTIME *) &st1, &tmp1);
+
+	ft1.dwLowDateTime = tmp1.LowPart;
+	ft1.dwHighDateTime = tmp1.HighPart;
+	FileTimeToLocalFileTime((const FILETIME *) &ft1, &ft2);
+
+	FileTimeToSystemTime((const FILETIME *) &ft2, &st2);
+	duk__convert_systime_to_ularge((const SYSTEMTIME *) &st2, &tmp2);
+
+	return (duk_int_t) (((LONGLONG) tmp2.QuadPart - (LONGLONG) tmp1.QuadPart) / 10000000LL);  /* seconds */
+}
+#endif  /* DUK_USE_DATE_TZO_WINDOWS_NO_DST */
 #line 1 "duk_bi_duktape.c"
 /*
  *  Duktape built-ins
@@ -28994,15 +29953,14 @@
 DUK_INTERNAL duk_ret_t duk_bi_duktape_object_gc(duk_context *ctx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_small_uint_t flags;
-	duk_bool_t rc;
 
 	flags = (duk_small_uint_t) duk_get_uint(ctx, 0);
-	rc = duk_heap_mark_and_sweep(thr->heap, flags);
+	duk_heap_mark_and_sweep(thr->heap, flags);
 
 	/* XXX: Not sure what the best return value would be in the API.
-	 * Return a boolean for now.  Note that rc == 0 is success (true).
-	 */
-	duk_push_boolean(ctx, !rc);
+	 * Return true for now.
+	 */
+	duk_push_true(ctx);
 	return 1;
 }
 
@@ -29014,15 +29972,16 @@
 		 * undefined; this does not remove the property at the moment.
 		 * The value could be type checked to be either a function
 		 * or something else; if something else, the property could
-		 * be deleted.
+		 * be deleted.  Must use duk_set_finalizer() to keep
+		 * DUK_HOBJECT_FLAG_HAVE_FINALIZER in sync.
 		 */
 		duk_set_top(ctx, 2);
-		(void) duk_put_prop_stridx_short(ctx, 0, DUK_STRIDX_INT_FINALIZER);
+		duk_set_finalizer(ctx, 0);
 		return 0;
 	} else {
 		/* Get. */
 		DUK_ASSERT(duk_get_top(ctx) == 1);
-		duk_get_prop_stridx_short(ctx, 0, DUK_STRIDX_INT_FINALIZER);
+		duk_get_finalizer(ctx, 0);
 		return 1;
 	}
 }
@@ -29274,6 +30233,7 @@
 	}
 }
 
+#if defined(DUK_USE_ENCODING_BUILTINS)
 DUK_LOCAL void duk__utf8_encode_char(void *udata, duk_codepoint_t codepoint) {
 	duk__encode_context *enc_ctx;
 
@@ -29328,6 +30288,7 @@
 	 */
 	enc_ctx->out += duk_unicode_encode_xutf8(codepoint, enc_ctx->out);
 }
+#endif  /* DUK_USE_ENCODING_BUILTINS */
 
 /* Shared helper for buffer-to-string using a TextDecoder() compatible UTF-8
  * decoder.
@@ -29590,8 +30551,8 @@
 	 * initialized explicitly.
 	 */
 	dec_ctx = (duk__decode_context *) duk_push_fixed_buffer(ctx, sizeof(duk__decode_context));
-	dec_ctx->fatal = fatal;
-	dec_ctx->ignore_bom = ignore_bom;
+	dec_ctx->fatal = (duk_uint8_t) fatal;
+	dec_ctx->ignore_bom = (duk_uint8_t) ignore_bom;
 	duk__utf8_decode_init(dec_ctx);  /* Initializes remaining fields. */
 
 	duk_put_prop_string(ctx, -2, "\xff" "Context");
@@ -29683,6 +30644,7 @@
 
 	/* same for both error and each subclass like TypeError */
 	duk_uint_t flags_and_class = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                             DUK_HOBJECT_FLAG_FASTREFS |
 	                             DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR);
 
 	DUK_UNREF(thr);
@@ -30124,7 +31086,7 @@
 	DUK_ASSERT_TOP(ctx, 3);
 
 	/* strictness is not inherited, intentional */
-	comp_flags = DUK_JS_COMPILE_FLAG_FUNCEXPR;
+	comp_flags = DUK_COMPILE_FUNCEXPR;
 
 	duk_push_hstring_stridx(ctx, DUK_STRIDX_COMPILE);  /* XXX: copy from caller? */  /* XXX: ignored now */
 	h_sourcecode = duk_require_hstring(ctx, -2);  /* no symbol check needed; -2 is concat'd code */
@@ -30395,6 +31357,7 @@
 	/* create bound function object */
 	h_bound = duk_push_object_helper(ctx,
 	                                 DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                 DUK_HOBJECT_FLAG_FASTREFS |
 	                                 DUK_HOBJECT_FLAG_BOUNDFUNC |
 	                                 DUK_HOBJECT_FLAG_CONSTRUCTABLE |
 	                                 DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION),
@@ -30916,7 +31879,8 @@
 
 	DUK_ASSERT(duk_get_top(ctx) == 1 || duk_get_top(ctx) == 2);  /* 2 when called by debugger */
 	DUK_ASSERT(thr->callstack_top >= 1);  /* at least this function exists */
-	DUK_ASSERT(((thr->callstack + thr->callstack_top - 1)->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0 || /* indirect eval */
+	DUK_ASSERT(thr->callstack_curr != NULL);
+	DUK_ASSERT((thr->callstack_curr->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0 || /* indirect eval */
 	           (thr->callstack_top >= 2));  /* if direct eval, calling activation must exist */
 
 	/*
@@ -30946,8 +31910,9 @@
 
 	/* [ source ] */
 
-	comp_flags = DUK_JS_COMPILE_FLAG_EVAL;
-	act_eval = thr->callstack + thr->callstack_top - 1;    /* this function */
+	comp_flags = DUK_COMPILE_EVAL;
+	act_eval = thr->callstack_curr;  /* this function */
+	DUK_ASSERT(act_eval != NULL);
 	if (thr->callstack_top >= (duk_size_t) -level) {
 		/* Have a calling activation, check for direct eval (otherwise
 		 * assume indirect eval.
@@ -30958,7 +31923,7 @@
 			/* Only direct eval inherits strictness from calling code
 			 * (E5.1 Section 10.1.1).
 			 */
-			comp_flags |= DUK_JS_COMPILE_FLAG_STRICT;
+			comp_flags |= DUK_COMPILE_STRICT;
 		}
 	} else {
 		DUK_ASSERT((act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0);
@@ -30978,7 +31943,7 @@
 
 	/* E5 Section 10.4.2 */
 	DUK_ASSERT(thr->callstack_top >= 1);
-	act = thr->callstack + thr->callstack_top - 1;  /* this function */
+	act = thr->callstack_curr;  /* this function */
 	if (act->flags & DUK_ACT_FLAG_DIRECT_EVAL) {
 		DUK_ASSERT(thr->callstack_top >= 2);
 		act = thr->callstack + thr->callstack_top + level;  /* caller */
@@ -30996,26 +31961,29 @@
 		this_to_global = 0;
 
 		if (DUK_HOBJECT_HAS_STRICT((duk_hobject *) func)) {
-			duk_hobject *new_env;
+			duk_hdecenv *new_env;
 			duk_hobject *act_lex_env;
 
 			DUK_DDD(DUK_DDDPRINT("direct eval call to a strict function -> "
 			                     "var_env and lex_env to a fresh env, "
 			                     "this_binding to caller's this_binding"));
 
-			act = thr->callstack + thr->callstack_top + level;  /* caller */
 			act_lex_env = act->lex_env;
 			act = NULL;  /* invalidated */
 
-			new_env = duk_push_object_helper_proto(ctx,
-			                                       DUK_HOBJECT_FLAG_EXTENSIBLE |
-			                                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
-			                                       act_lex_env);
+			new_env = duk_hdecenv_alloc(thr,
+			                            DUK_HOBJECT_FLAG_EXTENSIBLE |
+			                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV));
 			DUK_ASSERT(new_env != NULL);
+			duk_push_hobject(ctx, (duk_hobject *) new_env);
+
+			DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL);
+			DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, act_lex_env);
+			DUK_HOBJECT_INCREF_ALLOWNULL(thr, act_lex_env);
 			DUK_DDD(DUK_DDDPRINT("new_env allocated: %!iO", (duk_heaphdr *) new_env));
 
-			outer_lex_env = new_env;
-			outer_var_env = new_env;
+			outer_lex_env = (duk_hobject *) new_env;
+			outer_var_env = (duk_hobject *) new_env;
 
 			duk_insert(ctx, 0);  /* stash to bottom of value stack to keep new_env reachable for duration of eval */
 
@@ -31045,7 +32013,7 @@
 	/* Eval code doesn't need an automatic .prototype object. */
 	duk_js_push_closure(thr, func, outer_var_env, outer_lex_env, 0 /*add_auto_proto*/);
 
-	/* [ source template closure ] */
+	/* [ env? source template closure ] */
 
 	if (this_to_global) {
 		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL);
@@ -31064,11 +32032,11 @@
 	                     (duk_heaphdr *) outer_var_env,
 	                     duk_get_tval(ctx, -1)));
 
-	/* [ source template closure this ] */
+	/* [ env? source template closure this ] */
 
 	duk_call_method(ctx, 0);
 
-	/* [ source template result ] */
+	/* [ env? source template result ] */
 
 	return 1;
 }
@@ -31283,12 +32251,15 @@
 DUK_LOCAL_DECL void duk__enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv);
 #endif
 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
-DUK_LOCAL_DECL void duk__enc_buffer(duk_json_enc_ctx *js_ctx, duk_hbuffer *h);
+DUK_LOCAL_DECL void duk__enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h);
 DUK_LOCAL_DECL void duk__enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr);
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
 DUK_LOCAL_DECL void duk__enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj);
 #endif
 #endif
+#if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
+DUK_LOCAL_DECL void duk__enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h);
+#endif
 DUK_LOCAL_DECL void duk__enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_int_t depth);
 
 /*
@@ -32768,13 +33739,60 @@
 	DUK_BW_SET_PTR(thr, &js_ctx->bw, q);
 }
 
-DUK_LOCAL void duk__enc_buffer(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) {
+DUK_LOCAL void duk__enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) {
 	duk__enc_buffer_data(js_ctx,
 	                     (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h),
 	                     (duk_size_t) DUK_HBUFFER_GET_SIZE(h));
 }
 #endif  /* DUK_USE_JX || DUK_USE_JC */
 
+#if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
+DUK_LOCAL void duk__enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) {
+	duk_size_t i, n;
+	const duk_uint8_t *buf;
+	duk_uint8_t *q;
+
+	n = DUK_HBUFFER_GET_SIZE(h);
+	if (n == 0) {
+		DUK__EMIT_2(js_ctx, DUK_ASC_LCURLY, DUK_ASC_RCURLY);
+		return;
+	}
+
+	DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY);
+
+	/* Maximum encoded length with 32-bit index: 1 + 10 + 2 + 3 + 1 + 1 = 18,
+	 * with 64-bit index: 1 + 20 + 2 + 3 + 1 + 1 = 28.  32 has some spare.
+	 *
+	 * Note that because the output buffer is reallocated from time to time,
+	 * side effects (such as finalizers) affecting the buffer 'h' must be
+	 * disabled.  This is the case in the JSON.stringify() fast path.
+	 */
+
+	buf = (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h);
+	if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
+		for (i = 0; i < n; i++) {
+			duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth + 1);
+			q = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, 32);
+			q += DUK_SPRINTF((char *) q, "\"%lu\": %u,", (unsigned long) i, (unsigned int) buf[i]);
+			DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q);
+		}
+	} else {
+		q = DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw);
+		for (i = 0; i < n; i++) {
+			q = DUK_BW_ENSURE_RAW(js_ctx->thr, &js_ctx->bw, 32, q);
+			q += DUK_SPRINTF((char *) q, "\"%lu\":%u,", (unsigned long) i, (unsigned int) buf[i]);
+		}
+		DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q);
+	}
+	DUK__UNEMIT_1(js_ctx);  /* eat trailing comma */
+
+	if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
+		duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth);
+	}
+	DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY);
+}
+#endif  /* DUK_USE_JSON_STRINGIFY_FASTPATH */
+
 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
 DUK_LOCAL void duk__enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr) {
 	char buf[64];  /* XXX: how to figure correct size? */
@@ -33324,7 +34342,7 @@
 	case DUK_TAG_STRING: {
 		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			goto pop2_undef;
 		}
 		duk__enc_quote_string(js_ctx, h);
@@ -33355,12 +34373,13 @@
 	case DUK_TAG_BUFFER: {
 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
 		if (js_ctx->flag_ext_custom_or_compatible) {
-			duk__enc_buffer(js_ctx, DUK_TVAL_GET_BUFFER(tv));
-			break;
-		}
-#endif
-		/* Could implement a fast path, but object coerce and
-		 * serialize the result for now.
+			duk__enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv));
+			break;
+		}
+#endif
+
+		/* Could implement a fastpath, but the fast path would need
+		 * to handle realloc side effects correctly.
 		 */
 		duk_to_object(ctx, -1);
 		duk__enc_object(js_ctx);
@@ -33420,7 +34439,7 @@
 		duk_hstring *h;
 		h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			return 0;
 		}
 		return 1;
@@ -33489,7 +34508,7 @@
 		duk_hstring *h;
 		h = DUK_TVAL_GET_STRING(tv);
 		DUK_ASSERT(h != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			goto emit_undefined;
 		}
 		duk__enc_quote_string(js_ctx, h);
@@ -33654,7 +34673,7 @@
 					DUK_DD(DUK_DDPRINT("property is an accessor, abort fast path"));
 					goto abort_fastpath;
 				}
-				if (DUK_HSTRING_HAS_SYMBOL(k)) {
+				if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) {
 					continue;
 				}
 
@@ -33848,13 +34867,16 @@
 
 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
 		if (js_ctx->flag_ext_custom_or_compatible) {
-			duk__enc_buffer(js_ctx, DUK_TVAL_GET_BUFFER(tv));
-			break;
-		}
-#endif
-		/* Could implement a fast path, but abort fast path for now. */
-		DUK_DD(DUK_DDPRINT("value is a plain buffer and serializing as plain JSON, abort fast path"));
-		goto abort_fastpath;
+			duk__enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv));
+			break;
+		}
+#endif
+
+		/* Plain buffers mimic Uint8Arrays, and have enumerable index
+		 * properties.
+		 */
+		duk__enc_buffer_json_fastpath(js_ctx, DUK_TVAL_GET_BUFFER(tv));
+		break;
 	}
 	case DUK_TAG_POINTER: {
 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
@@ -34251,8 +35273,11 @@
 	}
 
 	if (js_ctx->h_gap != NULL) {
-		/* if gap is empty, behave as if not given at all */
-		if (DUK_HSTRING_GET_CHARLEN(js_ctx->h_gap) == 0) {
+		/* If gap is empty, behave as if not given at all.  Check
+		 * against byte length because character length is more
+		 * expensive.
+		 */
+		if (DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) == 0) {
 			js_ctx->h_gap = NULL;
 		}
 	}
@@ -34268,7 +35293,7 @@
 	if (js_ctx->h_replacer == NULL &&  /* replacer is a mutation risk */
 	    js_ctx->idx_proplist == -1) {  /* proplist is very rare */
 		duk_int_t pcall_rc;
-		duk_small_uint_t prev_mark_and_sweep_base_flags;
+		duk_small_uint_t prev_ms_base_flags;
 
 		DUK_DD(DUK_DDPRINT("try JSON.stringify() fast path"));
 
@@ -34290,14 +35315,17 @@
 		duk_dup(ctx, idx_value);
 
 		/* Must prevent finalizers which may have arbitrary side effects. */
-		prev_mark_and_sweep_base_flags = thr->heap->mark_and_sweep_base_flags;
-		thr->heap->mark_and_sweep_base_flags |=
-			DUK_MS_FLAG_NO_FINALIZERS |         /* avoid attempts to add/remove object keys */
-		        DUK_MS_FLAG_NO_OBJECT_COMPACTION;   /* avoid attempt to compact any objects */
+		prev_ms_base_flags = thr->heap->ms_base_flags;
+		thr->heap->ms_base_flags |=
+		        DUK_MS_FLAG_NO_OBJECT_COMPACTION;      /* Avoid attempt to compact any objects. */
+		thr->heap->pf_prevent_count++;                 /* Prevent finalizers. */
+		DUK_ASSERT(thr->heap->pf_prevent_count != 0);  /* Wrap. */
 
 		pcall_rc = duk_safe_call(ctx, duk__json_stringify_fast, (void *) js_ctx /*udata*/, 1 /*nargs*/, 0 /*nret*/);
 
-		thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+		DUK_ASSERT(thr->heap->pf_prevent_count > 0);
+		thr->heap->pf_prevent_count--;
+		thr->heap->ms_base_flags = prev_ms_base_flags;
 
 		if (pcall_rc == DUK_EXEC_SUCCESS) {
 			DUK_DD(DUK_DDPRINT("fast path successful"));
@@ -35144,6 +36172,7 @@
 
 	(void) duk_push_object_helper(ctx,
 	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_FLAG_FASTREFS |
 	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                              DUK_BIDX_OBJECT_PROTOTYPE);
 	return 1;
@@ -35206,6 +36235,7 @@
 
 	(void) duk_push_object_helper_proto(ctx,
 	                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                    DUK_HOBJECT_FLAG_FASTREFS |
 	                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                                    proto);
 
@@ -35925,6 +36955,7 @@
 	if (duk_is_constructor_call(ctx)) {
 		(void) duk_push_object_helper(ctx,
 		                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                              DUK_HOBJECT_FLAG_FASTREFS |
 		                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER),
 		                              DUK_BIDX_POINTER_PROTOTYPE);
 
@@ -36022,7 +37053,7 @@
 				goto skip_key;
 			}
 		}
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			if (!(flags & DUK_ENUM_INCLUDE_SYMBOLS)) {
 				DUK_DDD(DUK_DDDPRINT("ignore symbol property: %!T", duk_get_tval(ctx, -1)));
 				goto skip_key;
@@ -36099,6 +37130,7 @@
 	 */
 	(void) duk_push_object_helper_proto(ctx,
 	                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                    DUK_HOBJECT_FLAG_FASTREFS |
 	                                    DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ |
 	                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                                    NULL);
@@ -36481,6 +37513,96 @@
 #if defined(DUK_USE_STRING_BUILTIN)
 
 /*
+ *  Helpers
+ */
+
+DUK_LOCAL duk_hstring *duk__str_tostring_notregexp(duk_context *ctx, duk_idx_t idx) {
+	duk_hstring *h;
+
+	if (duk_get_class_number(ctx, idx) == DUK_HOBJECT_CLASS_REGEXP) {
+		DUK_ERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
+	}
+	h = duk_to_hstring(ctx, idx);
+	DUK_ASSERT(h != NULL);
+
+	return h;
+}
+
+DUK_LOCAL duk_int_t duk__str_search_shared(duk_context *ctx, duk_hstring *h_this, duk_hstring *h_search, duk_int_t start_cpos, duk_bool_t backwards) {
+	duk_int_t cpos;
+	duk_int_t bpos;
+	const duk_uint8_t *p_start, *p_end, *p;
+	const duk_uint8_t *q_start;
+	duk_int_t q_blen;
+	duk_uint8_t firstbyte;
+	duk_uint8_t t;
+
+	cpos = start_cpos;
+
+	/* Empty searchstring always matches; cpos must be clamped here.
+	 * (If q_blen were < 0 due to clamped coercion, it would also be
+	 * caught here.)
+	 */
+	q_start = DUK_HSTRING_GET_DATA(h_search);
+	q_blen = (duk_int_t) DUK_HSTRING_GET_BYTELEN(h_search);
+	if (q_blen <= 0) {
+		return cpos;
+	}
+	DUK_ASSERT(q_blen > 0);
+
+	bpos = (duk_int_t) duk_heap_strcache_offset_char2byte((duk_hthread *) ctx, h_this, (duk_uint32_t) cpos);
+
+	p_start = DUK_HSTRING_GET_DATA(h_this);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this);
+	p = p_start + bpos;
+
+	/* This loop is optimized for size.  For speed, there should be
+	 * two separate loops, and we should ensure that memcmp() can be
+	 * used without an extra "will searchstring fit" check.  Doing
+	 * the preconditioning for 'p' and 'p_end' is easy but cpos
+	 * must be updated if 'p' is wound back (backward scanning).
+	 */
+
+	firstbyte = q_start[0];  /* leading byte of match string */
+	while (p <= p_end && p >= p_start) {
+		t = *p;
+
+		/* For Ecmascript strings, this check can only match for
+		 * initial UTF-8 bytes (not continuation bytes).  For other
+		 * strings all bets are off.
+		 */
+
+		if ((t == firstbyte) && ((duk_size_t) (p_end - p) >= (duk_size_t) q_blen)) {
+			DUK_ASSERT(q_blen > 0);  /* no issues with memcmp() zero size, even if broken */
+			if (DUK_MEMCMP((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) {
+				return cpos;
+			}
+		}
+
+		/* track cpos while scanning */
+		if (backwards) {
+			/* when going backwards, we decrement cpos 'early';
+			 * 'p' may point to a continuation byte of the char
+			 * at offset 'cpos', but that's OK because we'll
+			 * backtrack all the way to the initial byte.
+			 */
+			if ((t & 0xc0) != 0x80) {
+				cpos--;
+			}
+			p--;
+		} else {
+			if ((t & 0xc0) != 0x80) {
+				cpos++;
+			}
+			p++;
+		}
+	}
+
+	/* Not found.  Empty string case is handled specially above. */
+	return -1;
+}
+
+/*
  *  Constructor
  */
 
@@ -36502,7 +37624,7 @@
 		duk_push_hstring_empty(ctx);
 	} else {
 		h = duk_to_hstring_acceptsymbol(ctx, 0);
-		if (DUK_HSTRING_HAS_SYMBOL(h) && !duk_is_constructor_call(ctx)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h) && !duk_is_constructor_call(ctx))) {
 			duk_push_symbol_descriptive_string(ctx, h);
 			duk_replace(ctx, 0);
 		}
@@ -36514,6 +37636,7 @@
 	if (duk_is_constructor_call(ctx)) {
 		/* String object internal value is immutable */
 		flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		        DUK_HOBJECT_FLAG_FASTREFS |
 		        DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ |
 		        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING);
 		duk_push_object_helper(ctx, flags, DUK_BIDX_STRING_PROTOTYPE);
@@ -36663,7 +37786,7 @@
 	pos = duk_to_int_clamped_raw(ctx,
 	                             0 /*index*/,
 	                             0 /*min(incl)*/,
-	                             DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl)*/,
+	                             (duk_int_t) DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl)*/,
 	                             &clamped /*out_clamped*/);
 #if defined(DUK_USE_ES6)
 	magic = duk_get_current_magic(ctx);
@@ -36825,17 +37948,10 @@
  */
 
 DUK_INTERNAL duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx) {
-	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_hstring *h_this;
 	duk_hstring *h_search;
 	duk_int_t clen_this;
 	duk_int_t cpos;
-	duk_int_t bpos;
-	const duk_uint8_t *p_start, *p_end, *p;
-	const duk_uint8_t *q_start;
-	duk_int_t q_blen;
-	duk_uint8_t firstbyte;
-	duk_uint8_t t;
 	duk_small_int_t is_lastindexof = duk_get_current_magic(ctx);  /* 0=indexOf, 1=lastIndexOf */
 
 	h_this = duk_push_this_coercible_to_string(ctx);
@@ -36844,8 +37960,6 @@
 
 	h_search = duk_to_hstring(ctx, 0);
 	DUK_ASSERT(h_search != NULL);
-	q_start = DUK_HSTRING_GET_DATA(h_search);
-	q_blen = (duk_int_t) DUK_HSTRING_GET_BYTELEN(h_search);
 
 	duk_to_number(ctx, 1);
 	if (duk_is_nan(ctx, 1) && is_lastindexof) {
@@ -36858,67 +37972,8 @@
 		cpos = duk_to_int_clamped(ctx, 1, 0, clen_this);
 	}
 
-	/* Empty searchstring always matches; cpos must be clamped here.
-	 * (If q_blen were < 0 due to clamped coercion, it would also be
-	 * caught here.)
-	 */
-	if (q_blen <= 0) {
-		duk_push_int(ctx, cpos);
-		return 1;
-	}
-	DUK_ASSERT(q_blen > 0);
-
-	bpos = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_this, (duk_uint32_t) cpos);
-
-	p_start = DUK_HSTRING_GET_DATA(h_this);
-	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this);
-	p = p_start + bpos;
-
-	/* This loop is optimized for size.  For speed, there should be
-	 * two separate loops, and we should ensure that memcmp() can be
-	 * used without an extra "will searchstring fit" check.  Doing
-	 * the preconditioning for 'p' and 'p_end' is easy but cpos
-	 * must be updated if 'p' is wound back (backward scanning).
-	 */
-
-	firstbyte = q_start[0];  /* leading byte of match string */
-	while (p <= p_end && p >= p_start) {
-		t = *p;
-
-		/* For Ecmascript strings, this check can only match for
-		 * initial UTF-8 bytes (not continuation bytes).  For other
-		 * strings all bets are off.
-		 */
-
-		if ((t == firstbyte) && ((duk_size_t) (p_end - p) >= (duk_size_t) q_blen)) {
-			DUK_ASSERT(q_blen > 0);  /* no issues with memcmp() zero size, even if broken */
-			if (DUK_MEMCMP((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) {
-				duk_push_int(ctx, cpos);
-				return 1;
-			}
-		}
-
-		/* track cpos while scanning */
-		if (is_lastindexof) {
-			/* when going backwards, we decrement cpos 'early';
-			 * 'p' may point to a continuation byte of the char
-			 * at offset 'cpos', but that's OK because we'll
-			 * backtrack all the way to the initial byte.
-			 */
-			if ((t & 0xc0) != 0x80) {
-				cpos--;
-			}
-			p--;
-		} else {
-			if ((t & 0xc0) != 0x80) {
-				cpos++;
-			}
-			p++;
-		}
-	}
-
-	/* Not found.  Empty string case is handled specially above. */
-	duk_push_int(ctx, -1);
+	cpos = duk__str_search_shared(ctx, h_this, h_search, cpos, is_lastindexof /*backwards*/);
+	duk_push_int(ctx, cpos);
 	return 1;
 }
 
@@ -37215,9 +38270,10 @@
 					/* Use match charlen instead of bytelen, just in case the input and
 					 * match codepoint encodings would have different lengths.
 					 */
+					/* XXX: charlen computed here, and also in char2byte helper. */
 					match_end_boff = duk_heap_strcache_offset_char2byte(thr,
 					                                                    h_input,
-					                                                    match_start_coff + DUK_HSTRING_GET_CHARLEN(h_match));
+					                                                    match_start_coff + (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_match));
 
 					tmp_sz = (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - match_end_boff);
 					DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + match_end_boff, tmp_sz);
@@ -37567,7 +38623,7 @@
 	DUK_DDD(DUK_DDDPRINT("split trailer; prev_end b=%ld,c=%ld",
 	                     (long) prev_match_end_boff, (long) prev_match_end_coff));
 
-	if (DUK_HSTRING_GET_CHARLEN(h_input) > 0 || !matched) {
+	if (DUK_HSTRING_GET_BYTELEN(h_input) > 0 || !matched) {
 		/* Add trailer if:
 		 *   a) non-empty input
 		 *   b) empty input and no (zero size) match found (step 11)
@@ -37909,6 +38965,93 @@
 	return 1;
 }
 
+#if defined(DUK_USE_ES6)
+DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_context *ctx) {
+	duk_int_t magic;
+	duk_hstring *h;
+	duk_hstring *h_search;
+	duk_size_t blen_search;
+	const duk_uint8_t *p_cmp_start;
+	duk_bool_t result;
+
+	h = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h != NULL);
+
+	h_search = duk__str_tostring_notregexp(ctx, 0);
+	DUK_ASSERT(h_search != NULL);
+
+	magic = duk_get_current_magic(ctx);
+
+	p_cmp_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
+	blen_search = DUK_HSTRING_GET_BYTELEN(h_search);
+
+	if (duk_is_undefined(ctx, 1)) {
+		if (magic) {
+			p_cmp_start += DUK_HSTRING_GET_BYTELEN(h) - blen_search;
+		} else {
+			/* p_cmp_start already OK */
+		}
+	} else {
+		duk_int_t len;
+		duk_int_t pos;
+
+		DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX);
+		len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
+		pos = duk_to_int_clamped(ctx, 1, 0, len);
+		DUK_ASSERT(pos >= 0 && pos <= len);
+
+		if (magic) {
+			p_cmp_start -= blen_search;  /* Conceptually subtracted last, but do already here. */
+		}
+		DUK_ASSERT(pos >= 0 && pos <= len);
+
+		p_cmp_start += duk_heap_strcache_offset_char2byte((duk_hthread *) ctx, h, pos);
+	}
+
+	/* The main comparison can be done using a memcmp() rather than
+	 * doing codepoint comparisons: for CESU-8 strings there is a
+	 * canonical representation for every codepoint.  But we do need
+	 * to deal with the char/byte offset translation to find the
+	 * comparison range.
+	 */
+
+	result = 0;
+	if (p_cmp_start >= DUK_HSTRING_GET_DATA(h) &&
+	    p_cmp_start - (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h) + blen_search <= DUK_HSTRING_GET_BYTELEN(h)) {
+		if (DUK_MEMCMP((const void *) p_cmp_start,
+		               (const void *) DUK_HSTRING_GET_DATA(h_search),
+		               (size_t) blen_search) == 0) {
+			result = 1;
+		}
+	}
+
+	duk_push_boolean(ctx, result);
+	return 1;
+}
+#endif  /* DUK_USE_ES6 */
+
+#if defined(DUK_USE_ES6)
+DUK_INTERNAL duk_ret_t duk_bi_string_prototype_includes(duk_context *ctx) {
+	duk_hstring *h;
+	duk_hstring *h_search;
+	duk_int_t len;
+	duk_int_t pos;
+
+	h = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h != NULL);
+
+	h_search = duk__str_tostring_notregexp(ctx, 0);
+	DUK_ASSERT(h_search != NULL);
+
+	len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
+	pos = duk_to_int_clamped(ctx, 1, 0, len);
+	DUK_ASSERT(pos >= 0 && pos <= len);
+
+	pos = duk__str_search_shared(ctx, h, h_search, pos, 0 /*backwards*/);
+	duk_push_boolean(ctx, pos >= 0);
+	return 1;
+}
+#endif  /* DUK_USE_ES6 */
 #endif  /* DUK_USE_STRING_BUILTIN */
 #line 1 "duk_bi_symbol.c"
 /*
@@ -38017,7 +39160,8 @@
 	h_str = DUK_TVAL_GET_STRING(tv);
 	DUK_ASSERT(h_str != NULL);
 
-	if (!DUK_HSTRING_HAS_SYMBOL(h_str)) {
+	/* Here symbol is more expected than not. */
+	if (DUK_UNLIKELY(!DUK_HSTRING_HAS_SYMBOL(h_str))) {
 		return NULL;
 	}
 
@@ -38037,6 +39181,7 @@
 		duk_push_symbol_descriptive_string(ctx, h_str);
 	} else {
 		/* .valueOf() */
+		duk_push_hstring(ctx, h_str);
 	}
 	return 1;
 }
@@ -38164,11 +39309,12 @@
 		DUK_DD(DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"));
 		goto state_error;
 	}
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);  /* us */
-	DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL);  /* caller */
-
-	caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2);
+	DUK_ASSERT(thr->callstack_curr != NULL);
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL);  /* us */
+	DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr - 1) != NULL);  /* caller */
+
+	caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr - 1);
 	if (!DUK_HOBJECT_IS_COMPFUNC(caller_func)) {
 		DUK_DD(DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"));
 		goto state_error;
@@ -38318,11 +39464,12 @@
 		DUK_DD(DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)"));
 		goto state_error;
 	}
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);  /* us */
-	DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL);  /* caller */
-
-	caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2);
+	DUK_ASSERT(thr->callstack_curr != NULL);
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL);  /* us */
+	DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr - 1) != NULL);  /* caller */
+
+	caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr - 1);
 	if (!DUK_HOBJECT_IS_COMPFUNC(caller_func)) {
 		DUK_DD(DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"));
 		goto state_error;
@@ -38900,6 +40047,9 @@
 		}
 	}
 	if (st->internal) {
+		if (DUK_HOBJECT_IS_ARRAY(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__array:true");
+		}
 		if (DUK_HOBJECT_HAS_EXTENSIBLE(h)) {
 			DUK__COMMA(); duk_fb_sprintf(fb, "__extensible:true");
 		}
@@ -38918,7 +40068,7 @@
 		if (DUK_HOBJECT_HAS_BUFOBJ(h)) {
 			DUK__COMMA(); duk_fb_sprintf(fb, "__bufobj:true");
 		}
-		if (DUK_HOBJECT_HAS_THREAD(h)) {
+		if (DUK_HOBJECT_IS_THREAD(h)) {
 			DUK__COMMA(); duk_fb_sprintf(fb, "__thread:true");
 		}
 		if (DUK_HOBJECT_HAS_ARRAY_PART(h)) {
@@ -38939,9 +40089,6 @@
 		if (DUK_HOBJECT_HAS_CREATEARGS(h)) {
 			DUK__COMMA(); duk_fb_sprintf(fb, "__createargs:true");
 		}
-		if (DUK_HOBJECT_HAS_ENVRECCLOSED(h)) {
-			DUK__COMMA(); duk_fb_sprintf(fb, "__envrecclosed:true");
-		}
 		if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)) {
 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_array:true");
 		}
@@ -38986,6 +40133,15 @@
 		duk_fb_put_funcptr(fb, (duk_uint8_t *) &f->func, sizeof(f->func));
 		DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
 		DUK__COMMA(); duk_fb_sprintf(fb, "__magic:%ld", (long) f->magic);
+	} else if (st->internal && DUK_HOBJECT_IS_DECENV(h)) {
+		duk_hdecenv *e = (duk_hdecenv *) h;
+		DUK__COMMA(); duk_fb_sprintf(fb, "__thread:"); duk__print_hobject(st, (duk_hobject *) e->thread);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__varmap:"); duk__print_hobject(st, (duk_hobject *) e->varmap);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__regbase:%ld", (long) e->regbase);
+	} else if (st->internal && DUK_HOBJECT_IS_OBJENV(h)) {
+		duk_hobjenv *e = (duk_hobjenv *) h;
+		DUK__COMMA(); duk_fb_sprintf(fb, "__target:"); duk__print_hobject(st, (duk_hobject *) e->target);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__has_this:%ld", (long) e->has_this);
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
 	} else if (st->internal && DUK_HOBJECT_IS_BUFOBJ(h)) {
 		duk_hbufobj *b = (duk_hbufobj *) h;
@@ -39219,6 +40375,10 @@
 	}
 #if defined(DUK_USE_FASTINT)
 	case DUK_TAG_FASTINT:
+		DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		duk_fb_sprintf(fb, "%.18gF", (double) DUK_TVAL_GET_NUMBER(tv));
+		break;
 #endif
 	default: {
 		/* IEEE double is approximately 16 decimal digits; print a couple extra */
@@ -39312,7 +40472,7 @@
 
 			if (ch == DUK_ASC_STAR) {
 				/* unsupported: would consume multiple args */
-				goto error;
+				goto format_error;
 			} else if (ch == DUK_ASC_PERCENT) {
 				duk_fb_put_byte(&fb, (duk_uint8_t) DUK_ASC_PERCENT);
 				break;
@@ -39364,7 +40524,7 @@
 				fmtlen = (duk_size_t) (p - p_begfmt);
 				if (fmtlen >= sizeof(fmtbuf)) {
 					/* format is too large, abort */
-					goto error;
+					goto format_error;
 				}
 				DUK_MEMZERO(fmtbuf, sizeof(fmtbuf));
 				DUK_MEMCPY(fmtbuf, p_begfmt, fmtlen);
@@ -39439,7 +40599,7 @@
 	}
 	goto done;
 
- error:
+ format_error:
 	duk_fb_put_cstring(&fb, "FMTERR");
 	/* fall through */
 
@@ -39568,7 +40728,6 @@
 	/* heap->dbg_detached_cb: keep */
 	/* heap->dbg_udata: keep */
 	/* heap->dbg_processing: keep on purpose to avoid debugger re-entry in detaching state */
-	DUK_HEAP_CLEAR_DEBUGGER_PAUSED(heap);
 	heap->dbg_state_dirty = 0;
 	heap->dbg_force_restart = 0;
 	heap->dbg_step_type = 0;
@@ -39576,6 +40735,8 @@
 	heap->dbg_step_csindex = 0;
 	heap->dbg_step_startline = 0;
 	heap->dbg_have_next_byte = 0;
+	duk_debug_clear_paused(heap);  /* XXX: some overlap with field inits above */
+	heap->dbg_state_dirty = 0;     /* XXX: clear_paused sets dirty; rework? */
 
 	/* Ensure there are no stale active breakpoint pointers.
 	 * Breakpoint list is currently kept - we could empty it
@@ -39594,7 +40755,10 @@
 	duk_context *ctx;
 
 	thr = heap->heap_thread;
-	DUK_ASSERT(thr != NULL);
+	if (thr == NULL) {
+		DUK_ASSERT(heap->dbg_detached_cb == NULL);
+		return;
+	}
 	ctx = (duk_context *) thr;
 
 	/* Safe to call multiple times. */
@@ -39628,6 +40792,9 @@
  */
 DUK_LOCAL void duk__debug_null_most_callbacks(duk_hthread *thr) {
 	duk_heap *heap;
+
+	DUK_ASSERT(thr != NULL);
+
 	heap = thr->heap;
 	DUK_D(DUK_DPRINT("transport read/write error, NULL all callbacks expected detached"));
 	heap->dbg_read_cb = NULL;
@@ -40475,7 +41642,7 @@
 	duk_uint_fast32_t line;
 	duk_uint_fast32_t pc;
 
-	act = duk_hthread_get_current_activation(thr);  /* may be NULL */
+	act = thr->callstack_curr;
 	if (act == NULL) {
 		return 0;
 	}
@@ -40506,13 +41673,13 @@
 	duk_debug_write_int(thr, (DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap) ? 1 : 0));
 
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);  /* unsigned */
-	if (thr->callstack_top == 0) {
+	act = thr->callstack_curr;
+	if (act == NULL) {
 		duk_debug_write_undefined(thr);
 		duk_debug_write_undefined(thr);
 		duk_debug_write_int(thr, 0);
 		duk_debug_write_int(thr, 0);
 	} else {
-		act = thr->callstack + thr->callstack_top - 1;
 		duk_push_tval(ctx, &act->tv_func);
 		duk_get_prop_string(ctx, -1, "fileName");
 		duk__debug_write_hstring_safe_top(thr);
@@ -40521,6 +41688,7 @@
 		duk_pop_3(ctx);
 		/* Report next pc/line to be executed. */
 		duk_debug_write_uint(thr, (duk_uint32_t) duk_debug_curr_line(thr));
+		act = thr->callstack_curr;
 		duk_debug_write_uint(thr, (duk_uint32_t) duk_hthread_get_act_curr_pc(thr, act));
 	}
 
@@ -40553,18 +41721,29 @@
 		duk__debug_write_hstring_safe_top(thr);
 		duk_get_prop_stridx_short(ctx, -2, DUK_STRIDX_LINE_NUMBER);
 		duk_debug_write_uint(thr, duk_get_uint(ctx, -1));
-	} else {
-		/* For anything other than an Error instance, we calculate the error
-		 * location directly from the current activation.
-		 */
-		act = thr->callstack + thr->callstack_top - 1;
-		duk_push_tval(ctx, &act->tv_func);
-		duk_get_prop_string(ctx, -1, "fileName");
-		duk__debug_write_hstring_safe_top(thr);
-		pc = duk_hthread_get_act_prev_pc(thr, act);
-		duk_debug_write_uint(thr, (duk_uint32_t) duk_hobject_pc2line_query(ctx, -2, pc));
-	}
-	duk_pop_2(ctx);  /* shared pop */
+		duk_pop_2(ctx);
+	} else {
+		/* For anything other than an Error instance, we calculate the
+		 * error location directly from the current activation if one
+		 * exists.
+		 */
+		act = thr->callstack_curr;
+		if (act != NULL) {
+			duk_push_tval(ctx, &act->tv_func);
+			duk_get_prop_string(ctx, -1, "fileName");
+			duk__debug_write_hstring_safe_top(thr);
+			act = thr->callstack_curr;
+			pc = duk_hthread_get_act_prev_pc(thr, act);
+			duk_debug_write_uint(thr, (duk_uint32_t) duk_hobject_pc2line_query(ctx, -2, pc));
+			duk_pop_2(ctx);
+		} else {
+			/* Can happen if duk_throw() is called on an empty
+			 * callstack.
+			 */
+			duk_debug_write_cstring(thr, "");
+			duk_debug_write_uint(thr, 0);
+		}
+	}
 
 	duk_debug_write_eom(thr);
 }
@@ -40704,7 +41883,11 @@
 DUK_LOCAL void duk__debug_handle_pause(duk_hthread *thr, duk_heap *heap) {
 	DUK_D(DUK_DPRINT("debug command Pause"));
 
-	DUK_HEAP_SET_PAUSED(heap);
+	if (duk_debug_is_paused(heap)) {
+		DUK_D(DUK_DPRINT("Pause requested when already paused, ignore"));
+	} else {
+		duk_debug_set_paused(heap);
+	}
 	duk_debug_write_reply(thr);
 	duk_debug_write_eom(thr);
 }
@@ -40712,7 +41895,7 @@
 DUK_LOCAL void duk__debug_handle_resume(duk_hthread *thr, duk_heap *heap) {
 	DUK_D(DUK_DPRINT("debug command Resume"));
 
-	DUK_HEAP_CLEAR_PAUSED(heap);
+	duk_debug_clear_paused(heap);
 	duk_debug_write_reply(thr);
 	duk_debug_write_eom(thr);
 }
@@ -40734,7 +41917,7 @@
 
 	line = duk_debug_curr_line(thr);
 	if (line > 0) {
-		DUK_HEAP_CLEAR_DEBUGGER_PAUSED(heap);
+		duk_debug_clear_paused(heap);  /* XXX: overlap with fields below; separate macro/helper? */
 		heap->dbg_step_type = step_type;
 		heap->dbg_step_thread = thr;
 		heap->dbg_step_csindex = thr->callstack_top - 1;
@@ -40996,6 +42179,7 @@
 	/* Read callstack index, if non-null. */
 	if (duk_debug_peek_byte(thr) == DUK_DBG_IB_NULL) {
 		direct_eval = 0;
+		level = -1;  /* Not needed, but silences warning. */
 		(void) duk_debug_read_byte(thr);
 	} else {
 		direct_eval = 1;
@@ -41238,82 +42422,29 @@
 	}
 }
 
-#if defined(DUK_USE_STRTAB_CHAIN)
-DUK_LOCAL void duk__debug_dump_strtab_chain(duk_hthread *thr, duk_heap *heap) {
-	duk_uint_fast32_t i, j;
-	duk_strtab_entry *e;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t *lst;
-#else
-	duk_hstring **lst;
-#endif
-	duk_hstring *h;
-
-	for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
-		e = heap->strtable + i;
-		if (e->listlen > 0) {
-#if defined(DUK_USE_HEAPPTR16)
-			lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-#else
-			lst = e->u.strlist;
-#endif
-			DUK_ASSERT(lst != NULL);
-
-			for (j = 0; j < e->listlen; j++) {
-#if defined(DUK_USE_HEAPPTR16)
-				h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[j]);
-#else
-				h = lst[j];
-#endif
-				if (h != NULL) {
-					duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h);
-				}
-			}
-		} else {
-#if defined(DUK_USE_HEAPPTR16)
-			h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
-#else
-			h = e->u.str;
-#endif
-			if (h != NULL) {
-				duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h);
-			}
-		}
-	}
-}
-#endif  /* DUK_USE_STRTAB_CHAIN */
-
-#if defined(DUK_USE_STRTAB_PROBE)
-DUK_LOCAL void duk__debug_dump_strtab_probe(duk_hthread *thr, duk_heap *heap) {
+DUK_LOCAL void duk__debug_dump_strtab(duk_hthread *thr, duk_heap *heap) {
 	duk_uint32_t i;
 	duk_hstring *h;
 
 	for (i = 0; i < heap->st_size; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-		h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+		h = DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, heap->strtable16[i]);
 #else
 		h = heap->strtable[i];
 #endif
-		if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
-			continue;
-		}
-
-		duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h);
-	}
-}
-#endif  /* DUK_USE_STRTAB_PROBE */
+		while (h != NULL) {
+			duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h);
+			h = h->hdr.h_next;
+		}
+	}
+}
 
 DUK_LOCAL void duk__debug_handle_dump_heap(duk_hthread *thr, duk_heap *heap) {
 	DUK_D(DUK_DPRINT("debug command DumpHeap"));
 
 	duk_debug_write_reply(thr);
 	duk__debug_dump_heap_allocated(thr, heap);
-#if defined(DUK_USE_STRTAB_CHAIN)
-	duk__debug_dump_strtab_chain(thr, heap);
-#endif
-#if defined(DUK_USE_STRTAB_PROBE)
-	duk__debug_dump_strtab_probe(thr, heap);
-#endif
+	duk__debug_dump_strtab(thr, heap);
 	duk_debug_write_eom(thr);
 }
 #endif  /* DUK_USE_DEBUGGER_DUMPHEAP */
@@ -41451,14 +42582,14 @@
 	"compfunc",
 	"natfunc",
 	"bufobj",
-	"thread",
+	"fastrefs",
 	"array_part",
 	"strict",
 	"notail",
 	"newenv",
 	"namebinding",
 	"createargs",
-	"envrecclosed",
+	"have_finalizer"
 	"exotic_array",
 	"exotic_stringobj",
 	"exotic_arguments",
@@ -41473,14 +42604,14 @@
 	DUK_HOBJECT_FLAG_COMPFUNC,
 	DUK_HOBJECT_FLAG_NATFUNC,
 	DUK_HOBJECT_FLAG_BUFOBJ,
-	DUK_HOBJECT_FLAG_THREAD,
+	DUK_HOBJECT_FLAG_FASTREFS,
 	DUK_HOBJECT_FLAG_ARRAY_PART,
 	DUK_HOBJECT_FLAG_STRICT,
 	DUK_HOBJECT_FLAG_NOTAIL,
 	DUK_HOBJECT_FLAG_NEWENV,
 	DUK_HOBJECT_FLAG_NAMEBINDING,
 	DUK_HOBJECT_FLAG_CREATEARGS,
-	DUK_HOBJECT_FLAG_ENVRECCLOSED,
+	DUK_HOBJECT_FLAG_HAVE_FINALIZER,
 	DUK_HOBJECT_FLAG_EXOTIC_ARRAY,
 	DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ,
 	DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS,
@@ -41648,9 +42779,9 @@
 		                           duk__debug_getinfo_hstring_keys,
 		                           duk__debug_getinfo_hstring_masks,
 		                           DUK_HEAPHDR_GET_FLAGS_RAW(h));
-		duk__debug_getinfo_prop_uint(thr, "bytelen", DUK_HSTRING_GET_BYTELEN(h_str));
-		duk__debug_getinfo_prop_uint(thr, "charlen", DUK_HSTRING_GET_CHARLEN(h_str));
-		duk__debug_getinfo_prop_uint(thr, "hash", DUK_HSTRING_GET_HASH(h_str));
+		duk__debug_getinfo_prop_uint(thr, "bytelen", (duk_uint_t) DUK_HSTRING_GET_BYTELEN(h_str));
+		duk__debug_getinfo_prop_uint(thr, "charlen", (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h_str));
+		duk__debug_getinfo_prop_uint(thr, "hash", (duk_uint_t) DUK_HSTRING_GET_HASH(h_str));
 		duk__debug_getinfo_flags_key(thr, "data");
 		duk_debug_write_hstring(thr, h_str);
 		break;
@@ -41748,6 +42879,26 @@
 			DUK_UNREF(h_thr);
 		}
 
+		if (DUK_HOBJECT_IS_DECENV(h_obj)) {
+			duk_hdecenv *h_env;
+			h_env = (duk_hdecenv *) h_obj;
+
+			duk__debug_getinfo_flags_key(thr, "thread");
+			duk_debug_write_heapptr(thr, (duk_heaphdr *) (h_env->thread));
+			duk__debug_getinfo_flags_key(thr, "varmap");
+			duk_debug_write_heapptr(thr, (duk_heaphdr *) (h_env->varmap));
+			duk__debug_getinfo_prop_uint(thr, "regbase", (duk_uint_t) h_env->regbase);
+		}
+
+		if (DUK_HOBJECT_IS_OBJENV(h_obj)) {
+			duk_hobjenv *h_env;
+			h_env = (duk_hobjenv *) h_obj;
+
+			duk__debug_getinfo_flags_key(thr, "target");
+			duk_debug_write_heapptr(thr, (duk_heaphdr *) (h_env->target));
+			duk__debug_getinfo_prop_bool(thr, "has_this", h_env->has_this);
+		}
+
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
 		if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) {
 			duk_hbufobj *h_bufobj;
@@ -42170,12 +43321,13 @@
 
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(thr->heap != NULL);
-	DUK_ASSERT(DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap));
+	DUK_ASSERT(duk_debug_is_attached(thr->heap));
 	DUK_ASSERT(thr->heap->dbg_processing == 0);
-
-	DUK_HEAP_SET_PAUSED(thr->heap);
-
-	act = duk_hthread_get_current_activation(thr);
+	DUK_ASSERT(!duk_debug_is_paused(thr->heap));
+
+	duk_debug_set_paused(thr->heap);
+
+	act = thr->callstack_curr;
 
 	/* NOTE: act may be NULL if an error is thrown outside of any activation,
 	 * which may happen in the case of, e.g. syntax errors.
@@ -42208,8 +43360,8 @@
 
 	thr->heap->dbg_state_dirty = 1;
 	while (DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap)) {
-		DUK_ASSERT(DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap));
-		DUK_ASSERT(thr->heap->dbg_processing);
+		DUK_ASSERT(duk_debug_is_attached(thr->heap));
+		DUK_ASSERT(thr->heap->dbg_processing == 0);
 		duk_debug_process_messages(thr, 0 /*no_block*/);
 	}
 
@@ -42271,7 +43423,7 @@
 	DUK_ASSERT(thr != NULL);
 	heap = thr->heap;
 	DUK_ASSERT(heap != NULL);
-	DUK_ASSERT(DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap));
+	DUK_ASSERT(duk_debug_is_attached(thr->heap));
 	DUK_ASSERT_DISABLE(breakpoint_index >= 0);  /* unsigned */
 
 	if (breakpoint_index >= heap->dbg_breakpoint_count) {
@@ -42300,6 +43452,55 @@
 	return 1;
 }
 
+/*
+ *  Misc state management
+ */
+
+DUK_INTERNAL duk_bool_t duk_debug_is_attached(duk_heap *heap) {
+	return (heap->dbg_read_cb != NULL);
+}
+
+DUK_INTERNAL duk_bool_t duk_debug_is_paused(duk_heap *heap) {
+	return (DUK_HEAP_HAS_DEBUGGER_PAUSED(heap) != 0);
+}
+
+DUK_INTERNAL void duk_debug_set_paused(duk_heap *heap) {
+	if (duk_debug_is_paused(heap)) {
+		DUK_D(DUK_DPRINT("trying to set paused state when already paused, ignoring"));
+	} else {
+		DUK_HEAP_SET_DEBUGGER_PAUSED(heap);
+		heap->dbg_state_dirty = 1;
+		duk_debug_clear_step_state(heap);
+		DUK_ASSERT(heap->ms_running == 0);  /* debugger can't be triggered within mark-and-sweep */
+		heap->ms_running = 1;  /* prevent mark-and-sweep, prevent refzero queueing */
+		heap->ms_prevent_count++;
+		DUK_ASSERT(heap->ms_prevent_count != 0);  /* Wrap. */
+		DUK_ASSERT(heap->heap_thread != NULL);
+	}
+}
+
+DUK_INTERNAL void duk_debug_clear_paused(duk_heap *heap) {
+	if (duk_debug_is_paused(heap)) {
+		DUK_HEAP_CLEAR_DEBUGGER_PAUSED(heap);
+		heap->dbg_state_dirty = 1;
+		duk_debug_clear_step_state(heap);
+		DUK_ASSERT(heap->ms_running == 1);
+		DUK_ASSERT(heap->ms_prevent_count > 0);
+		heap->ms_prevent_count--;
+		heap->ms_running = 0;
+		DUK_ASSERT(heap->heap_thread != NULL);
+	} else {
+		DUK_D(DUK_DPRINT("trying to clear paused state when not paused, ignoring"));
+	}
+}
+
+DUK_INTERNAL void duk_debug_clear_step_state(duk_heap *heap) {
+	heap->dbg_step_type = DUK_STEP_TYPE_NONE;
+	heap->dbg_step_thread = NULL;
+	heap->dbg_step_csindex = 0;
+	heap->dbg_step_startline = 0;
+}
+
 #else  /* DUK_USE_DEBUGGER_SUPPORT */
 
 /* No debugger support. */
@@ -42939,18 +44140,37 @@
 
 DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) {
 	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
 
 	DUK_DD(DUK_DDPRINT("longjmp error: type=%d iserror=%d value1=%!T value2=%!T",
 	                   (int) thr->heap->lj.type, (int) thr->heap->lj.iserror,
 	                   &thr->heap->lj.value1, &thr->heap->lj.value2));
 
-	/* Perform a refzero check before throwing: this catches cases where
-	 * some internal code uses no-refzero (NORZ) macro variants but an
-	 * error occurs before it has the chance to DUK_REFZERO_CHECK_xxx()
-	 * explicitly.  Refzero'ed objects would otherwise remain pending
-	 * until the next refzero (which is not a big issue but still).
-	 */
-	DUK_REFZERO_CHECK_SLOW(thr);
+	/* Prevent finalizer execution during error handling.  All error
+	 * handling sites will process pending finalizers once error handling
+	 * is complete and we're ready for the side effects.  Does not prevent
+	 * refzero freeing or mark-and-sweep during error handling.
+	 *
+	 * NOTE: when we come here some calling code may have used DECREF
+	 * NORZ macros without an explicit DUK_REFZERO_CHECK_xxx() call.
+	 * We don't want to do it here because it would just check for
+	 * pending finalizers and we prevent that explicitly.  Instead,
+	 * the error catcher will run the finalizers once error handling
+	 * is complete.
+	 */
+
+	DUK_ASSERT_LJSTATE_SET(thr->heap);
+
+	thr->heap->pf_prevent_count++;
+	DUK_ASSERT(thr->heap->pf_prevent_count != 0);  /* Wrap. */
+
+#if defined(DUK_USE_ASSERTIONS)
+	/* XXX: set this immediately when longjmp state is set */
+	DUK_ASSERT(thr->heap->error_not_allowed == 0);  /* Detect error within critical section. */
+	thr->heap->error_not_allowed = 1;
+#endif
+
+	DUK_DD(DUK_DDPRINT("about to longjmp, pf_prevent_count=%ld", (long) thr->heap->pf_prevent_count));
 
 #if !defined(DUK_USE_CPP_EXCEPTIONS)
 	/* If we don't have a jmpbuf_ptr, there is little we can do except
@@ -43050,11 +44270,16 @@
 }
 
 /*
- *  Exposed helper for setting up heap longjmp state.
- */
-
-DUK_INTERNAL void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type) {
+ *  Helper for debugger throw notify and pause-on-uncaught integration.
+ */
+
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
+#if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) || defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT)
+DUK_INTERNAL void duk_err_check_debugger_integration(duk_hthread *thr) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_bool_t fatal;
+	duk_tval *tv_obj;
+
 	/* If something is thrown with the debugger attached and nobody will
 	 * catch it, execution is paused before the longjmp, turning over
 	 * control to the debug client.  This allows local state to be examined
@@ -43062,55 +44287,102 @@
 	 * message loop is active (e.g. for Eval).
 	 */
 
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+
 	/* XXX: Allow customizing the pause and notify behavior at runtime
 	 * using debugger runtime flags.  For now the behavior is fixed using
 	 * config options.
 	 */
-#if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) || defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT)
-	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap) &&
-	    !thr->heap->dbg_processing &&
-	    lj_type == DUK_LJ_TYPE_THROW) {
-		duk_context *ctx = (duk_context *) thr;
-		duk_bool_t fatal;
-		duk_hobject *h_obj;
-
-		/* Don't intercept a DoubleError, we may have caused the initial double
-		 * fault and attempting to intercept it will cause us to be called
-		 * recursively and exhaust the C stack.
-		 */
-		h_obj = duk_get_hobject(ctx, -1);
-		if (h_obj == thr->builtins[DUK_BIDX_DOUBLE_ERROR]) {
-			DUK_D(DUK_DPRINT("built-in DoubleError instance thrown, not intercepting"));
-			goto skip_throw_intercept;
-		}
-
-		DUK_D(DUK_DPRINT("throw with debugger attached, report to client"));
-
-		fatal = !duk__have_active_catcher(thr);
+
+	if (!duk_debug_is_attached(thr->heap) ||
+	    thr->heap->dbg_processing ||
+	    thr->heap->lj.type != DUK_LJ_TYPE_THROW ||
+	    thr->heap->creating_error) {
+		DUK_D(DUK_DPRINT("skip debugger error integration; not attached, debugger processing, not THROW, or error thrown while creating error"));
+		return;
+	}
+
+	/* Don't intercept a DoubleError, we may have caused the initial double
+	 * fault and attempting to intercept it will cause us to be called
+	 * recursively and exhaust the C stack.  (This should no longer happen
+	 * for the initial throw because DoubleError path doesn't do a debugger
+	 * integration check, but it might happen for rethrows.)
+	 */
+	tv_obj = &thr->heap->lj.value1;
+	if (DUK_TVAL_IS_OBJECT(tv_obj) && DUK_TVAL_GET_OBJECT(tv_obj) == thr->builtins[DUK_BIDX_DOUBLE_ERROR]) {
+		DUK_D(DUK_DPRINT("built-in DoubleError instance (re)thrown, not intercepting"));
+		return;
+	}
+
+	fatal = !duk__have_active_catcher(thr);
+
+	/* Debugger code expects the value at stack top.  This also serves
+	 * as a backup: we need to store/restore the longjmp state because
+	 * when the debugger is paused Eval commands may be executed and
+	 * they can arbitrarily clobber the longjmp state.
+	 */
+	duk_push_tval(ctx, tv_obj);
+
+	/* Store and reset longjmp state. */
+	DUK_ASSERT_LJSTATE_SET(thr->heap);
+	DUK_TVAL_DECREF_NORZ(thr, tv_obj);
+	DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2));  /* Always for THROW type. */
+	DUK_TVAL_SET_UNDEFINED(tv_obj);
+	thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
 
 #if defined(DUK_USE_DEBUGGER_THROW_NOTIFY)
-		/* Report it to the debug client */
-		duk_debug_send_throw(thr, fatal);
+	/* Report it to the debug client */
+	DUK_D(DUK_DPRINT("throw with debugger attached, report to client"));
+	duk_debug_send_throw(thr, fatal);
 #endif
 
 #if defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT)
-		if (fatal) {
-			DUK_D(DUK_DPRINT("throw will be fatal, halt before longjmp"));
-			duk_debug_halt_execution(thr, 1 /*use_prev_pc*/);
-		}
-#endif
-	}
-
- skip_throw_intercept:
+	if (fatal) {
+		DUK_D(DUK_DPRINT("throw will be fatal, halt before longjmp"));
+		duk_debug_halt_execution(thr, 1 /*use_prev_pc*/);
+	}
+#endif
+
+	/* Restore longjmp state. */
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
+	thr->heap->lj.type = DUK_LJ_TYPE_THROW;
+	tv_obj = DUK_GET_TVAL_NEGIDX(ctx, -1);
+	DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1));
+	DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2));
+	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, tv_obj);
+	DUK_TVAL_INCREF(thr, tv_obj);
+	DUK_ASSERT_LJSTATE_SET(thr->heap);
+
+	duk_pop(ctx);
+}
+#else  /* DUK_USE_DEBUGGER_THROW_NOTIFY || DUK_USE_DEBUGGER_PAUSE_UNCAUGHT */
+DUK_INTERNAL void duk_err_check_debugger_integration(duk_hthread *thr) {
+	DUK_UNREF(thr);
+}
 #endif  /* DUK_USE_DEBUGGER_THROW_NOTIFY || DUK_USE_DEBUGGER_PAUSE_UNCAUGHT */
 #endif  /* DUK_USE_DEBUGGER_SUPPORT */
 
-	thr->heap->lj.type = lj_type;
-
-	DUK_ASSERT(thr->valstack_top > thr->valstack);
-	DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, thr->valstack_top - 1);  /* side effects */
-
-	duk_pop((duk_context *) thr);
+/*
+ *  Helpers for setting up heap longjmp state.
+ */
+
+DUK_INTERNAL void duk_err_setup_ljstate1(duk_hthread *thr, duk_small_uint_t lj_type, duk_tval *tv_val) {
+	duk_heap *heap;
+
+	DUK_ASSERT(thr != NULL);
+	heap = thr->heap;
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(tv_val != NULL);
+
+	DUK_ASSERT_LJSTATE_UNSET(heap);
+
+	heap->lj.type = lj_type;
+	DUK_TVAL_SET_TVAL(&heap->lj.value1, tv_val);
+	DUK_TVAL_INCREF(thr, tv_val);
+
+	DUK_ASSERT_LJSTATE_SET(heap);
 }
 #line 1 "duk_error_throw.c"
 /*
@@ -43131,7 +44403,7 @@
  *
  *  If an error occurs while we're dealing with the current error, we might
  *  enter an infinite recursion loop.  This is prevented by detecting a
- *  "double fault" through the heap->handling_error flag; the recursion
+ *  "double fault" through the heap->creating_error flag; the recursion
  *  then stops at the second level.
  */
 
@@ -43141,7 +44413,6 @@
 DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code) {
 #endif
 	duk_context *ctx = (duk_context *) thr;
-	duk_bool_t double_error = thr->heap->handling_error;
 
 #if defined(DUK_USE_VERBOSE_ERRORS)
 	DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld, msg=%s, filename=%s, line=%ld",
@@ -43154,18 +44425,11 @@
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(ctx != NULL);
 
-	thr->heap->handling_error = 1;
-
-	if (!double_error) {
-		/* Allow headroom for calls during error handling (see GH-191).
-		 * We allow space for 10 additional recursions, with one extra
-		 * for, e.g. a print() call at the deepest level.
-		 */
-		DUK_ASSERT(thr->callstack_max == DUK_CALLSTACK_DEFAULT_MAX);
-		thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX + DUK_CALLSTACK_GROW_STEP + 11;
-	}
-
-	DUK_ASSERT(thr->callstack_max == DUK_CALLSTACK_DEFAULT_MAX + DUK_CALLSTACK_GROW_STEP + 11);  /* just making sure */
+	/* Even though nested call is possible because we throw an error when
+	 * trying to create an error, the potential errors must happen before
+	 * the longjmp state is configured.
+	 */
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
 
 	/* Sync so that augmentation sees up-to-date activations, NULL
 	 * thr->ptr_curr_pc so that it's not used if side effects occur
@@ -43175,29 +44439,50 @@
 
 	/*
 	 *  Create and push an error object onto the top of stack.
+	 *  The error is potentially augmented before throwing.
+	 *
 	 *  If a "double error" occurs, use a fixed error instance
 	 *  to avoid further trouble.
 	 */
 
-	/* XXX: if attempt to push beyond allocated valstack, this double fault
-	 * handling fails miserably.  We should really write the double error
-	 * directly to thr->heap->lj.value1 and avoid valstack use entirely.
-	 */
-
-	if (double_error) {
-		if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) {
-			DUK_D(DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance"));
-			duk_push_hobject_bidx(ctx, DUK_BIDX_DOUBLE_ERROR);
+	if (thr->heap->creating_error) {
+		duk_tval tv_val;
+		duk_hobject *h_err;
+
+#if 0  /* XXX: not always true because the second throw may come from a different coroutine */
+		DUK_ASSERT(thr->callstack_max == DUK_CALLSTACK_DEFAULT_MAX + DUK_CALLSTACK_GROW_STEP + 11);
+#endif
+		thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX;
+		thr->heap->creating_error = 0;
+
+		h_err = thr->builtins[DUK_BIDX_DOUBLE_ERROR];
+		if (h_err != NULL) {
+			DUK_D(DUK_DPRINT("double fault detected -> use built-in fixed 'double error' instance"));
+			DUK_TVAL_SET_OBJECT(&tv_val, h_err);
 		} else {
 			DUK_D(DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance "
-			                 "-> push the error code as a number"));
-			duk_push_int(ctx, (duk_int_t) code);
-		}
-	} else {
-		/* Error object is augmented at its creation here. */
+			                 "-> use the error code as a number"));
+			DUK_TVAL_SET_I32(&tv_val, (duk_int32_t) code);
+		}
+
+		duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, &tv_val);
+
+		/* No augmentation to avoid any allocations or side effects. */
+	} else {
+		/* Allow headroom for calls during error handling (see GH-191).
+		 * We allow space for 10 additional recursions, with one extra
+		 * for, e.g. a print() call at the deepest level.
+		 */
+#if 0  /* XXX: not always true, second throw may come from a different coroutine */
+		DUK_ASSERT(thr->callstack_max == DUK_CALLSTACK_DEFAULT_MAX);
+#endif
+		thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX + DUK_CALLSTACK_GROW_STEP + 11;
+		thr->heap->creating_error = 1;
+
 		duk_require_stack(ctx, 1);
-		/* XXX: unnecessary '%s' formatting here, but cannot use
-		 * 'msg' as a format string directly.
+
+		/* XXX: usually unnecessary '%s' formatting here, but cannot
+		 * use 'msg' as a format string directly.
 		 */
 #if defined(DUK_USE_VERBOSE_ERRORS)
 		duk_push_error_object_raw(ctx,
@@ -43213,37 +44498,38 @@
 		                          0,
 		                          NULL);
 #endif
-	}
-
-	/*
-	 *  Augment error (throw time), unless double error
-	 *
-	 *  Note that an alloc error may happen during error augmentation.
-	 *  This may happen both when the original error is an alloc error
-	 *  and when it's something else.  Because any error in augmentation
-	 *  must be handled correctly anyway, there's no special check for
-	 *  avoiding it for alloc errors (this differs from Duktape 1.x).
-	 */
-
-	if (double_error) {
-		DUK_D(DUK_DPRINT("double error: skip throw augmenting to avoid further trouble"));
-	} else {
+
+		/* Note that an alloc error may happen during error augmentation.
+		 * This may happen both when the original error is an alloc error
+		 * and when it's something else.  Because any error in augmentation
+		 * must be handled correctly anyway, there's no special check for
+		 * avoiding it for alloc errors (this differs from Duktape 1.x).
+		 */
 #if defined(DUK_USE_AUGMENT_ERROR_THROW)
 		DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT (before throw augment)",
 		                     (duk_tval *) duk_get_tval(ctx, -1)));
 		duk_err_augment_error_throw(thr);
 #endif
+
+		duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, DUK_GET_TVAL_NEGIDX(ctx, -1));
+		thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX;
+		thr->heap->creating_error = 0;
+
+		/* Error is now created and we assume no errors can occur any
+		 * more.  Check for debugger Throw integration only when the
+		 * error is complete.  If we enter debugger message loop,
+		 * creating_error must be 0 so that errors can be thrown in
+		 * the paused state, e.g. in Eval commands.
+		 */
+#if defined(DUK_USE_DEBUGGER_SUPPORT)
+		duk_err_check_debugger_integration(thr);
+#endif
 	}
 
 	/*
 	 *  Finally, longjmp
 	 */
 
-	duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
-
-	thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX;  /* reset callstack limit */
-	thr->heap->handling_error = 0;
-
 	DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT (after throw augment)",
 	                     (duk_tval *) &thr->heap->lj.value1, (duk_tval *) &thr->heap->lj.value2));
 
@@ -43318,8 +44604,8 @@
 	}
 
 	res = (duk_hbuffer *) DUK_ALLOC(heap, alloc_size);
-	if (!res) {
-		goto error;
+	if (DUK_UNLIKELY(res == NULL)) {
+		goto alloc_error;
 	}
 
 	/* zero everything unless requested not to do so */
@@ -43355,9 +44641,9 @@
 #else
 			ptr = DUK_ALLOC(heap, size);
 #endif
-			if (!ptr) {
+			if (DUK_UNLIKELY(ptr == NULL)) {
 				/* Because size > 0, NULL check is correct */
-				goto error;
+				goto alloc_error;
 			}
 			*out_bufdata = ptr;
 
@@ -43393,7 +44679,7 @@
 	DUK_DDD(DUK_DDDPRINT("allocated hbuffer: %p", (void *) res));
 	return res;
 
- error:
+ alloc_error:
 	DUK_DD(DUK_DDPRINT("hbuffer allocation failed"));
 
 	DUK_FREE(heap, res);
@@ -43449,7 +44735,7 @@
 	 */
 
 	res = DUK_REALLOC_INDIRECT(thr->heap, duk_hbuffer_get_dynalloc_ptr, (void *) buf, new_size);
-	if (res != NULL || new_size == 0) {
+	if (DUK_LIKELY(res != NULL || new_size == 0)) {
 		/* 'res' may be NULL if new allocation size is 0. */
 
 		DUK_DDD(DUK_DDDPRINT("resized dynamic buffer %p:%ld -> %p:%ld",
@@ -43606,11 +44892,9 @@
 	case DUK_HTYPE_OBJECT:
 		duk_free_hobject(heap, (duk_hobject *) hdr);
 		break;
-	case DUK_HTYPE_BUFFER:
+	default:
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_BUFFER);
 		duk_free_hbuffer(heap, (duk_hbuffer *) hdr);
-		break;
-	default:
-		DUK_UNREACHABLE();
 	}
 
 }
@@ -43646,23 +44930,8 @@
 	}
 }
 
-#if defined(DUK_USE_REFERENCE_COUNTING)
-DUK_LOCAL void duk__free_refzero_list(duk_heap *heap) {
-	duk_heaphdr *curr;
-	duk_heaphdr *next;
-
-	curr = heap->refzero_list;
-	while (curr) {
-		DUK_DDD(DUK_DDDPRINT("FINALFREE (refzero_list): %!iO",
-		                     (duk_heaphdr *) curr));
-		next = DUK_HEAPHDR_GET_NEXT(heap, curr);
-		duk_heap_free_heaphdr_raw(heap, curr);
-		curr = next;
-	}
-}
-#endif
-
-DUK_LOCAL void duk__free_markandsweep_finalize_list(duk_heap *heap) {
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+DUK_LOCAL void duk__free_finalize_list(duk_heap *heap) {
 	duk_heaphdr *curr;
 	duk_heaphdr *next;
 
@@ -43675,15 +44944,15 @@
 		curr = next;
 	}
 }
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
 
 DUK_LOCAL void duk__free_stringtable(duk_heap *heap) {
 	/* strings are only tracked by stringtable */
-	duk_heap_free_strtab(heap);
+	duk_heap_strtable_free(heap);
 }
 
 #if defined(DUK_USE_FINALIZER_SUPPORT)
 DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) {
-	duk_hthread *thr;
 	duk_heaphdr *curr;
 	duk_uint_t round_no;
 	duk_size_t count_all;
@@ -43691,25 +44960,31 @@
 	duk_size_t curr_limit;
 
 	DUK_ASSERT(heap != NULL);
-	DUK_ASSERT(heap->heap_thread != NULL);
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
 	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
 #endif
-	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
-
-	/* XXX: here again finalizer thread is the heap_thread which needs
-	 * to be coordinated with finalizer thread fixes.
-	 */
-	thr = heap->heap_thread;
-	DUK_ASSERT(thr != NULL);
-
-	/* Prevent mark-and-sweep for the pending finalizers, also prevents
-	 * refzero handling from moving objects away from the heap_allocated
-	 * list.  (The flag meaning is slightly abused here.)
-	 */
-	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
-	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);
+	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep last pass */
+
+	if (heap->heap_thread == NULL) {
+		/* May happen when heap allocation fails right off.  There
+		 * cannot be any finalizable objects in this case.
+		 */
+		DUK_D(DUK_DPRINT("no heap_thread in heap destruct, assume no finalizable objects"));
+		return;
+	}
+
+	/* Prevent finalize_list processing and mark-and-sweep entirely.
+	 * Setting ms_running = 1 also prevents refzero handling from moving
+	 * objects away from the heap_allocated list (the flag name is a bit
+	 * misleading here).
+	 */
+	DUK_ASSERT(heap->pf_prevent_count == 0);
+	heap->pf_prevent_count = 1;
+	DUK_ASSERT(heap->ms_running == 0);
+	heap->ms_running = 1;
+	DUK_ASSERT(heap->ms_prevent_count == 0);
+	heap->ms_prevent_count = 1;  /* Bump, because mark-and-sweep assumes it's bumped when ms_running is set. */
 
 	curr_limit = 0;  /* suppress warning, not used */
 	for (round_no = 0; ; round_no++) {
@@ -43718,18 +44993,17 @@
 		count_finalized = 0;
 		while (curr) {
 			count_all++;
-			if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
+			if (DUK_HEAPHDR_IS_OBJECT(curr)) {
 				/* Only objects in heap_allocated may have finalizers.  Check that
 				 * the object itself has a _Finalizer property (own or inherited)
 				 * so that we don't execute finalizers for e.g. Proxy objects.
 				 */
-				DUK_ASSERT(thr != NULL);
 				DUK_ASSERT(curr != NULL);
 
-				if (duk_hobject_hasprop_raw(thr, (duk_hobject *) curr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
+				if (DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) curr)) {
 					if (!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) curr)) {
 						DUK_ASSERT(DUK_HEAP_HAS_FINALIZER_NORESCUE(heap));  /* maps to finalizer 2nd argument */
-						duk_hobject_run_finalizer(thr, (duk_hobject *) curr);
+						duk_heap_run_finalizer(heap, (duk_hobject *) curr);
 						count_finalized++;
 					}
 				}
@@ -43770,8 +45044,10 @@
 		}
 	}
 
-	DUK_ASSERT(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
-	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);
+	DUK_ASSERT(heap->ms_running == 1);
+	heap->ms_running = 0;
+	DUK_ASSERT(heap->pf_prevent_count == 1);
+	heap->pf_prevent_count = 0;
 }
 #endif  /* DUK_USE_FINALIZER_SUPPORT */
 
@@ -43779,7 +45055,7 @@
 	DUK_D(DUK_DPRINT("free heap: %p", (void *) heap));
 
 #if defined(DUK_USE_DEBUG)
-	duk_heap_dump_strtab(heap);
+	duk_heap_strtable_dump(heap);
 #endif
 
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
@@ -43793,32 +45069,47 @@
 #endif
 
 	/* Execute finalizers before freeing the heap, even for reachable
-	 * objects, and regardless of whether or not mark-and-sweep is
-	 * enabled.  This gives finalizers the chance to free any native
+	 * objects.  This gives finalizers the chance to free any native
 	 * resources like file handles, allocations made outside Duktape,
 	 * etc.  This is quite tricky to get right, so that all finalizer
 	 * guarantees are honored.
 	 *
-	 * XXX: this perhaps requires an execution time limit.
-	 */
-	DUK_D(DUK_DPRINT("execute finalizers before freeing heap"));
-	/* Run mark-and-sweep a few times just in case (unreachable object
+	 * Run mark-and-sweep a few times just in case (unreachable object
 	 * finalizers run already here).  The last round must rescue objects
 	 * from the previous round without running any more finalizers.  This
 	 * ensures rescued objects get their FINALIZED flag cleared so that
 	 * their finalizer is called once more in forced finalization to
 	 * satisfy finalizer guarantees.  However, we don't want to run any
-	 * more finalizer because that'd required one more loop, and so on.
-	 */
+	 * more finalizers because that'd required one more loop, and so on.
+	 *
+	 * XXX: this perhaps requires an execution time limit.
+	 */
+	DUK_D(DUK_DPRINT("execute finalizers before freeing heap"));
+	DUK_ASSERT(heap->pf_skip_finalizers == 0);
 	DUK_D(DUK_DPRINT("forced gc #1 in heap destruction"));
 	duk_heap_mark_and_sweep(heap, 0);
 	DUK_D(DUK_DPRINT("forced gc #2 in heap destruction"));
 	duk_heap_mark_and_sweep(heap, 0);
 	DUK_D(DUK_DPRINT("forced gc #3 in heap destruction (don't run finalizers)"));
-	duk_heap_mark_and_sweep(heap, DUK_MS_FLAG_SKIP_FINALIZERS);  /* skip finalizers; queue finalizable objects to heap_allocated */
+	heap->pf_skip_finalizers = 1;
+	duk_heap_mark_and_sweep(heap, 0);  /* Skip finalizers; queue finalizable objects to heap_allocated. */
+
+	/* There are never objects in refzero_list at this point, or at any
+	 * point beyond a DECREF (even a DECREF_NORZ).  Since Duktape 2.1
+	 * refzero_list processing is side effect free, so it is always
+	 * processed to completion by a DECREF initially triggering a zero
+	 * refcount.
+	 */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	DUK_ASSERT(heap->refzero_list == NULL);  /* Always processed to completion inline. */
+#endif
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	DUK_ASSERT(heap->finalize_list == NULL);  /* Last mark-and-sweep with skip_finalizers. */
+#endif
 
 #if defined(DUK_USE_FINALIZER_SUPPORT)
-	DUK_HEAP_SET_FINALIZER_NORESCUE(heap);  /* rescue no longer supported */
+	DUK_D(DUK_DPRINT("run finalizers for remaining finalizable objects"));
+	DUK_HEAP_SET_FINALIZER_NORESCUE(heap);  /* Rescue no longer supported. */
 	duk__free_run_finalizers(heap);
 #endif  /* DUK_USE_FINALIZER_SUPPORT */
 
@@ -43826,16 +45117,17 @@
 	 * are on the heap allocated list.
 	 */
 
-	DUK_D(DUK_DPRINT("freeing heap objects of heap: %p", (void *) heap));
+	DUK_D(DUK_DPRINT("freeing heap_allocated of heap: %p", (void *) heap));
 	duk__free_allocated(heap);
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	DUK_D(DUK_DPRINT("freeing refzero list of heap: %p", (void *) heap));
-	duk__free_refzero_list(heap);
-#endif
-
-	DUK_D(DUK_DPRINT("freeing mark-and-sweep finalize list of heap: %p", (void *) heap));
-	duk__free_markandsweep_finalize_list(heap);
+	DUK_ASSERT(heap->refzero_list == NULL);  /* Always processed to completion inline. */
+#endif
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	DUK_D(DUK_DPRINT("freeing finalize_list of heap: %p", (void *) heap));
+	duk__free_finalize_list(heap);
+#endif
 
 	DUK_D(DUK_DPRINT("freeing string table of heap: %p", (void *) heap));
 	duk__free_stringtable(heap);
@@ -43857,20 +45149,26 @@
 	duk_small_uint_t i;
 #endif
 
+	DUK_UNREF(heap);
+
 	/* With ROM-based strings, heap->strs[] and thr->strs[] are omitted
 	 * so nothing to initialize for strs[].
 	 */
 
 #if defined(DUK_USE_ASSERTIONS)
-	for (i = 0; i < sizeof(duk_rom_strings) / sizeof(const duk_hstring *); i++) {
-		duk_uint32_t hash;
+	for (i = 0; i < sizeof(duk_rom_strings_lookup) / sizeof(const duk_hstring *); i++) {
 		const duk_hstring *h;
-		h = duk_rom_strings[i];
-		DUK_ASSERT(h != NULL);
-		hash = duk_heap_hashstring(heap, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
-		DUK_DD(DUK_DDPRINT("duk_rom_strings[%d] -> hash 0x%08lx, computed 0x%08lx",
-		                   (int) i, (unsigned long) DUK_HSTRING_GET_HASH(h), (unsigned long) hash));
-		DUK_ASSERT(hash == (duk_uint32_t) DUK_HSTRING_GET_HASH(h));
+		duk_uint32_t hash;
+
+		h = duk_rom_strings_lookup[i];
+		while (h != NULL) {
+			hash = duk_heap_hashstring(heap, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
+			DUK_DD(DUK_DDPRINT("duk_rom_strings_lookup[%d] -> hash 0x%08lx, computed 0x%08lx",
+			                   (int) i, (unsigned long) DUK_HSTRING_GET_HASH(h), (unsigned long) hash));
+			DUK_ASSERT(hash == (duk_uint32_t) DUK_HSTRING_GET_HASH(h));
+
+			h = (const duk_hstring *) h->hdr.h_next;
+		}
 	}
 #endif
 	return 1;
@@ -43898,9 +45196,9 @@
 		 */
 		DUK_ASSERT(len <= 0xffffUL);
 		DUK_DDD(DUK_DDDPRINT("intern built-in string %ld", (long) i));
-		h = duk_heap_string_intern(heap, tmp, len);
+		h = duk_heap_strtable_intern(heap, tmp, len);
 		if (!h) {
-			goto error;
+			goto failed;
 		}
 		DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h));
 
@@ -43935,7 +45233,7 @@
 
 	return 1;
 
- error:
+ failed:
 	return 0;
 }
 #endif  /* DUK_USE_ROM_STRINGS */
@@ -43943,12 +45241,11 @@
 DUK_LOCAL duk_bool_t duk__init_heap_thread(duk_heap *heap) {
 	duk_hthread *thr;
 
-	DUK_DD(DUK_DDPRINT("heap init: alloc heap thread"));
-	thr = duk_hthread_alloc(heap,
-	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                        DUK_HOBJECT_FLAG_THREAD |
-	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD));
-	if (!thr) {
+	DUK_D(DUK_DPRINT("heap init: alloc heap thread"));
+	thr = duk_hthread_alloc_unchecked(heap,
+	                                  DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                  DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD));
+	if (thr == NULL) {
 		DUK_D(DUK_DPRINT("failed to alloc heap_thread"));
 		return 0;
 	}
@@ -43968,6 +45265,7 @@
 
 	/* 'thr' is now reachable */
 
+	DUK_D(DUK_DPRINT("heap init: init heap thread stacks"));
 	if (!duk_hthread_init_stacks(heap, thr)) {
 		return 0;
 	}
@@ -44086,6 +45384,8 @@
 	DUK__DUMPSZ(duk_harray);
 	DUK__DUMPSZ(duk_hcompfunc);
 	DUK__DUMPSZ(duk_hnatfunc);
+	DUK__DUMPSZ(duk_hdecenv);
+	DUK__DUMPSZ(duk_hobjenv);
 	DUK__DUMPSZ(duk_hthread);
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
 	DUK__DUMPSZ(duk_hbufobj);
@@ -44098,9 +45398,6 @@
 	DUK__DUMPSZ(duk_propvalue);
 	DUK__DUMPSZ(duk_propdesc);
 	DUK__DUMPSZ(duk_heap);
-#if defined(DUK_USE_STRTAB_CHAIN)
-	DUK__DUMPSZ(duk_strtab_entry);
-#endif
 	DUK__DUMPSZ(duk_activation);
 	DUK__DUMPSZ(duk_catcher);
 	DUK__DUMPSZ(duk_strcache);
@@ -44209,10 +45506,21 @@
                          void *heap_udata,
                          duk_fatal_function fatal_func) {
 	duk_heap *res = NULL;
+	duk_uint32_t st_initsize;
 
 	DUK_D(DUK_DPRINT("allocate heap"));
 
 	/*
+	 *  Random config sanity asserts
+	 */
+
+	DUK_ASSERT(DUK_USE_STRTAB_MINSIZE >= 64);
+
+	DUK_ASSERT((DUK_HTYPE_STRING & 0x01U) == 0);
+	DUK_ASSERT((DUK_HTYPE_BUFFER & 0x01U) == 0);
+	DUK_ASSERT((DUK_HTYPE_OBJECT & 0x01U) == 1);  /* DUK_HEAPHDR_IS_OBJECT() relies ont his. */
+
+	/*
 	 *  Debug dump type sizes
 	 */
 
@@ -44227,9 +45535,11 @@
 	 */
 
 #if defined(DUK_USE_SELF_TESTS)
+	DUK_D(DUK_DPRINT("run self tests"));
 	if (duk_selftest_run_tests(alloc_func, realloc_func, free_func, heap_udata) > 0) {
 		fatal_func(heap_udata, "self test(s) failed");
 	}
+	DUK_D(DUK_DPRINT("self tests passed"));
 #endif
 
 	/*
@@ -44286,9 +45596,13 @@
 	 *  Use a raw call, all macros expect the heap to be initialized
 	 */
 
+#if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 1)
+	goto failed;
+#endif
+	DUK_D(DUK_DPRINT("alloc duk_heap object"));
 	res = (duk_heap *) alloc_func(heap_udata, sizeof(duk_heap));
 	if (!res) {
-		goto error;
+		goto failed;
 	}
 
 	/*
@@ -44296,6 +45610,9 @@
 	 */
 
 	DUK_MEMZERO(res, sizeof(*res));
+#if defined(DUK_USE_ASSERTIONS)
+	res->heap_initializing = 1;
+#endif
 
 	/* explicit NULL inits */
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
@@ -44303,20 +45620,20 @@
 	res->heap_allocated = NULL;
 #if defined(DUK_USE_REFERENCE_COUNTING)
 	res->refzero_list = NULL;
-	res->refzero_list_tail = NULL;
-#endif
+#endif
+#if defined(DUK_USE_FINALIZER_SUPPORT)
 	res->finalize_list = NULL;
+#if defined(DUK_USE_ASSERTIONS)
+	res->currently_finalizing = NULL;
+#endif
+#endif
 	res->heap_thread = NULL;
 	res->curr_thread = NULL;
 	res->heap_object = NULL;
-#if defined(DUK_USE_STRTAB_CHAIN)
-	/* nothing to NULL */
-#elif defined(DUK_USE_STRTAB_PROBE)
-#if defined(DUK_USE_HEAPPTR16)
-	res->strtable16 = (duk_uint16_t *) NULL;
-#else
-	res->strtable = (duk_hstring **) NULL;
-#endif
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	res->strtable16 = NULL;
+#else
+	res->strtable = NULL;
 #endif
 #if defined(DUK_USE_ROM_STRINGS)
 	/* no res->strs[] */
@@ -44350,13 +45667,21 @@
 	res->heap_udata = heap_udata;
 	res->fatal_func = fatal_func;
 
-#if defined(DUK_USE_HEAPPTR16)
-	/* XXX: zero assumption */
-	res->heapptr_null16 = DUK_USE_HEAPPTR_ENC16(res->heap_udata, (void *) NULL);
-	res->heapptr_deleted16 = DUK_USE_HEAPPTR_ENC16(res->heap_udata, (void *) DUK_STRTAB_DELETED_MARKER(res));
-#endif
-
-	/* res->mark_and_sweep_trigger_counter == 0 -> now causes immediate GC; which is OK */
+	/* XXX: for now there's a pointer packing zero assumption, i.e.
+	 * NULL <=> compressed pointer 0.  If this is removed, may need
+	 * to precompute e.g. null16 here.
+	 */
+
+	/* res->ms_trigger_counter == 0 -> now causes immediate GC; which is OK */
+
+	/* Prevent mark-and-sweep and finalizer execution until heap is completely
+	 * initialized.
+	 */
+	DUK_ASSERT(res->ms_prevent_count == 0);
+	DUK_ASSERT(res->pf_prevent_count == 0);
+	res->ms_prevent_count = 1;
+	res->pf_prevent_count = 1;
+	DUK_ASSERT(res->ms_running == 0);
 
 	res->call_recursion_depth = 0;
 	res->call_recursion_limit = DUK_USE_NATIVE_CALL_RECLIMIT;
@@ -44384,71 +45709,49 @@
 	res->lj.jmpbuf_ptr = NULL;
 #endif
 	DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN);  /* zero */
-
+	DUK_ASSERT(res->lj.iserror == 0);
 	DUK_TVAL_SET_UNDEFINED(&res->lj.value1);
 	DUK_TVAL_SET_UNDEFINED(&res->lj.value2);
 
-#if (DUK_STRTAB_INITIAL_SIZE < DUK_UTIL_MIN_HASH_PRIME)
-#error initial heap stringtable size is defined incorrectly
-#endif
+	DUK_ASSERT_LJSTATE_UNSET(res);
 
 	/*
 	 *  Init stringtable: fixed variant
 	 */
 
-#if defined(DUK_USE_STRTAB_CHAIN)
-	DUK_MEMZERO(res->strtable, sizeof(duk_strtab_entry) * DUK_STRTAB_CHAIN_SIZE);
+	st_initsize = DUK_USE_STRTAB_MINSIZE;
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	res->strtable16 = (duk_uint16_t *) alloc_func(heap_udata, sizeof(duk_uint16_t) * st_initsize);
+	if (res->strtable16 == NULL) {
+		goto failed;
+	}
+#else
+	res->strtable = (duk_hstring **) alloc_func(heap_udata, sizeof(duk_hstring *) * st_initsize);
+	if (res->strtable == NULL) {
+		goto failed;
+	}
+#endif
+	res->st_size = st_initsize;
+	res->st_mask = st_initsize - 1;
+#if (DUK_USE_STRTAB_MINSIZE != DUK_USE_STRTAB_MAXSIZE)
+	DUK_ASSERT(res->st_count == 0);
+#endif
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	/* zero assumption */
+	DUK_MEMZERO(res->strtable16, sizeof(duk_uint16_t) * st_initsize);
+#else
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
 	{
 		duk_small_uint_t i;
-	        for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-			res->strtable[i].u.str16 = res->heapptr_null16;
-#else
-			res->strtable[i].u.str = NULL;
-#endif
+	        for (i = 0; i < st_initsize; i++) {
+			res->strtable[i] = NULL;
 	        }
 	}
+#else
+	DUK_MEMZERO(res->strtable, sizeof(duk_hstring *) * st_initsize);
 #endif  /* DUK_USE_EXPLICIT_NULL_INIT */
-#endif  /* DUK_USE_STRTAB_CHAIN */
-
-	/*
-	 *  Init stringtable: probe variant
-	 */
-
-#if defined(DUK_USE_STRTAB_PROBE)
-#if defined(DUK_USE_HEAPPTR16)
-	res->strtable16 = (duk_uint16_t *) alloc_func(heap_udata, sizeof(duk_uint16_t) * DUK_STRTAB_INITIAL_SIZE);
-	if (!res->strtable16) {
-		goto error;
-	}
-#else  /* DUK_USE_HEAPPTR16 */
-	res->strtable = (duk_hstring **) alloc_func(heap_udata, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE);
-	if (!res->strtable) {
-		goto error;
-	}
-#endif  /* DUK_USE_HEAPPTR16 */
-	res->st_size = DUK_STRTAB_INITIAL_SIZE;
-#if defined(DUK_USE_EXPLICIT_NULL_INIT)
-	{
-		duk_small_uint_t i;
-		DUK_ASSERT(res->st_size == DUK_STRTAB_INITIAL_SIZE);
-	        for (i = 0; i < DUK_STRTAB_INITIAL_SIZE; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-			res->strtable16[i] = res->heapptr_null16;
-#else
-			res->strtable[i] = NULL;
-#endif
-	        }
-	}
-#else  /* DUK_USE_EXPLICIT_NULL_INIT */
-#if defined(DUK_USE_HEAPPTR16)
-	DUK_MEMZERO(res->strtable16, sizeof(duk_uint16_t) * DUK_STRTAB_INITIAL_SIZE);
-#else
-	DUK_MEMZERO(res->strtable, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE);
-#endif
-#endif  /* DUK_USE_EXPLICIT_NULL_INIT */
-#endif  /* DUK_USE_STRTAB_PROBE */
+#endif  /* DUK_USE_STRTAB_PTRCOMP */
 
 	/*
 	 *  Init stringcache
@@ -44473,30 +45776,40 @@
 	 *  Init built-in strings
 	 */
 
-	DUK_DD(DUK_DDPRINT("HEAP: INIT STRINGS"));
+#if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 2)
+	goto failed;
+#endif
+	DUK_D(DUK_DPRINT("heap init: initialize heap strings"));
 	if (!duk__init_heap_strings(res)) {
-		goto error;
+		goto failed;
 	}
 
 	/*
 	 *  Init the heap thread
 	 */
 
-	DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP THREAD"));
+#if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 3)
+	goto failed;
+#endif
+	DUK_D(DUK_DPRINT("heap init: initialize heap thread"));
 	if (!duk__init_heap_thread(res)) {
-		goto error;
+		goto failed;
 	}
 
 	/*
 	 *  Init the heap object
 	 */
 
-	DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP OBJECT"));
+#if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 4)
+	goto failed;
+#endif
+	DUK_D(DUK_DPRINT("heap init: initialize heap object"));
 	DUK_ASSERT(res->heap_thread != NULL);
-	res->heap_object = duk_hobject_alloc(res, DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                                          DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT));
-	if (!res->heap_object) {
-		goto error;
+	res->heap_object = duk_hobject_alloc_unchecked(res, DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                                    DUK_HOBJECT_FLAG_FASTREFS |
+	                                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT));
+	if (res->heap_object == NULL) {
+		goto failed;
 	}
 	DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object);
 
@@ -44543,24 +45856,50 @@
 #endif
 
 	/*
-	 *  All done
+	 *  Allow finalizer and mark-and-sweep processing.
+	 */
+
+	DUK_D(DUK_DPRINT("heap init: allow finalizer/mark-and-sweep processing"));
+	DUK_ASSERT(res->ms_prevent_count == 1);
+	DUK_ASSERT(res->pf_prevent_count == 1);
+	res->ms_prevent_count = 0;
+	res->pf_prevent_count = 0;
+	DUK_ASSERT(res->ms_running == 0);
+#if defined(DUK_USE_ASSERTIONS)
+	res->heap_initializing = 0;
+#endif
+
+	/*
+	 *  All done.
 	 */
 
 	DUK_D(DUK_DPRINT("allocated heap: %p", (void *) res));
 	return res;
 
- error:
+ failed:
 	DUK_D(DUK_DPRINT("heap allocation failed"));
 
-	if (res) {
-		/* assumes that allocated pointers and alloc funcs are valid
-		 * if res exists
-		 */
+	if (res != NULL) {
+		/* Assumes that allocated pointers and alloc funcs are valid
+		 * if res exists.
+		 */
+		DUK_ASSERT(res->ms_prevent_count == 1);
+		DUK_ASSERT(res->pf_prevent_count == 1);
+		DUK_ASSERT(res->ms_running == 0);
+		if (res->heap_thread != NULL) {
+			res->ms_prevent_count = 0;
+			res->pf_prevent_count = 0;
+		}
+#if defined(DUK_USE_ASSERTIONS)
+		res->heap_initializing = 0;
+#endif
+
 		DUK_ASSERT(res->alloc_func != NULL);
 		DUK_ASSERT(res->realloc_func != NULL);
 		DUK_ASSERT(res->free_func != NULL);
 		duk_heap_free(res);
 	}
+
 	return NULL;
 }
 
@@ -44571,6 +45910,456 @@
 #undef DUK__DUMPLM_UNSIGNED_RAW
 #undef DUK__DUMPSZ
 #undef DUK__FIXED_HASH_SEED
+#line 1 "duk_heap_finalize.c"
+/*
+ *  Finalizer handling.
+ */
+
+/* #include duk_internal.h -> already included */
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+
+/*
+ *  Fake torture finalizer.
+ */
+
+#if defined(DUK_USE_FINALIZER_TORTURE)
+DUK_LOCAL duk_ret_t duk__fake_global_finalizer(duk_context *ctx) {
+	DUK_DD(DUK_DDPRINT("fake global torture finalizer executed"));
+
+	/* Require a lot of stack to force a value stack grow/shrink. */
+	duk_require_stack(ctx, 100000);
+
+	/* Force a reallocation with pointer change for value, call, and
+	 * catch stacks to maximize side effects.
+	 */
+	duk_hthread_valstack_torture_realloc((duk_hthread *) ctx);
+	duk_hthread_callstack_torture_realloc((duk_hthread *) ctx);
+	duk_hthread_catchstack_torture_realloc((duk_hthread *) ctx);
+
+	/* Inner function call, error throw. */
+	duk_eval_string_noresult(ctx,
+		"(function dummy() {\n"
+		"    dummy.prototype = null;  /* break reference loop */\n"
+		"    try {\n"
+		"        throw 'fake-finalizer-dummy-error';\n"
+		"    } catch (e) {\n"
+		"        void e;\n"
+		"    }\n"
+		"})()");
+
+	/* The above creates garbage (e.g. a function instance).  Because
+	 * the function/prototype reference loop is broken, it gets collected
+	 * immediately by DECREF.  If Function.prototype has a _Finalizer
+	 * property (happens in some test cases), the garbage gets queued to
+	 * finalize_list.  This still won't cause an infinite loop because
+	 * the torture finalizer is called once per finalize_list run and
+	 * the garbage gets handled in the same run.  (If the garbage needs
+	 * mark-and-sweep collection, an infinite loop might ensue.)
+	 */
+	return 0;
+}
+
+DUK_LOCAL void duk__run_global_torture_finalizer(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+
+	/* Avoid fake finalization when callstack limit has been reached.
+	 * Otherwise a callstack limit error will be created, then refzero'ed.
+	 */
+	if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit ||
+	    thr->callstack_size + 2 * DUK_CALLSTACK_GROW_STEP >= thr->callstack_max /*approximate*/) {
+		DUK_D(DUK_DPRINT("skip global torture finalizer because of call recursion or call stack size limit"));
+		return;
+	}
+
+	/* Run fake finalizer.  Avoid creating unnecessary garbage. */
+	duk_push_c_function((duk_context *) thr, duk__fake_global_finalizer, 0 /*nargs*/);
+	(void) duk_pcall((duk_context *) thr, 0 /*nargs*/);
+	duk_pop((duk_context *) thr);
+}
+#endif  /* DUK_USE_FINALIZER_TORTURE */
+
+/*
+ *  Process the finalize_list to completion.
+ *
+ *  An object may be placed on finalize_list by either refcounting or
+ *  mark-and-sweep.  The refcount of objects placed by refcounting will be
+ *  zero; the refcount of objects placed by mark-and-sweep is > 0.  In both
+ *  cases the refcount is bumped by 1 artificially so that a REFZERO event
+ *  can never happen while an object is waiting for finalization.  Without
+ *  this bump a REFZERO could now happen because user code may call
+ *  duk_push_heapptr() and then pop a value even when it's on finalize_list.
+ *
+ *  List processing assumes refcounts are kept up-to-date at all times, so
+ *  that once the finalizer returns, a zero refcount is a reliable reason to
+ *  free the object immediately rather than place it back to the heap.  This
+ *  is the case because we run outside of refzero_list processing so that
+ *  DECREF cascades are handled fully inline.
+ *
+ *  For mark-and-sweep queued objects (had_zero_refcount false) the object
+ *  may be freed immediately if its refcount is zero after the finalizer call
+ *  (i.e. finalizer removed the reference loop for the object).  If not, the
+ *  next mark-and-sweep will collect the object unless it has become reachable
+ *  (i.e. rescued) by that time and its refcount hasn't fallen to zero before
+ *  that.  Mark-and-sweep detects these objects because their FINALIZED flag
+ *  is set.
+ *
+ *  There's an inherent limitation for mark-and-sweep finalizer rescuing: an
+ *  object won't get refinalized if (1) it's rescued, but (2) becomes
+ *  unreachable before mark-and-sweep has had time to notice it.  The next
+ *  mark-and-sweep round simply doesn't have any information of whether the
+ *  object has been unreachable the whole time or not (the only way to get
+ *  that information would be a mark-and-sweep pass for *every finalized
+ *  object*).  This is awkward for the application because the mark-and-sweep
+ *  round is not generally visible or under full application control.
+ *
+ *  For refcount queued objects (had_zero_refcount true) the object is either
+ *  immediately freed or rescued, and waiting for a mark-and-sweep round is not
+ *  necessary (or desirable); FINALIZED is cleared when a rescued object is
+ *  queued back to heap_allocated.  The object is eligible for finalization
+ *  again (either via refcounting or mark-and-sweep) immediately after being
+ *  rescued.  If a refcount finalized object is placed into an unreachable
+ *  reference loop by its finalizer, it will get collected by mark-and-sweep
+ *  and currently the finalizer will execute again.
+ *
+ *  There's a special case where:
+ *
+ *    - Mark-and-sweep queues an object to finalize_list for finalization.
+ *    - The finalizer is executed, FINALIZED is set, and object is queued
+ *      back to heap_allocated, waiting for a new mark-and-sweep round.
+ *    - The object's refcount drops to zero before mark-and-sweep has a
+ *      chance to run another round and make a rescue/free decision.
+ *
+ *  This is now handled by refzero code: if an object has a finalizer but
+ *  FINALIZED is already set, the object is freed without finalizer processing.
+ *  The outcome is the same as if mark-and-sweep was executed at that point;
+ *  mark-and-sweep would also free the object without another finalizer run.
+ *  This could also be changed so that the refzero-triggered finalizer *IS*
+ *  executed: being refzero collected implies someone has operated on the
+ *  object so it hasn't been totally unreachable the whole time.  This would
+ *  risk a finalizer loop however.
+ */
+
+DUK_INTERNAL void duk_heap_process_finalize_list(duk_heap *heap) {
+	duk_heaphdr *curr;
+#if defined(DUK_USE_DEBUG)
+	duk_size_t count = 0;
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("duk_heap_process_finalize_list: %p", (void *) heap));
+
+	if (heap->pf_prevent_count != 0) {
+		DUK_DDD(DUK_DDDPRINT("skip finalize_list processing: pf_prevent_count != 0"));
+		return;
+	}
+
+	/* Heap alloc prevents mark-and-sweep before heap_thread is ready. */
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+	DUK_ASSERT(heap->heap_thread->valstack != NULL);
+	DUK_ASSERT(heap->heap_thread->callstack != NULL);
+	DUK_ASSERT(heap->heap_thread->catchstack != NULL);
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	DUK_ASSERT(heap->refzero_list == NULL);
+#endif
+
+	DUK_ASSERT(heap->pf_prevent_count == 0);
+	heap->pf_prevent_count = 1;
+
+	/* Mark-and-sweep no longer needs to be prevented when running
+	 * finalizers: mark-and-sweep skips any rescue decisions if there
+	 * are any objects in finalize_list when mark-and-sweep is entered.
+	 * This protects finalized objects from incorrect rescue decisions
+	 * caused by finalize_list being a reachability root and only
+	 * partially processed.  Freeing decisions are not postponed.
+	 */
+
+	/* When finalizer torture is enabled, make a fake finalizer call with
+	 * maximum side effects regardless of whether finalize_list is empty.
+	 */
+#if defined(DUK_USE_FINALIZER_TORTURE)
+	duk__run_global_torture_finalizer(heap->heap_thread);
+#endif
+
+	/* Process finalize_list until it becomes empty.  There's currently no
+	 * protection against a finalizer always creating more garbage.
+	 */
+	while ((curr = heap->finalize_list) != NULL) {
+#if defined(DUK_USE_REFERENCE_COUNTING)
+		duk_bool_t queue_back;
+#endif
+
+		DUK_DD(DUK_DDPRINT("processing finalize_list entry: %p -> %!iO", (void *) curr, curr));
+
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);  /* Only objects have finalizers. */
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr));
+		DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE(curr));  /* All objects on finalize_list will have this flag (except object being finalized right now). */
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));   /* Queueing code ensures. */
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr));  /* ROM objects never get freed (or finalized). */
+
+#if defined(DUK_USE_ASSERTIONS)
+		DUK_ASSERT(heap->currently_finalizing == NULL);
+		heap->currently_finalizing = curr;
+#endif
+
+		/* Clear FINALIZABLE for object being finalized, so that
+		 * duk_push_heapptr() can properly ignore the object.
+		 */
+		DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
+
+		if (DUK_LIKELY(!heap->pf_skip_finalizers)) {
+			/* Run the finalizer, duk_heap_run_finalizer() sets
+			 * and checks for FINALIZED to prevent the finalizer
+			 * from executing multiple times per finalization cycle.
+			 * (This safeguard shouldn't be actually needed anymore).
+			 */
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			duk_bool_t had_zero_refcount;
+#endif
+
+			/* The object's refcount is >0 throughout so it won't be
+			 * refzero processed prematurely.
+			 */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) >= 1);
+			had_zero_refcount = (DUK_HEAPHDR_GET_REFCOUNT(curr) == 1);  /* Preincremented on finalize_list insert. */
+#endif
+
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
+			duk_heap_run_finalizer(heap, (duk_hobject *) curr);  /* must never longjmp */
+			DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(curr));
+			/* XXX: assert that object is still in finalize_list
+			 * when duk_push_heapptr() allows automatic rescue.
+			 */
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			DUK_DD(DUK_DDPRINT("refcount after finalizer (includes bump): %ld", (long) DUK_HEAPHDR_GET_REFCOUNT(curr)));
+			if (DUK_HEAPHDR_GET_REFCOUNT(curr) == 1) {  /* Only artificial bump in refcount? */
+#if defined(DUK_USE_DEBUG)
+				if (had_zero_refcount) {
+					DUK_DD(DUK_DDPRINT("finalized object's refcount is zero -> free immediately (refcount queued)"));
+				} else {
+					DUK_DD(DUK_DDPRINT("finalized object's refcount is zero -> free immediately (mark-and-sweep queued)"));
+				}
+#endif
+				queue_back = 0;
+			} else
+#endif
+			{
+#if defined(DUK_USE_REFERENCE_COUNTING)
+				queue_back = 1;
+				if (had_zero_refcount) {
+					/* When finalization is triggered
+					 * by refzero and we queue the object
+					 * back, clear FINALIZED right away
+					 * so that the object can be refinalized
+					 * immediately if necessary.
+					 */
+					DUK_HEAPHDR_CLEAR_FINALIZED(curr);
+				}
+#endif
+			}
+		} else {
+			/* Used during heap destruction: don't actually run finalizers
+			 * because we're heading into forced finalization.  Instead,
+			 * queue finalizable objects back to the heap_allocated list.
+			 */
+			DUK_D(DUK_DPRINT("skip finalizers flag set, queue object to heap_allocated without finalizing"));
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			queue_back = 1;
+#endif
+		}
+
+		/* Dequeue object from finalize_list.  Note that 'curr' may no
+		 * longer be finalize_list head because new objects may have
+		 * been queued to the list.  As a result we can't optimize for
+		 * the single-linked heap case and must scan the list for
+		 * removal, typically the scan is very short however.
+		 */
+		DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(heap, curr);
+
+		/* Queue back to heap_allocated or free immediately. */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+		if (queue_back) {
+			/* FINALIZED is only cleared if object originally
+			 * queued for finalization by refcounting.  For
+			 * mark-and-sweep FINALIZED is left set, so that
+			 * next mark-and-sweep round can make a rescue/free
+			 * decision.
+			 */
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) >= 1);
+			DUK_HEAPHDR_PREDEC_REFCOUNT(curr);  /* Remove artificial refcount bump. */
+			DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
+			DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr);
+		} else {
+			/* No need to remove the refcount bump here. */
+			DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);  /* currently, always the case */
+			DUK_DD(DUK_DDPRINT("refcount finalize after finalizer call: %!O", curr));
+			duk_hobject_refcount_finalize_norz(heap, (duk_hobject *) curr);
+			duk_free_hobject(heap, (duk_hobject *) curr);
+			DUK_DD(DUK_DDPRINT("freed hobject after finalization: %p", (void *) curr));
+		}
+#else  /* DUK_USE_REFERENCE_COUNTING */
+		DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
+		DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr);
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+#if defined(DUK_USE_DEBUG)
+		count++;
+#endif
+
+#if defined(DUK_USE_ASSERTIONS)
+		DUK_ASSERT(heap->currently_finalizing != NULL);
+		heap->currently_finalizing = NULL;
+#endif
+	}
+
+	/* finalize_list will always be processed completely. */
+	DUK_ASSERT(heap->finalize_list == NULL);
+
+#if 0
+	/* While NORZ macros are used above, this is unnecessary because the
+	 * only pending side effects are now finalizers, and finalize_list is
+	 * empty.
+	 */
+	DUK_REFZERO_CHECK_SLOW(heap->heap_thread);
+#endif
+
+	/* Prevent count may be bumped while finalizers run, but should always
+	 * be reliably unbumped by the time we get here.
+	 */
+	DUK_ASSERT(heap->pf_prevent_count == 1);
+	heap->pf_prevent_count = 0;
+
+#if defined(DUK_USE_DEBUG)
+	DUK_DD(DUK_DDPRINT("duk_heap_process_finalize_list: %ld finalizers called", (long) count));
+#endif
+}
+
+/*
+ *  Run an duk_hobject finalizer.  Must never throw an uncaught error
+ *  (but may throw caught errors).
+ *
+ *  There is no return value.  Any return value or error thrown by
+ *  the finalizer is ignored (although errors are debug logged).
+ *
+ *  Notes:
+ *
+ *    - The finalizer thread 'top' assertions are there because it is
+ *      critical that strict stack policy is observed (i.e. no cruft
+ *      left on the finalizer stack).
+ */
+
+DUK_LOCAL duk_ret_t duk__finalize_helper(duk_context *ctx, void *udata) {
+	duk_hthread *thr;
+
+	DUK_ASSERT(ctx != NULL);
+	thr = (duk_hthread *) ctx;
+	DUK_UNREF(udata);
+
+	DUK_DDD(DUK_DDDPRINT("protected finalization helper running"));
+
+	/* [... obj] */
+
+	/* _Finalizer property is read without checking if the value is
+	 * callable or even exists.  This is intentional, and handled
+	 * by throwing an error which is caught by the safe call wrapper.
+	 *
+	 * XXX: Finalizer lookup should traverse the prototype chain (to allow
+	 * inherited finalizers) but should not invoke accessors or proxy object
+	 * behavior.  At the moment this lookup will invoke proxy behavior, so
+	 * caller must ensure that this function is not called if the target is
+	 * a Proxy.
+	 */
+	duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_FINALIZER);  /* -> [... obj finalizer] */
+	duk_dup_m2(ctx);
+	duk_push_boolean(ctx, DUK_HEAP_HAS_FINALIZER_NORESCUE(thr->heap));
+	DUK_DDD(DUK_DDDPRINT("calling finalizer"));
+	duk_call(ctx, 2);  /* [ ... obj finalizer obj heapDestruct ]  -> [ ... obj retval ] */
+	DUK_DDD(DUK_DDDPRINT("finalizer returned successfully"));
+	return 0;
+
+	/* Note: we rely on duk_safe_call() to fix up the stack for the caller,
+	 * so we don't need to pop stuff here.  There is no return value;
+	 * caller determines rescued status based on object refcount.
+	 */
+}
+
+DUK_INTERNAL void duk_heap_run_finalizer(duk_heap *heap, duk_hobject *obj) {
+	duk_context *ctx;
+	duk_ret_t rc;
+#if defined(DUK_USE_ASSERTIONS)
+	duk_idx_t entry_top;
+#endif
+
+	DUK_DD(DUK_DDPRINT("running duk_hobject finalizer for object: %p", (void *) obj));
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+	ctx = (duk_context *) heap->heap_thread;
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT_VALSTACK_SPACE(heap->heap_thread, 1);
+
+#if defined(DUK_USE_ASSERTIONS)
+	entry_top = duk_get_top(ctx);
+#endif
+	/*
+	 *  Get and call the finalizer.  All of this must be wrapped
+	 *  in a protected call, because even getting the finalizer
+	 *  may trigger an error (getter may throw one, for instance).
+	 */
+
+	/* ROM objects could inherit a finalizer, but they are never deemed
+	 * unreachable by mark-and-sweep, and their refcount never falls to 0.
+	 */
+	DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj));
+
+	/* Duktape 2.1: finalize_list never contains objects with FINALIZED
+	 * set, so no need to check here.
+	 */
+	DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) obj));
+#if 0
+	if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) obj)) {
+		DUK_D(DUK_DPRINT("object already finalized, avoid running finalizer twice: %!O", obj));
+		return;
+	}
+#endif
+	DUK_HEAPHDR_SET_FINALIZED((duk_heaphdr *) obj);  /* ensure never re-entered until rescue cycle complete */
+
+	if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj)) {
+		/* This may happen if duk_set_finalizer() or Duktape.fin() is
+		 * called for a Proxy object.  In such cases the fast finalizer
+		 * flag will be set on the Proxy, not the target, and neither
+		 * will be finalized.
+		 */
+		DUK_D(DUK_DPRINT("object is a proxy, skip finalizer call"));
+		return;
+	}
+
+	duk_push_hobject(ctx, obj);  /* this also increases refcount by one */
+	rc = duk_safe_call(ctx, duk__finalize_helper, NULL /*udata*/, 0 /*nargs*/, 1 /*nrets*/);  /* -> [... obj retval/error] */
+	DUK_ASSERT_TOP(ctx, entry_top + 2);  /* duk_safe_call discipline */
+
+	if (rc != DUK_EXEC_SUCCESS) {
+		/* Note: we ask for one return value from duk_safe_call to get this
+		 * error debugging here.
+		 */
+		DUK_D(DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T",
+		                 (void *) obj, (duk_tval *) duk_get_tval(ctx, -1)));
+	}
+	duk_pop_2(ctx);  /* -> [...] */
+
+	DUK_ASSERT_TOP(ctx, entry_top);
+}
+
+#else  /* DUK_USE_FINALIZER_SUPPORT */
+
+/* nothing */
+
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
 #line 1 "duk_heap_hashstring.c"
 /*
  *  String hash computation (interning).
@@ -44704,22 +46493,7 @@
 DUK_LOCAL_DECL void duk__mark_tval(duk_heap *heap, duk_tval *tv);
 
 /*
- *  Misc
- */
-
-/* Select a thread for mark-and-sweep use.
- *
- * XXX: This needs to change later.
- */
-DUK_LOCAL duk_hthread *duk__get_temp_hthread(duk_heap *heap) {
-	if (heap->curr_thread) {
-		return heap->curr_thread;
-	}
-	return heap->heap_thread;  /* may be NULL, too */
-}
-
-/*
- *  Marking functions for heap types: mark children recursively
+ *  Marking functions for heap types: mark children recursively.
  */
 
 DUK_LOCAL void duk__mark_hstring(duk_heap *heap, duk_hstring *h) {
@@ -44743,7 +46517,7 @@
 
 	for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) {
 		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(heap, h, i);
-		if (!key) {
+		if (key == NULL) {
 			continue;
 		}
 		duk__mark_heaphdr(heap, (duk_heaphdr *) key);
@@ -44759,15 +46533,19 @@
 		duk__mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i));
 	}
 
-	/* hash part is a 'weak reference' and does not contribute */
+	/* Hash part is a 'weak reference' and does not contribute. */
 
 	duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(heap, h));
 
-	/* XXX: rearrange bits to allow a switch case to be used here? */
-	/* XXX: add a fast path for objects (and arrays)? */
-	/* DUK_HOBJECT_IS_ARRAY(h): needs no special handling now as there are
-	 * no extra fields in need of marking.
-	 */
+	/* Fast path for objects which don't have a subclass struct, or have a
+	 * subclass struct but nothing that needs marking in the subclass struct.
+	 */
+	if (DUK_HOBJECT_HAS_FASTREFS(h)) {
+		DUK_ASSERT(DUK_HOBJECT_ALLOWS_FASTREFS(h));
+		return;
+	}
+	DUK_ASSERT(DUK_HOBJECT_PROHIBITS_FASTREFS(h));
+
 	if (DUK_HOBJECT_IS_COMPFUNC(h)) {
 		duk_hcompfunc *f = (duk_hcompfunc *) h;
 		duk_tval *tv, *tv_end;
@@ -44799,16 +46577,21 @@
 			/* May happen in some out-of-memory corner cases. */
 			DUK_D(DUK_DPRINT("duk_hcompfunc 'data' is NULL, skipping marking"));
 		}
-	} else if (DUK_HOBJECT_IS_NATFUNC(h)) {
-		duk_hnatfunc *f = (duk_hnatfunc *) h;
-		DUK_UNREF(f);
-		/* nothing to mark */
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
 	} else if (DUK_HOBJECT_IS_BUFOBJ(h)) {
 		duk_hbufobj *b = (duk_hbufobj *) h;
 		duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf);
 		duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf_prop);
 #endif  /* DUK_USE_BUFFEROBJECT_SUPPORT */
+	} else if (DUK_HOBJECT_IS_DECENV(h)) {
+		duk_hdecenv *e = (duk_hdecenv *) h;
+		DUK_ASSERT_HDECENV_VALID(e);
+		duk__mark_heaphdr(heap, (duk_heaphdr *) e->thread);
+		duk__mark_heaphdr(heap, (duk_heaphdr *) e->varmap);
+	} else if (DUK_HOBJECT_IS_OBJENV(h)) {
+		duk_hobjenv *e = (duk_hobjenv *) h;
+		DUK_ASSERT_HOBJENV_VALID(e);
+		duk__mark_heaphdr(heap, (duk_heaphdr *) e->target);
 	} else if (DUK_HOBJECT_IS_THREAD(h)) {
 		duk_hthread *t = (duk_hthread *) h;
 		duk_tval *tv;
@@ -44837,19 +46620,25 @@
 
 		duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer);
 
-		/* XXX: duk_small_uint_t would be enough for this loop */
 		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
 			duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]);
 		}
-	}
-}
-
-/* recursion tracking happens here only */
+	} else {
+		/* We may come here if the object should have a FASTREFS flag
+		 * but it's missing for some reason.  Assert for never getting
+		 * here; however, other than performance, this is harmless.
+		 */
+		DUK_D(DUK_DPRINT("missing FASTREFS flag for: %!iO", h));
+		DUK_ASSERT(0);
+	}
+}
+
+/* Mark any duk_heaphdr type.  Recursion tracking happens only here. */
 DUK_LOCAL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h) {
 	DUK_DDD(DUK_DDDPRINT("duk__mark_heaphdr %p, type %ld",
 	                     (void *) h,
 	                     (h != NULL ? (long) DUK_HEAPHDR_GET_TYPE(h) : (long) -1)));
-	if (!h) {
+	if (h == NULL) {
 		return;
 	}
 #if defined(DUK_USE_ROM_OBJECTS)
@@ -44858,21 +46647,24 @@
 		return;
 	}
 #endif
+#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING)
+	h->h_assert_refcount++;  /* Comparison refcount: bump even if already reachable. */
+#endif
 	if (DUK_HEAPHDR_HAS_REACHABLE(h)) {
 		DUK_DDD(DUK_DDDPRINT("already marked reachable, skip"));
 		return;
 	}
 	DUK_HEAPHDR_SET_REACHABLE(h);
 
-	if (heap->mark_and_sweep_recursion_depth >= DUK_USE_MARK_AND_SWEEP_RECLIMIT) {
-		/* log this with a normal debug level because this should be relatively rare */
+	if (heap->ms_recursion_depth >= DUK_USE_MARK_AND_SWEEP_RECLIMIT) {
 		DUK_D(DUK_DPRINT("mark-and-sweep recursion limit reached, marking as temproot: %p", (void *) h));
 		DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap);
 		DUK_HEAPHDR_SET_TEMPROOT(h);
 		return;
 	}
 
-	heap->mark_and_sweep_recursion_depth++;
+	heap->ms_recursion_depth++;
+	DUK_ASSERT(heap->ms_recursion_depth != 0);  /* Wrap. */
 
 	switch (DUK_HEAPHDR_GET_TYPE(h)) {
 	case DUK_HTYPE_STRING:
@@ -44889,12 +46681,13 @@
 		DUK_UNREACHABLE();
 	}
 
-	heap->mark_and_sweep_recursion_depth--;
+	DUK_ASSERT(heap->ms_recursion_depth > 0);
+	heap->ms_recursion_depth--;
 }
 
 DUK_LOCAL void duk__mark_tval(duk_heap *heap, duk_tval *tv) {
 	DUK_DDD(DUK_DDDPRINT("duk__mark_tval %p", (void *) tv));
-	if (!tv) {
+	if (tv == NULL) {
 		return;
 	}
 	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
@@ -44930,37 +46723,12 @@
 }
 
 /*
- *  Mark refzero_list objects.
- *
- *  Objects on the refzero_list have no inbound references.  They might have
- *  outbound references to objects that we might free, which would invalidate
- *  any references held by the refzero objects.  A refzero object might also
- *  be rescued by refcount finalization.  Refzero objects are treated as
- *  reachability roots to ensure they (or anything they point to) are not
- *  freed in mark-and-sweep.
- */
-
-#if defined(DUK_USE_REFERENCE_COUNTING)
-DUK_LOCAL void duk__mark_refzero_list(duk_heap *heap) {
-	duk_heaphdr *hdr;
-
-	DUK_DD(DUK_DDPRINT("duk__mark_refzero_list: %p", (void *) heap));
-
-	hdr = heap->refzero_list;
-	while (hdr) {
-		duk__mark_heaphdr(heap, hdr);
-		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
-	}
-}
-#endif
-
-/*
  *  Mark unreachable, finalizable objects.
  *
- *  Such objects will be moved aside and their finalizers run later.  They have
- *  to be treated as reachability roots for their properties etc to remain
- *  allocated.  This marking is only done for unreachable values which would
- *  be swept later (refzero_list is thus excluded).
+ *  Such objects will be moved aside and their finalizers run later.  They
+ *  have to be treated as reachability roots for their properties etc to
+ *  remain allocated.  This marking is only done for unreachable values which
+ *  would be swept later.
  *
  *  Objects are first marked FINALIZABLE and only then marked as reachability
  *  roots; otherwise circular references might be handled inconsistently.
@@ -44968,32 +46736,30 @@
 
 #if defined(DUK_USE_FINALIZER_SUPPORT)
 DUK_LOCAL void duk__mark_finalizable(duk_heap *heap) {
-	duk_hthread *thr;
 	duk_heaphdr *hdr;
 	duk_size_t count_finalizable = 0;
 
 	DUK_DD(DUK_DDPRINT("duk__mark_finalizable: %p", (void *) heap));
 
-	thr = duk__get_temp_hthread(heap);
-	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
 
 	hdr = heap->heap_allocated;
-	while (hdr) {
-		/* A finalizer is looked up from the object and up its prototype chain
-		 * (which allows inherited finalizers).  A prototype loop must not cause
-		 * an error to be thrown here; duk_hobject_hasprop_raw() will ignore a
-		 * prototype loop silently and indicate that the property doesn't exist.
+	while (hdr != NULL) {
+		/* A finalizer is looked up from the object and up its
+		 * prototype chain (which allows inherited finalizers).
+		 * The finalizer is checked for using a duk_hobject flag
+		 * which is kept in sync with the presence and callability
+		 * of a _Finalizer hidden symbol.
 		 */
 
 		if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) &&
-		    DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_OBJECT &&
+		    DUK_HEAPHDR_IS_OBJECT(hdr) &&
 		    !DUK_HEAPHDR_HAS_FINALIZED(hdr) &&
-		    duk_hobject_hasprop_raw(thr, (duk_hobject *) hdr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
-
+		    DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) hdr)) {
 			/* heaphdr:
 			 *  - is not reachable
 			 *  - is an object
-			 *  - is not a finalized object
+			 *  - is not a finalized object waiting for rescue/keep decision
 			 *  - has a finalizer
 			 */
 
@@ -45003,7 +46769,7 @@
 			                   (void *) hdr));
 			DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr));
 			DUK_HEAPHDR_SET_FINALIZABLE(hdr);
-			count_finalizable ++;
+			count_finalizable++;
 		}
 
 		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
@@ -45017,7 +46783,7 @@
 	                   (long) count_finalizable));
 
 	hdr = heap->heap_allocated;
-	while (hdr) {
+	while (hdr != NULL) {
 		if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) {
 			duk__mark_heaphdr(heap, hdr);
 		}
@@ -45031,7 +46797,6 @@
 
 /*
  *  Mark objects on finalize_list.
- *
  */
 
 #if defined(DUK_USE_FINALIZER_SUPPORT)
@@ -45044,7 +46809,7 @@
 	DUK_DD(DUK_DDPRINT("duk__mark_finalize_list: %p", (void *) heap));
 
 	hdr = heap->finalize_list;
-	while (hdr) {
+	while (hdr != NULL) {
 		duk__mark_heaphdr(heap, hdr);
 		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
 #if defined(DUK_USE_DEBUG)
@@ -45064,15 +46829,18 @@
 /*
  *  Fallback marking handler if recursion limit is reached.
  *
- *  Iterates 'temproots' until recursion limit is no longer hit.  Note
- *  that temproots may reside either in heap allocated list or the
- *  refzero work list.  This is a slow scan, but guarantees that we
- *  finish with a bounded C stack.
- *
- *  Note that nodes may have been marked as temproots before this
- *  scan begun, OR they may have been marked during the scan (as
- *  we process nodes recursively also during the scan).  This is
- *  intended behavior.
+ *  Iterates 'temproots' until recursion limit is no longer hit.  Temproots
+ *  can be in heap_allocated or finalize_list; refzero_list is now always
+ *  empty for mark-and-sweep.  A temproot may occur in finalize_list now if
+ *  there are objects on the finalize_list and user code creates a reference
+ *  from an object in heap_allocated to the object in finalize_list (which is
+ *  now allowed), and it happened to coincide with the recursion depth limit.
+ *
+ *  This is a slow scan, but guarantees that we finish with a bounded C stack.
+ *
+ *  Note that nodes may have been marked as temproots before this scan begun,
+ *  OR they may have been marked during the scan (as we process nodes
+ *  recursively also during the scan).  This is intended behavior.
  */
 
 #if defined(DUK_USE_DEBUG)
@@ -45087,7 +46855,10 @@
 
 	DUK_DDD(DUK_DDDPRINT("found a temp root: %p", (void *) hdr));
 	DUK_HEAPHDR_CLEAR_TEMPROOT(hdr);
-	DUK_HEAPHDR_CLEAR_REACHABLE(hdr);  /* done so that duk__mark_heaphdr() works correctly */
+	DUK_HEAPHDR_CLEAR_REACHABLE(hdr);  /* Done so that duk__mark_heaphdr() works correctly. */
+#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING)
+	hdr->h_assert_refcount--;  /* Same node visited twice. */
+#endif
 	duk__mark_heaphdr(heap, hdr);
 
 #if defined(DUK_USE_DEBUG)
@@ -45121,9 +46892,8 @@
 			hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
 		}
 
-		/* must also check refzero_list */
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		hdr = heap->refzero_list;
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+		hdr = heap->finalize_list;
 		while (hdr) {
 #if defined(DUK_USE_DEBUG)
 			duk__handle_temproot(heap, hdr, &count);
@@ -45132,7 +46902,7 @@
 #endif
 			hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
 		}
-#endif  /* DUK_USE_REFERENCE_COUNTING */
+#endif
 
 #if defined(DUK_USE_DEBUG)
 		DUK_DD(DUK_DDPRINT("temproot mark heap scan processed %ld temp roots", (long) count));
@@ -45151,14 +46921,11 @@
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
 DUK_LOCAL void duk__finalize_refcounts(duk_heap *heap) {
-	duk_hthread *thr;
 	duk_heaphdr *hdr;
 
-	thr = duk__get_temp_hthread(heap);
-	DUK_ASSERT(thr != NULL);
-
-	DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p, hthread=%p",
-	                   (void *) heap, (void *) thr));
+	DUK_ASSERT(heap->heap_thread != NULL);
+
+	DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p", (void *) heap));
 
 	hdr = heap->heap_allocated;
 	while (hdr) {
@@ -45174,7 +46941,12 @@
 			 */
 
 			DUK_DDD(DUK_DDDPRINT("unreachable object, refcount finalize before sweeping: %p", (void *) hdr));
-			duk_heaphdr_refcount_finalize(thr, hdr);
+
+			/* Finalize using heap->heap_thread; DECREF has a
+			 * suppress check for mark-and-sweep which is based
+			 * on heap->ms_running.
+			 */
+			duk_heaphdr_refcount_finalize_norz(heap, hdr);
 		}
 
 		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
@@ -45183,28 +46955,7 @@
 #endif  /* DUK_USE_REFERENCE_COUNTING */
 
 /*
- *  Clear (reachable) flags of refzero work list.
- */
-
-#if defined(DUK_USE_REFERENCE_COUNTING)
-DUK_LOCAL void duk__clear_refzero_list_flags(duk_heap *heap) {
-	duk_heaphdr *hdr;
-
-	DUK_DD(DUK_DDPRINT("duk__clear_refzero_list_flags: %p", (void *) heap));
-
-	hdr = heap->refzero_list;
-	while (hdr) {
-		DUK_HEAPHDR_CLEAR_REACHABLE(hdr);
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
-		/* DUK_HEAPHDR_HAS_FINALIZED may or may not be set. */
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
-		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
-	}
-}
-#endif  /* DUK_USE_REFERENCE_COUNTING */
-
-/*
- *  Clear (reachable) flags of finalize_list
+ *  Clear (reachable) flags of finalize_list.
  *
  *  We could mostly do in the sweep phase when we move objects from the
  *  heap into the finalize_list.  However, if a finalizer run is skipped
@@ -45223,8 +46974,11 @@
 	hdr = heap->finalize_list;
 	while (hdr) {
 		DUK_HEAPHDR_CLEAR_REACHABLE(hdr);
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
+#if defined(DUK_USE_ASSERTIONS)
+		DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE(hdr) || \
+		           (heap->currently_finalizing == hdr));
+#endif
+		/* DUK_HEAPHDR_FLAG_FINALIZED may be set. */
 		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
 		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
 	}
@@ -45232,126 +46986,13 @@
 #endif  /* DUK_USE_FINALIZER_SUPPORT */
 
 /*
- *  Sweep stringtable
- */
-
-#if defined(DUK_USE_STRTAB_CHAIN)
-
-/* XXX: skip count_free w/o debug? */
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL void duk__sweep_string_chain16(duk_heap *heap, duk_uint16_t *slot, duk_size_t *count_keep, duk_size_t *count_free) {
-	duk_uint16_t h16 = *slot;
+ *  Sweep stringtable.
+ */
+
+DUK_LOCAL void duk__sweep_stringtable(duk_heap *heap, duk_size_t *out_count_keep) {
 	duk_hstring *h;
-	duk_uint16_t null16 = heap->heapptr_null16;
-
-	if (h16 == null16) {
-		/* nop */
-		return;
-	}
-	h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, h16);
-	DUK_ASSERT(h != NULL);
-
-	if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
-		DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h);
-		(*count_keep)++;
-	} else {
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
-#endif
-		/* deal with weak references first */
-		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
-		*slot = null16;
-
-		/* free inner references (these exist e.g. when external
-		 * strings are enabled)
-		 */
-		duk_free_hstring(heap, h);
-		(*count_free)++;
-	}
-}
-#else  /* DUK_USE_HEAPPTR16 */
-DUK_LOCAL void duk__sweep_string_chain(duk_heap *heap, duk_hstring **slot, duk_size_t *count_keep, duk_size_t *count_free) {
-	duk_hstring *h = *slot;
-
-	if (h == NULL) {
-		/* nop */
-		return;
-	}
-
-	if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
-		DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h);
-		(*count_keep)++;
-	} else {
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
-#endif
-		/* deal with weak references first */
-		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
-		*slot = NULL;
-
-		/* free inner references (these exist e.g. when external
-		 * strings are enabled)
-		 */
-		duk_free_hstring(heap, h);
-		(*count_free)++;
-	}
-}
-#endif  /* DUK_USE_HEAPPTR16 */
-
-DUK_LOCAL void duk__sweep_stringtable_chain(duk_heap *heap, duk_size_t *out_count_keep) {
-	duk_strtab_entry *e;
-	duk_uint_fast32_t i;
-	duk_size_t count_free = 0;
-	duk_size_t count_keep = 0;
-	duk_size_t j, n;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t *lst;
-#else
-	duk_hstring **lst;
-#endif
-
-	DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap));
-
-	/* Non-zero refcounts should not happen for unreachable strings,
-	 * because we refcount finalize all unreachable objects which
-	 * should have decreased unreachable string refcounts to zero
-	 * (even for cycles).
-	 */
-
-	for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
-		e = heap->strtable + i;
-		if (e->listlen == 0) {
-#if defined(DUK_USE_HEAPPTR16)
-			duk__sweep_string_chain16(heap, &e->u.str16, &count_keep, &count_free);
-#else
-			duk__sweep_string_chain(heap, &e->u.str, &count_keep, &count_free);
-#endif
-		} else {
-#if defined(DUK_USE_HEAPPTR16)
-			lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-#else
-			lst = e->u.strlist;
-#endif
-			for (j = 0, n = e->listlen; j < n; j++) {
-#if defined(DUK_USE_HEAPPTR16)
-				duk__sweep_string_chain16(heap, lst + j, &count_keep, &count_free);
-#else
-				duk__sweep_string_chain(heap, lst + j, &count_keep, &count_free);
-#endif
-			}
-		}
-	}
-
-	DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept",
-	                 (long) count_free, (long) count_keep));
-	*out_count_keep = count_keep;
-}
-#endif  /* DUK_USE_STRTAB_CHAIN */
-
-#if defined(DUK_USE_STRTAB_PROBE)
-DUK_LOCAL void duk__sweep_stringtable_probe(duk_heap *heap, duk_size_t *out_count_keep) {
-	duk_hstring *h;
-	duk_uint_fast32_t i;
+	duk_hstring *prev;
+	duk_uint32_t i;
 #if defined(DUK_USE_DEBUG)
 	duk_size_t count_free = 0;
 #endif
@@ -45359,64 +47000,71 @@
 
 	DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap));
 
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	if (heap->strtable16 == NULL) {
+#else
+	if (heap->strtable == NULL) {
+#endif
+		goto done;
+	}
+
 	for (i = 0; i < heap->st_size; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-		h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+		h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
 #else
 		h = heap->strtable[i];
 #endif
-		if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
-			continue;
-		} else if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
-			DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h);
-			count_keep++;
-			continue;
-		}
-
+		prev = NULL;
+		while (h != NULL) {
+			duk_hstring *next;
+			next = h->hdr.h_next;
+
+			if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
+				DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h);
+				count_keep++;
+				prev = h;
+			} else {
 #if defined(DUK_USE_DEBUG)
-		count_free++;
+				count_free++;
 #endif
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
-		/* Non-zero refcounts should not happen for unreachable strings,
-		 * because we refcount finalize all unreachable objects which
-		 * should have decreased unreachable string refcounts to zero
-		 * (even for cycles).
-		 */
-		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
-#endif
-
-		DUK_DDD(DUK_DDDPRINT("sweep string, not reachable: %p", (void *) h));
-
-		/* deal with weak references first */
-		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
-
-		/* remove the string (mark DELETED), could also call
-		 * duk_heap_string_remove() but that would be slow and
-		 * pointless because we already know the slot.
-		 */
-#if defined(DUK_USE_HEAPPTR16)
-		heap->strtable16[i] = heap->heapptr_deleted16;
-#else
-		heap->strtable[i] = DUK_STRTAB_DELETED_MARKER(heap);
-#endif
-
-		/* free inner references (these exist e.g. when external
-		 * strings are enabled) and the struct itself.
-		 */
-		duk_free_hstring(heap, (duk_hstring *) h);
-	}
-
+				/* Non-zero refcounts should not happen for unreachable strings,
+				 * because we refcount finalize all unreachable objects which
+				 * should have decreased unreachable string refcounts to zero
+				 * (even for cycles).
+				 */
+				DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
+#endif
+
+				/* Deal with weak references first. */
+				duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
+
+				/* Remove the string from the string table. */
+				duk_heap_strtable_unlink_prev(heap, (duk_hstring *) h, (duk_hstring *) prev);
+
+				/* Free inner references (these exist e.g. when external
+				 * strings are enabled) and the struct itself.
+				 */
+				duk_free_hstring(heap, (duk_hstring *) h);
+
+				/* Don't update 'prev'; it should be last string kept. */
+			}
+
+			h = next;
+		}
+	}
+
+ done:
 #if defined(DUK_USE_DEBUG)
 	DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept",
 	                 (long) count_free, (long) count_keep));
 #endif
 	*out_count_keep = count_keep;
 }
-#endif  /* DUK_USE_STRTAB_PROBE */
-
-/*
- *  Sweep heap
+
+/*
+ *  Sweep heap.
  */
 
 DUK_LOCAL void duk__sweep_heap(duk_heap *heap, duk_int_t flags, duk_size_t *out_count_keep) {
@@ -45445,66 +47093,63 @@
 
 		if (DUK_HEAPHDR_HAS_REACHABLE(curr)) {
 			/*
-			 *  Reachable object, keep
-			 */
-
-			DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr));
-
-			if (DUK_HEAPHDR_HAS_FINALIZABLE(curr)) {
-				/*
-				 *  If object has been marked finalizable, move it to the
-				 *  "to be finalized" work list.  It will be collected on
-				 *  the next mark-and-sweep if it is still unreachable
-				 *  after running the finalizer.
-				 */
-
+			 *  Reachable object:
+			 *    - If FINALIZABLE -> actually unreachable (but marked
+			 *      artificially reachable), queue to finalize_list.
+			 *    - If !FINALIZABLE but FINALIZED -> rescued after
+			 *      finalizer execution.
+			 *    - Otherwise just a normal, reachable object.
+			 *
+			 *  Objects which are kept are queued to heap_allocated
+			 *  tail (we're essentially filtering heap_allocated in
+			 *  practice).
+			 */
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+			if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZABLE(curr))) {
 				DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
 				DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
-				DUK_DDD(DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr));
-
-#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
-				if (heap->finalize_list) {
-					DUK_HEAPHDR_SET_PREV(heap, heap->finalize_list, curr);
-				}
-				DUK_HEAPHDR_SET_PREV(heap, curr, NULL);
-#endif
-				DUK_HEAPHDR_SET_NEXT(heap, curr, heap->finalize_list);
-				DUK_ASSERT_HEAPHDR_LINKS(heap, curr);
-				heap->finalize_list = curr;
+				DUK_DD(DUK_DDPRINT("sweep; reachable, finalizable --> move to finalize_list: %p", (void *) curr));
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+				DUK_HEAPHDR_PREINC_REFCOUNT(curr);  /* Bump refcount so that refzero never occurs when pending a finalizer call. */
+#endif
+				DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap, curr);
 #if defined(DUK_USE_DEBUG)
 				count_finalize++;
 #endif
-			} else {
-				/*
-				 *  Object will be kept; queue object back to heap_allocated (to tail)
-				 */
-
-				if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
-					/*
-					 *  Object's finalizer was executed on last round, and
-					 *  object has been happily rescued.
-					 */
-
+			}
+			else
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
+			{
+				if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZED(curr))) {
 					DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
 					DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
-					DUK_DD(DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr));
+
+					if (flags & DUK_MS_FLAG_POSTPONE_RESCUE) {
+						DUK_DD(DUK_DDPRINT("sweep; reachable, finalized, but postponing rescue decisions --> keep object (with FINALIZED set): %!iO", curr));
+						count_keep++;
+					} else {
+						DUK_DD(DUK_DDPRINT("sweep; reachable, finalized --> rescued after finalization: %p", (void *) curr));
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+						DUK_HEAPHDR_CLEAR_FINALIZED(curr);
+#endif
 #if defined(DUK_USE_DEBUG)
-					count_rescue++;
-#endif
+						count_rescue++;
+#endif
+					}
 				} else {
-					/*
-					 *  Plain, boring reachable object.
-					 */
-					DUK_DD(DUK_DDPRINT("keep object: %!iO", curr));
+					DUK_DD(DUK_DDPRINT("sweep; reachable --> keep: %!iO", curr));
 					count_keep++;
 				}
 
-				if (!heap->heap_allocated) {
+				if (prev != NULL) {
+					DUK_ASSERT(heap->heap_allocated != NULL);
+					DUK_HEAPHDR_SET_NEXT(heap, prev, curr);
+				} else {
+					DUK_ASSERT(heap->heap_allocated == NULL);
 					heap->heap_allocated = curr;
 				}
-				if (prev) {
-					DUK_HEAPHDR_SET_NEXT(heap, prev, curr);
-				}
 #if defined(DUK_USE_DOUBLE_LINKED_HEAP)
 				DUK_HEAPHDR_SET_PREV(heap, curr, prev);
 #endif
@@ -45514,20 +47159,22 @@
 			}
 
 			DUK_HEAPHDR_CLEAR_REACHABLE(curr);
-			DUK_HEAPHDR_CLEAR_FINALIZED(curr);
-			DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
-
+			/* Keep FINALIZED if set, used if rescue decisions are postponed. */
+			/* Keep FINALIZABLE for objects on finalize_list. */
 			DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
-			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
-			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
-
-			curr = next;
 		} else {
 			/*
-			 *  Unreachable object, free
-			 */
-
-			DUK_DDD(DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr));
+			 *  Unreachable object:
+			 *    - If FINALIZED, object was finalized but not
+			 *      rescued.  This doesn't affect freeing.
+			 *    - Otherwise normal unreachable object.
+			 *
+			 *  There's no guard preventing a FINALIZED object
+			 *  from being freed while finalizers execute: the
+			 *  artificial finalize_list reachability roots can't
+			 *  cause an incorrect free decision (but can cause
+			 *  an incorrect rescue decision).
+			 */
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
 			/* Non-zero refcounts should not happen because we refcount
@@ -45538,9 +47185,14 @@
 #endif
 			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
 
+#if defined(DUK_USE_DEBUG)
 			if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
-				DUK_DDD(DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr));
-			}
+				DUK_DD(DUK_DDPRINT("sweep; unreachable, finalized --> finalized object not rescued: %p", (void *) curr));
+			} else {
+				DUK_DD(DUK_DDPRINT("sweep; not reachable --> free: %p", (void *) curr));
+			}
+
+#endif
 
 			/* Note: object cannot be a finalizable unreachable object, as
 			 * they have been marked temporarily reachable for this round,
@@ -45551,17 +47203,18 @@
 			count_free++;
 #endif
 
-			/* weak refs should be handled here, but no weak refs for
+			/* Weak refs should be handled here, but no weak refs for
 			 * any non-string objects exist right now.
 			 */
 
-			/* free object and all auxiliary (non-heap) allocs */
+			/* Free object and all auxiliary (non-heap) allocs. */
 			duk_heap_free_heaphdr_raw(heap, curr);
-
-			curr = next;
-		}
-	}
-	if (prev) {
+		}
+
+		curr = next;
+	}
+
+	if (prev != NULL) {
 		DUK_HEAPHDR_SET_NEXT(heap, prev, NULL);
 	}
 	DUK_ASSERT_HEAPHDR_LINKS(heap, prev);
@@ -45574,81 +47227,6 @@
 }
 
 /*
- *  Run (object) finalizers in the "to be finalized" work list.
- */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-DUK_LOCAL void duk__run_object_finalizers(duk_heap *heap, duk_small_uint_t flags) {
-	duk_heaphdr *curr;
-	duk_heaphdr *next;
-#if defined(DUK_USE_DEBUG)
-	duk_size_t count = 0;
-#endif
-	duk_hthread *thr;
-
-	DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap));
-
-	thr = duk__get_temp_hthread(heap);
-	DUK_ASSERT(thr != NULL);
-
-	curr = heap->finalize_list;
-	while (curr) {
-		DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr));
-
-		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);  /* only objects have finalizers */
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));                /* flags have been already cleared */
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr));
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr));  /* No finalizers for ROM objects */
-
-		/* Keep heap->finalize_list up-to-date during the list walk.
-		 * This has no functional impact, but does matter e.g. for
-		 * duk_push_heapptr() asserts when assertions are enabled.
-		 */
-		heap->finalize_list = curr;
-
-		if (DUK_LIKELY((flags & DUK_MS_FLAG_SKIP_FINALIZERS) == 0)) {
-			/* Run the finalizer, duk_hobject_run_finalizer() sets FINALIZED.
-			 * Next mark-and-sweep will collect the object unless it has
-			 * become reachable (i.e. rescued).  FINALIZED prevents the
-			 * finalizer from being executed again before that.
-			 */
-			duk_hobject_run_finalizer(thr, (duk_hobject *) curr);  /* must never longjmp */
-			DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(curr));
-
-			/* XXX: could clear FINALIZED already here; now cleared in
-			 * next mark-and-sweep.
-			 */
-		} else {
-			/* Used during heap destruction: don't actually run finalizers
-			 * because we're heading into forced finalization.  Instead,
-			 * queue finalizable objects back to the heap_allocated list.
-			 */
-			DUK_D(DUK_DPRINT("skip finalizers flag set, queue object to heap_allocated without finalizing"));
-			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
-		}
-
-		/* queue back to heap_allocated */
-		next = DUK_HEAPHDR_GET_NEXT(heap, curr);
-		DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr);
-
-		curr = next;
-#if defined(DUK_USE_DEBUG)
-		count++;
-#endif
-	}
-
-	/* finalize_list will always be processed completely */
-	heap->finalize_list = NULL;
-
-#if defined(DUK_USE_DEBUG)
-	DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %ld finalizers called", (long) count));
-#endif
-}
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
-
-/*
  *  Object compaction.
  *
  *  Compaction is assumed to never throw an error.
@@ -45723,25 +47301,24 @@
 	duk_size_t count_compact = 0;
 	duk_size_t count_bytes_saved = 0;
 #endif
-	duk_hthread *thr;
 
 	DUK_DD(DUK_DDPRINT("duk__compact_objects: %p", (void *) heap));
 
-	thr = duk__get_temp_hthread(heap);
-	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
 
 #if defined(DUK_USE_DEBUG)
-	duk__compact_object_list(heap, thr, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved);
-	duk__compact_object_list(heap, thr, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved);
+	duk__compact_object_list(heap, heap->heap_thread, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved);
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	duk__compact_object_list(heap, heap->heap_thread, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved);
+#endif
+#else
+	duk__compact_object_list(heap, heap->heap_thread, heap->heap_allocated);
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	duk__compact_object_list(heap, heap->heap_thread, heap->finalize_list);
+#endif
+#endif
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	duk__compact_object_list(heap, thr, heap->refzero_list, &count_check, &count_compact, &count_bytes_saved);
-#endif
-#else
-	duk__compact_object_list(heap, thr, heap->heap_allocated);
-	duk__compact_object_list(heap, thr, heap->finalize_list);
-#if defined(DUK_USE_REFERENCE_COUNTING)
-	duk__compact_object_list(heap, thr, heap->refzero_list);
-#endif
+	DUK_ASSERT(heap->refzero_list == NULL);  /* Always handled to completion inline in DECREF. */
 #endif
 
 #if defined(DUK_USE_DEBUG)
@@ -45768,170 +47345,189 @@
 	}
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	hdr = heap->refzero_list;
-	while (hdr) {
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
-		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
-		/* DUK_HEAPHDR_HAS_FINALIZED may be set if we're doing a
-		 * refzero finalization and mark-and-sweep gets triggered
-		 * during the finalizer.
-		 */
-		/* DUK_HEAPHDR_HAS_FINALIZED may or may not be set. */
-		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
-	}
-#endif  /* DUK_USE_REFERENCE_COUNTING */
+	DUK_ASSERT(heap->refzero_list == NULL);  /* Always handled to completion inline in DECREF. */
+#endif
 }
 
 #if defined(DUK_USE_REFERENCE_COUNTING)
 DUK_LOCAL void duk__assert_valid_refcounts(duk_heap *heap) {
 	duk_heaphdr *hdr = heap->heap_allocated;
 	while (hdr) {
+		/* Cannot really assert much w.r.t. refcounts now. */
+
 		if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0 &&
 		    DUK_HEAPHDR_HAS_FINALIZED(hdr)) {
 			/* An object may be in heap_allocated list with a zero
 			 * refcount if it has just been finalized and is waiting
 			 * to be collected by the next cycle.
+			 * (This doesn't currently happen however.)
 			 */
 		} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0) {
 			/* An object may be in heap_allocated list with a zero
-			 * refcount also if it is a temporary object created by
-			 * a finalizer; because finalization now runs inside
-			 * mark-and-sweep, such objects will not be queued to
-			 * refzero_list and will thus appear here with refcount
-			 * zero.
-			 */
-#if 0  /* this case can no longer occur because refcount is unsigned */
-		} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) < 0) {
-			DUK_D(DUK_DPRINT("invalid refcount: %ld, %p -> %!O",
-			                 (hdr != NULL ? (long) DUK_HEAPHDR_GET_REFCOUNT(hdr) : (long) 0),
-			                 (void *) hdr, (duk_heaphdr *) hdr));
-			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(hdr) > 0);
-#endif
-		}
+			 * refcount also if it is a temporary object created
+			 * during debugger paused state.  It will get collected
+			 * by mark-and-sweep based on its reachability status
+			 * (presumably not reachable because refcount is 0).
+			 */
+		}
+		DUK_ASSERT_DISABLE(DUK_HEAPHDR_GET_REFCOUNT(hdr) >= 0);  /* Unsigned. */
 		hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
 	}
 }
+
+DUK_LOCAL void duk__clear_assert_refcounts(duk_heap *heap) {
+	duk_heaphdr *curr;
+	duk_uint32_t i;
+
+	for (curr = heap->heap_allocated; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) {
+		curr->h_assert_refcount = 0;
+	}
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	for (curr = heap->finalize_list; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) {
+		curr->h_assert_refcount = 0;
+	}
+#endif
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	for (curr = heap->refzero_list; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) {
+		curr->h_assert_refcount = 0;
+	}
+#endif
+
+	for (i = 0; i < heap->st_size; i++) {
+		duk_hstring *h;
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+		h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
+#else
+		h = heap->strtable[i];
+#endif
+		while (h != NULL) {
+			((duk_heaphdr *) h)->h_assert_refcount = 0;
+			h = h->hdr.h_next;
+		}
+	}
+}
+
+DUK_LOCAL void duk__check_refcount_heaphdr(duk_heaphdr *hdr) {
+	duk_bool_t count_ok;
+
+	/* The refcount check only makes sense for reachable objects on
+	 * heap_allocated or string table, after the sweep phase.  Prior to
+	 * sweep phase refcounts will include references that are not visible
+	 * via reachability roots.
+	 *
+	 * Because we're called after the sweep phase, all heap objects on
+	 * heap_allocated are reachable.  REACHABLE flags have already been
+	 * cleared so we can't check them.
+	 */
+
+	/* ROM objects have intentionally incorrect refcount (1), but we won't
+	 * check them.
+	 */
+	DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr));
+
+	count_ok = ((duk_size_t) DUK_HEAPHDR_GET_REFCOUNT(hdr) == hdr->h_assert_refcount);
+	if (!count_ok) {
+		DUK_D(DUK_DPRINT("refcount mismatch for: %p: header=%ld counted=%ld --> %!iO",
+		                 (void *) hdr, (long) DUK_HEAPHDR_GET_REFCOUNT(hdr),
+		                 (long) hdr->h_assert_refcount, hdr));
+		DUK_ASSERT(0);
+	}
+}
+
+DUK_LOCAL void duk__check_assert_refcounts(duk_heap *heap) {
+	duk_heaphdr *curr;
+	duk_uint32_t i;
+
+	for (curr = heap->heap_allocated; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) {
+		duk__check_refcount_heaphdr(curr);
+	}
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	for (curr = heap->finalize_list; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) {
+		duk__check_refcount_heaphdr(curr);
+	}
+#endif
+
+	for (i = 0; i < heap->st_size; i++) {
+		duk_hstring *h;
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+		h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
+#else
+		h = heap->strtable[i];
+#endif
+		while (h != NULL) {
+			duk__check_refcount_heaphdr((duk_heaphdr *) h);
+			h = h->hdr.h_next;
+		}
+	}
+}
 #endif  /* DUK_USE_REFERENCE_COUNTING */
 #endif  /* DUK_USE_ASSERTIONS */
 
 /*
- *  Finalizer torture.  Do one fake finalizer call which causes side effects
- *  similar to one or more finalizers on actual objects.
- */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-#if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE)
-DUK_LOCAL duk_ret_t duk__markandsweep_fake_finalizer(duk_context *ctx) {
-	DUK_D(DUK_DPRINT("fake mark-and-sweep torture finalizer executed"));
-
-	/* Require a lot of stack to force a value stack grow/shrink.
-	 * Recursive mark-and-sweep is prevented by allocation macros
-	 * so this won't trigger another mark-and-sweep.
-	 */
-	duk_require_stack(ctx, 100000);
-
-	/* XXX: do something to force a callstack grow/shrink, perhaps
-	 * just a manual forced resize or a forced relocating realloc?
-	 */
-
-	return 0;
-}
-
-DUK_LOCAL void duk__markandsweep_torture_finalizer(duk_hthread *thr) {
-	duk_context *ctx;
-	duk_int_t rc;
-
-	DUK_ASSERT(thr != NULL);
-	ctx = (duk_context *) thr;
-
-	/* Avoid fake finalization when callstack limit has been reached.
-	 * Otherwise a callstack limit error will be created, then refzero'ed.
-	 */
-	if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit ||
-	    thr->callstack_size + 2 * DUK_CALLSTACK_GROW_STEP >= thr->callstack_max /*approximate*/) {
-		DUK_D(DUK_DPRINT("call recursion depth reached, avoid fake mark-and-sweep torture finalizer"));
-		return;
-	}
-
-	/* Run fake finalizer.  Avoid creating unnecessary garbage. */
-	duk_push_c_function(ctx, duk__markandsweep_fake_finalizer, 0 /*nargs*/);
-	rc = duk_pcall(ctx, 0 /*nargs*/);
-	DUK_UNREF(rc);  /* ignored */
-	duk_pop(ctx);
-}
-#endif  /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
-
-/*
  *  Main mark-and-sweep function.
  *
  *  'flags' represents the features requested by the caller.  The current
- *  heap->mark_and_sweep_base_flags is ORed automatically into the flags;
- *  the base flags mask typically prevents certain mark-and-sweep operations
- *  to avoid trouble.
- */
-
-DUK_INTERNAL duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) {
-	duk_hthread *thr;
+ *  heap->ms_base_flags is ORed automatically into the flags; the base flags
+ *  mask typically prevents certain mark-and-sweep operation to avoid trouble.
+ */
+
+DUK_INTERNAL void duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) {
 	duk_size_t count_keep_obj;
 	duk_size_t count_keep_str;
 #if defined(DUK_USE_VOLUNTARY_GC)
 	duk_size_t tmp;
 #endif
 
-	/* XXX: thread selection for mark-and-sweep is currently a hack.
-	 * If we don't have a thread, the entire mark-and-sweep is now
-	 * skipped (although we could just skip finalizations).
-	 */
-
-	/* If thr != NULL, the thr may still be in the middle of
-	 * initialization.
-	 * XXX: Improve the thread viability test.
-	 */
-	thr = duk__get_temp_hthread(heap);
-	if (thr == NULL) {
-		DUK_D(DUK_DPRINT("gc skipped because we don't have a temp thread"));
-
-		/* reset voluntary gc trigger count */
-#if defined(DUK_USE_VOLUNTARY_GC)
-		heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP;
-#endif
-		return 0;  /* OK */
-	}
-
-	/* If debugger is paused, garbage collection is disabled by default. */
-	/* XXX: will need a force flag if garbage collection is triggered
-	 * explicitly during paused state.
-	 */
-#if defined(DUK_USE_DEBUGGER_SUPPORT)
-	if (DUK_HEAP_IS_PAUSED(heap)) {
-		/* Checking this here rather that in memory alloc primitives
-		 * reduces checking code there but means a failed allocation
-		 * will go through a few retries before giving up.  That's
-		 * fine because this only happens during debugging.
-		 */
-		DUK_D(DUK_DPRINT("gc skipped because debugger is paused"));
-		return 0;
-	}
-#endif
+	/* If debugger is paused, garbage collection is disabled by default.
+	 * This is achieved by bumping ms_prevent_count when becoming paused.
+	 */
+	DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(heap) || heap->ms_prevent_count > 0);
+
+	/* Prevention/recursion check as soon as possible because we may
+	 * be called a number of times when voluntary mark-and-sweep is
+	 * pending.
+	 */
+	if (heap->ms_prevent_count != 0) {
+		DUK_DD(DUK_DDPRINT("reject recursive mark-and-sweep"));
+		return;
+	}
+	DUK_ASSERT(heap->ms_running == 0);  /* ms_prevent_count is bumped when ms_running is set */
+
+	/* Heap_thread is used during mark-and-sweep for refcount finalization
+	 * (it's also used for finalizer execution once mark-and-sweep is
+	 * complete).  Heap allocation code ensures heap_thread is set and
+	 * properly initialized before setting ms_prevent_count to 0.
+	 */
+	DUK_ASSERT(heap->heap_thread != NULL);
+	DUK_ASSERT(heap->heap_thread->valstack != NULL);
+	DUK_ASSERT(heap->heap_thread->callstack != NULL);
+	DUK_ASSERT(heap->heap_thread->catchstack != NULL);
 
 	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08lx, effective flags: 0x%08lx",
-	                 (unsigned long) flags, (unsigned long) (flags | heap->mark_and_sweep_base_flags)));
-
-	flags |= heap->mark_and_sweep_base_flags;
+	                 (unsigned long) flags, (unsigned long) (flags | heap->ms_base_flags)));
+
+	flags |= heap->ms_base_flags;
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	if (heap->finalize_list != NULL) {
+		flags |= DUK_MS_FLAG_POSTPONE_RESCUE;
+	}
+#endif
 
 	/*
 	 *  Assertions before
 	 */
 
 #if defined(DUK_USE_ASSERTIONS)
-	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
+	DUK_ASSERT(heap->ms_prevent_count == 0);
+	DUK_ASSERT(heap->ms_running == 0);
+	DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(heap));
 	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
-	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
+	DUK_ASSERT(heap->ms_recursion_depth == 0);
 	duk__assert_heaphdr_flags(heap);
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
+	/* Note: heap->refzero_free_running may be true; a refcount
 	 * finalizer may trigger a mark-and-sweep.
 	 */
 	duk__assert_valid_refcounts(heap);
@@ -45942,7 +47538,10 @@
 	 *  Begin
 	 */
 
-	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);
+	DUK_ASSERT(heap->ms_prevent_count == 0);
+	DUK_ASSERT(heap->ms_running == 0);
+	heap->ms_prevent_count = 1;
+	heap->ms_running = 1;
 
 	/*
 	 *  Mark roots, hoping that recursion limit is not normally hit.
@@ -45960,17 +47559,20 @@
 	 *  previous run had finalizer skip flag.
 	 */
 
-	duk__mark_roots_heap(heap);               /* main reachability roots */
+#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING)
+	duk__clear_assert_refcounts(heap);
+#endif
+	duk__mark_roots_heap(heap);               /* Mark main reachability roots. */
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	duk__mark_refzero_list(heap);             /* refzero_list treated as reachability roots */
-#endif
-	duk__mark_temproots_by_heap_scan(heap);   /* temproots */
+	DUK_ASSERT(heap->refzero_list == NULL);   /* Always handled to completion inline in DECREF. */
+#endif
+	duk__mark_temproots_by_heap_scan(heap);   /* Temproots. */
 
 #if defined(DUK_USE_FINALIZER_SUPPORT)
-	duk__mark_finalizable(heap);              /* mark finalizable as reachability roots */
-	duk__mark_finalize_list(heap);            /* mark finalizer work list as reachability roots */
-#endif
-	duk__mark_temproots_by_heap_scan(heap);   /* temproots */
+	duk__mark_finalizable(heap);              /* Mark finalizable as reachability roots. */
+	duk__mark_finalize_list(heap);            /* Mark finalizer work list as reachability roots. */
+#endif
+	duk__mark_temproots_by_heap_scan(heap);   /* Temproots. */
 
 	/*
 	 *  Sweep garbage and remove marking flags, and move objects with
@@ -45992,15 +47594,12 @@
 	duk__finalize_refcounts(heap);
 #endif
 	duk__sweep_heap(heap, flags, &count_keep_obj);
-#if defined(DUK_USE_STRTAB_CHAIN)
-	duk__sweep_stringtable_chain(heap, &count_keep_str);
-#elif defined(DUK_USE_STRTAB_PROBE)
-	duk__sweep_stringtable_probe(heap, &count_keep_str);
-#else
-#error internal error, invalid strtab options
+	duk__sweep_stringtable(heap, &count_keep_str);
+#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING)
+	duk__check_assert_refcounts(heap);
 #endif
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	duk__clear_refzero_list_flags(heap);
+	DUK_ASSERT(heap->refzero_list == NULL);   /* Always handled to completion inline in DECREF. */
 #endif
 #if defined(DUK_USE_FINALIZER_SUPPORT)
 	duk__clear_finalize_list_flags(heap);
@@ -46031,94 +47630,39 @@
 	/*
 	 *  String table resize check.
 	 *
-	 *  Note: this may silently (and safely) fail if GC is caused by an
-	 *  allocation call in stringtable resize_hash().  Resize_hash()
-	 *  will prevent a recursive call to itself by setting the
-	 *  DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags.
-	 */
-
-	/* XXX: stringtable emergency compaction? */
-
-	/* XXX: remove this feature entirely? it would only matter for
-	 * emergency GC.  Disable for lowest memory builds.
-	 */
-#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
-	if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) {
-		DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap));
-		duk_heap_force_strtab_resize(heap);
-	} else {
-		DUK_D(DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set"));
-	}
-#endif
-
-	/*
-	 *  Finalize objects in the finalization work list.  Finalized
-	 *  objects are queued back to heap_allocated with FINALIZED set.
-	 *
-	 *  Since finalizers may cause arbitrary side effects, they are
-	 *  prevented during string table and object property allocation
-	 *  resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in
-	 *  heap->mark_and_sweep_base_flags.  In this case the objects
-	 *  remain in the finalization work list after mark-and-sweep
-	 *  exits and they may be finalized on the next pass.
-	 *
-	 *  Finalization currently happens inside "MARKANDSWEEP_RUNNING"
-	 *  protection (no mark-and-sweep may be triggered by the
-	 *  finalizers).  As a side effect:
-	 *
-	 *    1) an out-of-memory error inside a finalizer will not
-	 *       cause a mark-and-sweep and may cause the finalizer
-	 *       to fail unnecessarily
-	 *
-	 *    2) any temporary objects whose refcount decreases to zero
-	 *       during finalization will not be put into refzero_list;
-	 *       they can only be collected by another mark-and-sweep
-	 *
-	 *  This is not optimal, but since the sweep for this phase has
-	 *  already happened, this is probably good enough for now.
-	 */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-#if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE)
-	/* Cannot simulate individual finalizers because finalize_list only
-	 * contains objects with actual finalizers.  But simulate side effects
-	 * from finalization by doing a bogus function call and resizing the
-	 * stacks.
-	 */
-	if (flags & DUK_MS_FLAG_NO_FINALIZERS) {
-		DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, DUK_MS_FLAG_NO_FINALIZERS is set"));
-	} else if (!(thr->valstack != NULL && thr->callstack != NULL && thr->catchstack != NULL)) {
-		DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, thread not yet viable"));
-	} else {
-		DUK_D(DUK_DPRINT("run mark-and-sweep torture finalizer"));
-		duk__markandsweep_torture_finalizer(thr);
-	}
-#endif  /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */
-
-	if (flags & DUK_MS_FLAG_NO_FINALIZERS) {
-		DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set"));
-	} else {
-		duk__run_object_finalizers(heap, flags);
-	}
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
+	 *  This is mainly useful in emergency GC: if the string table load
+	 *  factor is really low for some reason, we can shrink the string
+	 *  table to a smaller size and free some memory in the process.
+	 *  Only execute in emergency GC.  String table has internal flags
+	 *  to protect against recursive resizing if this mark-and-sweep pass
+	 *  was triggered by a string table resize.
+	 */
+
+	if (flags & DUK_MS_FLAG_EMERGENCY) {
+		DUK_D(DUK_DPRINT("stringtable resize check in emergency gc"));
+		duk_heap_strtable_force_resize(heap);
+	}
 
 	/*
 	 *  Finish
 	 */
 
-	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);
+	DUK_ASSERT(heap->ms_prevent_count == 1);
+	heap->ms_prevent_count = 0;
+	DUK_ASSERT(heap->ms_running == 1);
+	heap->ms_running = 0;
 
 	/*
 	 *  Assertions after
 	 */
 
 #if defined(DUK_USE_ASSERTIONS)
-	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
+	DUK_ASSERT(heap->ms_prevent_count == 0);
 	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
-	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
+	DUK_ASSERT(heap->ms_recursion_depth == 0);
 	duk__assert_heaphdr_flags(heap);
 #if defined(DUK_USE_REFERENCE_COUNTING)
-	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
+	/* Note: heap->refzero_free_running may be true; a refcount
 	 * finalizer may trigger a mark-and-sweep.
 	 */
 	duk__assert_valid_refcounts(heap);
@@ -46131,17 +47675,49 @@
 
 #if defined(DUK_USE_VOLUNTARY_GC)
 	tmp = (count_keep_obj + count_keep_str) / 256;
-	heap->mark_and_sweep_trigger_counter = (duk_int_t) (
+	heap->ms_trigger_counter = (duk_int_t) (
 	    (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) +
 	    DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD);
 	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, trigger reset to %ld",
-	                 (long) count_keep_obj, (long) count_keep_str, (long) heap->mark_and_sweep_trigger_counter));
+	                 (long) count_keep_obj, (long) count_keep_str, (long) heap->ms_trigger_counter));
 #else
 	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, no voluntary trigger",
 	                 (long) count_keep_obj, (long) count_keep_str));
 #endif
 
-	return 0;  /* OK */
+	/*
+	 *  Finalize objects in the finalization work list.  Finalized
+	 *  objects are queued back to heap_allocated with FINALIZED set.
+	 *
+	 *  Since finalizers may cause arbitrary side effects, they are
+	 *  prevented e.g. during string table and object property allocation
+	 *  resizing using heap->pf_prevent_count.  In this case the objects
+	 *  remain in the finalization work list after mark-and-sweep exits
+	 *  and they may be finalized on the next pass or any DECREF checking
+	 *  for finalize_list.
+	 *
+	 *  As of Duktape 2.1 finalization happens outside mark-and-sweep
+	 *  protection.  Mark-and-sweep is allowed while the finalize_list
+	 *  is being processed, but no rescue decisions are done while the
+	 *  process is on-going.  This avoids incorrect rescue decisions
+	 *  if an object is considered reachable (and thus rescued) because
+	 *  of a reference via finalize_list (which is considered a reachability
+	 *  root).  When finalize_list is being processed, reachable objects
+	 *  with FINALIZED set will just keep their FINALIZED flag for later
+	 *  mark-and-sweep processing.
+	 *
+	 *  This could also be handled (a bit better) by having a more refined
+	 *  notion of reachability for rescue/free decisions.
+	 *
+	 *  XXX: avoid finalizer execution when doing emergency GC?
+	 */
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	/* Attempt to process finalize_list, pf_prevent_count check
+	 * is inside the target.
+	 */
+	duk_heap_process_finalize_list(heap);
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
 }
 #line 1 "duk_heap_memory.c"
 /*
@@ -46151,34 +47727,28 @@
 /* #include duk_internal.h -> already included */
 
 /*
- *  Helpers
- *
- *  The fast path checks are done within a macro to ensure "inlining"
- *  while the slow path actions use a helper (which won't typically be
- *  inlined in size optimized builds).
+ *  Voluntary GC check
  */
 
 #if defined(DUK_USE_VOLUNTARY_GC)
-#define DUK__VOLUNTARY_PERIODIC_GC(heap)  do { \
-		(heap)->mark_and_sweep_trigger_counter--; \
-		if ((heap)->mark_and_sweep_trigger_counter <= 0) { \
-			duk__run_voluntary_gc(heap); \
-		} \
-	} while (0)
-
-DUK_LOCAL void duk__run_voluntary_gc(duk_heap *heap) {
-	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
-		DUK_DD(DUK_DDPRINT("mark-and-sweep in progress -> skip voluntary mark-and-sweep now"));
-	} else {
-		duk_small_uint_t flags;
-		duk_bool_t rc;
-
-		DUK_D(DUK_DPRINT("triggering voluntary mark-and-sweep"));
-		flags = 0;
-		rc = duk_heap_mark_and_sweep(heap, flags);
-		DUK_UNREF(rc);
-	}
-}
+DUK_LOCAL DUK_INLINE void duk__check_voluntary_gc(duk_heap *heap) {
+	if (DUK_UNLIKELY(--(heap)->ms_trigger_counter < 0)) {
+#if defined(DUK_USE_DEBUG)
+		if (heap->ms_prevent_count == 0) {
+			DUK_D(DUK_DPRINT("triggering voluntary mark-and-sweep"));
+		} else {
+			DUK_DD(DUK_DDPRINT("gc blocked -> skip voluntary mark-and-sweep now"));
+		}
+#endif
+
+		/* Prevention checks in the call target handle cases where
+		 * voluntary GC is not allowed.  The voluntary GC trigger
+		 * counter is only rewritten if mark-and-sweep actually runs.
+		 */
+		duk_heap_mark_and_sweep(heap, DUK_MS_FLAG_VOLUNTARY /*flags*/);
+	}
+}
+#define DUK__VOLUNTARY_PERIODIC_GC(heap)  do { duk__check_voluntary_gc((heap)); } while (0)
 #else
 #define DUK__VOLUNTARY_PERIODIC_GC(heap)  /* no voluntary gc */
 #endif  /* DUK_USE_VOLUNTARY_GC */
@@ -46189,7 +47759,6 @@
 
 DUK_INTERNAL void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size) {
 	void *res;
-	duk_bool_t rc;
 	duk_small_int_t i;
 
 	DUK_ASSERT(heap != NULL);
@@ -46207,7 +47776,7 @@
 
 #if defined(DUK_USE_GC_TORTURE)
 	/* simulate alloc failure on every alloc (except when mark-and-sweep is running) */
-	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+	if (heap->ms_prevent_count == 0) {
 		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first alloc attempt fails"));
 		res = NULL;
 		DUK_UNREF(res);
@@ -46215,7 +47784,7 @@
 	}
 #endif
 	res = heap->alloc_func(heap->heap_udata, size);
-	if (res || size == 0) {
+	if (DUK_LIKELY(res || size == 0)) {
 		/* for zero size allocations NULL is allowed */
 		return res;
 	}
@@ -46225,16 +47794,22 @@
 
 	DUK_D(DUK_DPRINT("first alloc attempt failed, attempt to gc and retry"));
 
+#if 0
 	/*
 	 *  Avoid a GC if GC is already running.  This can happen at a late
 	 *  stage in a GC when we try to e.g. resize the stringtable
 	 *  or compact objects.
-	 */
-
-	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+	 *
+	 *  NOTE: explicit handling isn't actually be needed: if the GC is
+	 *  not allowed, duk_heap_mark_and_sweep() will reject it for every
+	 *  attempt in the loop below, resulting in a NULL same as here.
+	 */
+
+	if (heap->ms_prevent_count != 0) {
 		DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed, gc in progress (gc skipped), alloc size %ld", (long) size));
 		return NULL;
 	}
+#endif
 
 	/*
 	 *  Retry with several GC attempts.  Initial attempts are made without
@@ -46250,8 +47825,7 @@
 			flags |= DUK_MS_FLAG_EMERGENCY;
 		}
 
-		rc = duk_heap_mark_and_sweep(heap, flags);
-		DUK_UNREF(rc);
+		duk_heap_mark_and_sweep(heap, flags);
 
 		res = heap->alloc_func(heap->heap_udata, size);
 		if (res) {
@@ -46272,20 +47846,43 @@
 	DUK_ASSERT_DISABLE(size >= 0);
 
 	res = DUK_ALLOC(heap, size);
-	if (res) {
+	if (DUK_LIKELY(res != NULL)) {
 		/* assume memset with zero size is OK */
 		DUK_MEMZERO(res, size);
 	}
 	return res;
 }
 
+DUK_INTERNAL void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size) {
+	void *res;
+
+	DUK_ASSERT(thr != NULL);
+	res = duk_heap_mem_alloc(thr->heap, size);
+	if (DUK_LIKELY(res != NULL || size == 0)) {
+		return res;
+	}
+	DUK_ERROR_ALLOC_FAILED(thr);
+	return NULL;
+}
+
+DUK_INTERNAL void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size) {
+	void *res;
+
+	DUK_ASSERT(thr != NULL);
+	res = duk_heap_mem_alloc_zeroed(thr->heap, size);
+	if (DUK_LIKELY(res != NULL || size == 0)) {
+		return res;
+	}
+	DUK_ERROR_ALLOC_FAILED(thr);
+	return NULL;
+}
+
 /*
  *  Reallocate memory with garbage collection
  */
 
 DUK_INTERNAL void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize) {
 	void *res;
-	duk_bool_t rc;
 	duk_small_int_t i;
 
 	DUK_ASSERT(heap != NULL);
@@ -46304,7 +47901,7 @@
 
 #if defined(DUK_USE_GC_TORTURE)
 	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
-	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+	if (heap->ms_prevent_count == 0) {
 		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first realloc attempt fails"));
 		res = NULL;
 		DUK_UNREF(res);
@@ -46312,7 +47909,7 @@
 	}
 #endif
 	res = heap->realloc_func(heap->heap_udata, ptr, newsize);
-	if (res || newsize == 0) {
+	if (DUK_LIKELY(res || newsize == 0)) {
 		/* for zero size allocations NULL is allowed */
 		return res;
 	}
@@ -46322,14 +47919,16 @@
 
 	DUK_D(DUK_DPRINT("first realloc attempt failed, attempt to gc and retry"));
 
+#if 0
 	/*
 	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
 	 */
 
-	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+	if (heap->ms_prevent_count != 0) {
 		DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize));
 		return NULL;
 	}
+#endif
 
 	/*
 	 *  Retry with several GC attempts.  Initial attempts are made without
@@ -46345,8 +47944,7 @@
 			flags |= DUK_MS_FLAG_EMERGENCY;
 		}
 
-		rc = duk_heap_mark_and_sweep(heap, flags);
-		DUK_UNREF(rc);
+		duk_heap_mark_and_sweep(heap, flags);
 
 		res = heap->realloc_func(heap->heap_udata, ptr, newsize);
 		if (res || newsize == 0) {
@@ -46368,7 +47966,6 @@
 
 DUK_INTERNAL void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) {
 	void *res;
-	duk_bool_t rc;
 	duk_small_int_t i;
 
 	DUK_ASSERT(heap != NULL);
@@ -46386,7 +47983,7 @@
 
 #if defined(DUK_USE_GC_TORTURE)
 	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
-	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+	if (heap->ms_prevent_count == 0) {
 		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first indirect realloc attempt fails"));
 		res = NULL;
 		DUK_UNREF(res);
@@ -46394,7 +47991,7 @@
 	}
 #endif
 	res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize);
-	if (res || newsize == 0) {
+	if (DUK_LIKELY(res || newsize == 0)) {
 		/* for zero size allocations NULL is allowed */
 		return res;
 	}
@@ -46404,14 +48001,16 @@
 
 	DUK_D(DUK_DPRINT("first indirect realloc attempt failed, attempt to gc and retry"));
 
+#if 0
 	/*
 	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
 	 */
 
-	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+	if (heap->ms_prevent_count != 0) {
 		DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize));
 		return NULL;
 	}
+#endif
 
 	/*
 	 *  Retry with several GC attempts.  Initial attempts are made without
@@ -46435,8 +48034,7 @@
 			flags |= DUK_MS_FLAG_EMERGENCY;
 		}
 
-		rc = duk_heap_mark_and_sweep(heap, flags);
-		DUK_UNREF(rc);
+		duk_heap_mark_and_sweep(heap, flags);
 #if defined(DUK_USE_ASSERTIONS)
 		ptr_post = cb(heap, ud);
 		if (ptr_pre != ptr_post) {
@@ -46475,14 +48073,10 @@
 	 */
 	heap->free_func(heap->heap_udata, ptr);
 
-	/* Count free operations toward triggering a GC but never actually trigger
-	 * a GC from a free.  Otherwise code which frees internal structures would
-	 * need to put in NULLs at every turn to ensure the object is always in
-	 * consistent state for a mark-and-sweep.
-	 */
-#if defined(DUK_USE_VOLUNTARY_GC)
-	heap->mark_and_sweep_trigger_counter--;
-#endif
+	/* Never perform a GC (even voluntary) in a memory free, otherwise
+	 * all call sites doing frees would need to deal with the side effects.
+	 * No need to update voluntary GC counter either.
+	 */
 }
 
 /* automatic undefs */
@@ -46494,44 +48088,146 @@
 
 /* #include duk_internal.h -> already included */
 
-#if defined(DUK_USE_DOUBLE_LINKED_HEAP) && defined(DUK_USE_REFERENCE_COUNTING)
-/* arbitrary remove only works with double linked heap, and is only required by
- * reference counting so far.
- */
-DUK_INTERNAL void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
+DUK_INTERNAL void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
+	duk_heaphdr *root;
+
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);
+
+	root = heap->heap_allocated;
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
+	if (root != NULL) {
+		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, root) == NULL);
+		DUK_HEAPHDR_SET_PREV(heap, root, hdr);
+	}
+	DUK_HEAPHDR_SET_PREV(heap, hdr, NULL);
+#endif
+	DUK_HEAPHDR_SET_NEXT(heap, hdr, root);
+	DUK_ASSERT_HEAPHDR_LINKS(heap, hdr);
+	DUK_ASSERT_HEAPHDR_LINKS(heap, root);
+	heap->heap_allocated = hdr;
+}
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+DUK_INTERNAL void duk_heap_remove_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
+	duk_heaphdr *prev;
+	duk_heaphdr *next;
+
+	/* Strings are in string table. */
+	DUK_ASSERT(hdr != NULL);
 	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);
 
-	if (DUK_HEAPHDR_GET_PREV(heap, hdr)) {
-		DUK_HEAPHDR_SET_NEXT(heap, DUK_HEAPHDR_GET_PREV(heap, hdr), DUK_HEAPHDR_GET_NEXT(heap, hdr));
-	} else {
-		heap->heap_allocated = DUK_HEAPHDR_GET_NEXT(heap, hdr);
-	}
-	if (DUK_HEAPHDR_GET_NEXT(heap, hdr)) {
-		DUK_HEAPHDR_SET_PREV(heap, DUK_HEAPHDR_GET_NEXT(heap, hdr), DUK_HEAPHDR_GET_PREV(heap, hdr));
+	/* Target 'hdr' must be in heap_allocated (not e.g. finalize_list).
+	 * If not, heap lists will become corrupted so assert early for it.
+	 */
+#if defined(DUK_USE_ASSERTIONS)
+	{
+		duk_heaphdr *tmp;
+		for (tmp = heap->heap_allocated; tmp != NULL; tmp = DUK_HEAPHDR_GET_NEXT(heap, tmp)) {
+			if (tmp == hdr) {
+				break;
+			}
+		}
+		DUK_ASSERT(tmp == hdr);
+	}
+#endif
+
+	/* Read/write only once to minimize pointer compression calls. */
+	prev = DUK_HEAPHDR_GET_PREV(heap, hdr);
+	next = DUK_HEAPHDR_GET_NEXT(heap, hdr);
+
+	if (prev != NULL) {
+		DUK_ASSERT(heap->heap_allocated != hdr);
+		DUK_HEAPHDR_SET_NEXT(heap, prev, next);
+	} else {
+		DUK_ASSERT(heap->heap_allocated == hdr);
+		heap->heap_allocated = next;
+	}
+	if (next != NULL) {
+		DUK_HEAPHDR_SET_PREV(heap, next, prev);
 	} else {
 		;
 	}
-
-	/* The prev/next pointers of the removed duk_heaphdr are left as garbage.
-	 * It's up to the caller to ensure they're written before inserting the
-	 * object back.
-	 */
-}
-#endif
-
-DUK_INTERNAL void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
-	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);
-
+}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+DUK_INTERNAL void duk_heap_insert_into_finalize_list(duk_heap *heap, duk_heaphdr *hdr) {
+	duk_heaphdr *root;
+
+	root = heap->finalize_list;
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
+	DUK_HEAPHDR_SET_PREV(heap, hdr, NULL);
+	if (root != NULL) {
+		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, root) == NULL);
+		DUK_HEAPHDR_SET_PREV(heap, root, hdr);
+	}
+#endif
+	DUK_HEAPHDR_SET_NEXT(heap, hdr, root);
+	DUK_ASSERT_HEAPHDR_LINKS(heap, hdr);
+	DUK_ASSERT_HEAPHDR_LINKS(heap, root);
+	heap->finalize_list = hdr;
+}
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+DUK_INTERNAL void duk_heap_remove_from_finalize_list(duk_heap *heap, duk_heaphdr *hdr) {
 #if defined(DUK_USE_DOUBLE_LINKED_HEAP)
-	if (heap->heap_allocated) {
-		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, heap->heap_allocated) == NULL);
-		DUK_HEAPHDR_SET_PREV(heap, heap->heap_allocated, hdr);
-	}
-	DUK_HEAPHDR_SET_PREV(heap, hdr, NULL);
-#endif
-	DUK_HEAPHDR_SET_NEXT(heap, hdr, heap->heap_allocated);
-	heap->heap_allocated = hdr;
-}
+	duk_heaphdr *next;
+	duk_heaphdr *prev;
+
+	next = DUK_HEAPHDR_GET_NEXT(heap, hdr);
+	prev = DUK_HEAPHDR_GET_PREV(heap, hdr);
+	if (next != NULL) {
+		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, next) == hdr);
+		DUK_HEAPHDR_SET_PREV(heap, next, prev);
+	}
+	if (prev == NULL) {
+		DUK_ASSERT(hdr == heap->finalize_list);
+		heap->finalize_list = next;
+	} else {
+		DUK_ASSERT(hdr != heap->finalize_list);
+		DUK_HEAPHDR_SET_NEXT(heap, prev, next);
+	}
+#else
+	duk_heaphdr *next;
+	duk_heaphdr *curr;
+
+	/* Random removal is expensive: we need to locate the previous element
+	 * because we don't have a 'prev' pointer.
+	 */
+	curr = heap->finalize_list;
+	if (curr == hdr) {
+		heap->finalize_list = DUK_HEAPHDR_GET_NEXT(heap, curr);
+	} else {
+		DUK_ASSERT(hdr != heap->finalize_list);
+		for (;;) {
+			DUK_ASSERT(curr != NULL);  /* Caller responsibility. */
+
+			next = DUK_HEAPHDR_GET_NEXT(heap, curr);
+			if (next == hdr) {
+				next = DUK_HEAPHDR_GET_NEXT(heap, hdr);
+				DUK_HEAPHDR_SET_NEXT(heap, curr, next);
+				break;
+			}
+		}
+	}
+#endif
+}
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
+
+#if defined(DUK_USE_ASSERTIONS)
+DUK_INTERNAL duk_bool_t duk_heap_in_heap_allocated(duk_heap *heap, duk_heaphdr *ptr) {
+	duk_heaphdr *curr;
+	DUK_ASSERT(heap != NULL);
+
+	for (curr = heap->heap_allocated; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) {
+		if (curr == ptr) {
+			return 1;
+		}
+	}
+	return 0;
+}
+#endif  /* DUK_USE_ASSERTIONS */
 
 #if defined(DUK_USE_INTERRUPT_COUNTER)
 DUK_INTERNAL void duk_heap_switch_thread(duk_heap *heap, duk_hthread *new_thr) {
@@ -46570,6 +48266,10 @@
 #line 1 "duk_heap_refcount.c"
 /*
  *  Reference counting implementation.
+ *
+ *  INCREF/DECREF, finalization and freeing of objects whose refcount reaches
+ *  zero (refzero).  These operations are very performance sensitive, so
+ *  various small tricks are used in an attempt to maximize speed.
  */
 
 /* #include duk_internal.h -> already included */
@@ -46581,36 +48281,6 @@
 #endif
 
 /*
- *  Misc
- */
-
-DUK_LOCAL void duk__queue_refzero(duk_heap *heap, duk_heaphdr *hdr) {
-	/* tail insert: don't disturb head in case refzero is running */
-
-	if (heap->refzero_list != NULL) {
-		duk_heaphdr *hdr_prev;
-
-		hdr_prev = heap->refzero_list_tail;
-		DUK_ASSERT(hdr_prev != NULL);
-		DUK_ASSERT(DUK_HEAPHDR_GET_NEXT(heap, hdr_prev) == NULL);
-
-		DUK_HEAPHDR_SET_NEXT(heap, hdr, NULL);
-		DUK_HEAPHDR_SET_PREV(heap, hdr, hdr_prev);
-		DUK_HEAPHDR_SET_NEXT(heap, hdr_prev, hdr);
-		DUK_ASSERT_HEAPHDR_LINKS(heap, hdr);
-		DUK_ASSERT_HEAPHDR_LINKS(heap, hdr_prev);
-		heap->refzero_list_tail = hdr;
-	} else {
-		DUK_ASSERT(heap->refzero_list_tail == NULL);
-		DUK_HEAPHDR_SET_NEXT(heap, hdr, NULL);
-		DUK_HEAPHDR_SET_PREV(heap, hdr, NULL);
-		DUK_ASSERT_HEAPHDR_LINKS(heap, hdr);
-		heap->refzero_list = hdr;
-		heap->refzero_list_tail = hdr;
-	}
-}
-
-/*
  *  Heap object refcount finalization.
  *
  *  When an object is about to be freed, all other objects it refers to must
@@ -46618,41 +48288,47 @@
  *  allocations (mark-and-sweep shares these helpers), it just manipulates
  *  the refcounts.
  *
- *  Note that any of the decref's may cause a refcount to drop to zero, BUT
- *  it will not be processed inline.  If refcount finalization is triggered
- *  by refzero processing, the objects will be just queued to the refzero
- *  list and processed later which eliminates C recursion.  If refcount
- *  finalization is triggered by mark-and-sweep, any refzero situations are
- *  ignored because mark-and-sweep will deal with them.  NORZ variants can
- *  be used here in both cases.
- */
-
-DUK_LOCAL void duk__refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h) {
+ *  Note that any of the DECREFs may cause a refcount to drop to zero.  If so,
+ *  the object won't be refzero processed inline, but will just be queued to
+ *  refzero_list and processed by an earlier caller working on refzero_list,
+ *  eliminating C recursion from even long refzero cascades.  If refzero
+ *  finalization is triggered by mark-and-sweep, refzero conditions are ignored
+ *  (objects are not even queued to refzero_list) because mark-and-sweep deals
+ *  with them; refcounts are still updated so that they remain in sync with
+ *  actual references.
+ */
+
+DUK_INTERNAL void duk_hobject_refcount_finalize_norz(duk_heap *heap, duk_hobject *h) {
+	duk_hthread *thr;
 	duk_uint_fast32_t i;
 	duk_uint_fast32_t n;
 	duk_propvalue *p_val;
 	duk_tval *p_tv;
 	duk_hstring **p_key;
 	duk_uint8_t *p_flag;
-
+	duk_hobject *h_proto;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
 	DUK_ASSERT(h);
 	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h) == DUK_HTYPE_OBJECT);
 
-	/* XXX: better to get base and walk forwards? */
-
-	p_key = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, h);
-	p_val = DUK_HOBJECT_E_GET_VALUE_BASE(thr->heap, h);
-	p_flag = DUK_HOBJECT_E_GET_FLAGS_BASE(thr->heap, h);
+	thr = heap->heap_thread;
+	DUK_ASSERT(thr != NULL);
+
+	p_key = DUK_HOBJECT_E_GET_KEY_BASE(heap, h);
+	p_val = DUK_HOBJECT_E_GET_VALUE_BASE(heap, h);
+	p_flag = DUK_HOBJECT_E_GET_FLAGS_BASE(heap, h);
 	n = DUK_HOBJECT_GET_ENEXT(h);
 	while (n-- > 0) {
 		duk_hstring *key;
 
 		key = p_key[n];
-		if (!key) {
+		if (DUK_UNLIKELY(key == NULL)) {
 			continue;
 		}
 		DUK_HSTRING_DECREF_NORZ(thr, key);
-		if (p_flag[n] & DUK_PROPDESC_FLAG_ACCESSOR) {
+		if (DUK_UNLIKELY(p_flag[n] & DUK_PROPDESC_FLAG_ACCESSOR)) {
 			duk_hobject *h_getset;
 			h_getset = p_val[n].a.get;
 			DUK_ASSERT(h_getset == NULL || DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_getset));
@@ -46667,7 +48343,7 @@
 		}
 	}
 
-	p_tv = DUK_HOBJECT_A_GET_BASE(thr->heap, h);
+	p_tv = DUK_HOBJECT_A_GET_BASE(heap, h);
 	n = DUK_HOBJECT_GET_ASIZE(h);
 	while (n-- > 0) {
 		duk_tval *tv_val;
@@ -46675,39 +48351,49 @@
 		DUK_TVAL_DECREF_NORZ(thr, tv_val);
 	}
 
-	/* hash part is a 'weak reference' and does not contribute */
-
-	{
-		duk_hobject *h_proto;
-		h_proto = (duk_hobject *) DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h);
-		DUK_ASSERT(h_proto == NULL || DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_proto));
-		DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h_proto);
-	}
-
-	/* XXX: rearrange bits to allow a switch case to be used here? */
-	/* XXX: add a fast path for objects (and arrays)? */
-
-	/* DUK_HOBJECT_IS_ARRAY(h): needs no special handling now as there are
-	 * no extra fields in need of decref.
-	 */
+	/* Hash part is a 'weak reference' and doesn't contribute to refcounts. */
+
+	h_proto = (duk_hobject *) DUK_HOBJECT_GET_PROTOTYPE(heap, h);
+	DUK_ASSERT(h_proto == NULL || DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_proto));
+	DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h_proto);
+
+	/* XXX: Object subclass tests are quite awkward at present, ideally
+	 * we should be able to switch-case here with a dense index (subtype
+	 * number or something).  For now, fast path plain objects and arrays
+	 * and bit test the rest individually.
+	 */
+
+	if (DUK_HOBJECT_HAS_FASTREFS(h)) {
+		/* Plain object or array, nothing more to do.  While a
+		 * duk_harray has additional fields, none of them need
+		 * DECREF updates.
+		 */
+		DUK_ASSERT(DUK_HOBJECT_ALLOWS_FASTREFS(h));
+		return;
+	}
+	DUK_ASSERT(DUK_HOBJECT_PROHIBITS_FASTREFS(h));
+
+	/* Slow path: special object, start bit checks from most likely. */
+
 	if (DUK_HOBJECT_IS_COMPFUNC(h)) {
 		duk_hcompfunc *f = (duk_hcompfunc *) h;
 		duk_tval *tv, *tv_end;
 		duk_hobject **funcs, **funcs_end;
 
-		if (DUK_HCOMPFUNC_GET_DATA(thr->heap, f) != NULL) {
-			tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, f);
-			tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, f);
+		if (DUK_LIKELY(DUK_HCOMPFUNC_GET_DATA(heap, f) != NULL)) {
+			tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, f);
+			tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(heap, f);
 			while (tv < tv_end) {
 				DUK_TVAL_DECREF_NORZ(thr, tv);
 				tv++;
 			}
 
-			funcs = DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, f);
-			funcs_end = DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, f);
+			funcs = DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, f);
+			funcs_end = DUK_HCOMPFUNC_GET_FUNCS_END(heap, f);
 			while (funcs < funcs_end) {
 				duk_hobject *h_func;
 				h_func = *funcs;
+				DUK_ASSERT(h_func != NULL);
 				DUK_ASSERT(DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_func));
 				DUK_HCOMPFUNC_DECREF_NORZ(thr, (duk_hcompfunc *) h_func);
 				funcs++;
@@ -46717,13 +48403,19 @@
 			DUK_D(DUK_DPRINT("duk_hcompfunc 'data' is NULL, skipping decref"));
 		}
 
-		DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_LEXENV(thr->heap, f));
-		DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_VARENV(thr->heap, f));
-		DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(thr->heap, f));
-	} else if (DUK_HOBJECT_IS_NATFUNC(h)) {
-		duk_hnatfunc *f = (duk_hnatfunc *) h;
-		DUK_UNREF(f);
-		/* nothing to finalize */
+		DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_LEXENV(heap, f));
+		DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_VARENV(heap, f));
+		DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(heap, f));
+	} else if (DUK_HOBJECT_IS_DECENV(h)) {
+		duk_hdecenv *e = (duk_hdecenv *) h;
+		DUK_ASSERT_HDECENV_VALID(e);
+		DUK_HTHREAD_DECREF_NORZ_ALLOWNULL(thr, e->thread);
+		DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, e->varmap);
+	} else if (DUK_HOBJECT_IS_OBJENV(h)) {
+		duk_hobjenv *e = (duk_hobjenv *) h;
+		DUK_ASSERT_HOBJENV_VALID(e);
+		DUK_ASSERT(e->target != NULL);  /* Required for object environments. */
+		DUK_HOBJECT_DECREF_NORZ(thr, e->target);
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
 	} else if (DUK_HOBJECT_IS_BUFOBJ(h)) {
 		duk_hbufobj *b = (duk_hbufobj *) h;
@@ -46761,264 +48453,284 @@
 		}
 
 		DUK_HTHREAD_DECREF_NORZ_ALLOWNULL(thr, (duk_hthread *) t->resumer);
-	}
-}
-
-DUK_INTERNAL void duk_heaphdr_refcount_finalize(duk_hthread *thr, duk_heaphdr *hdr) {
-	DUK_ASSERT(hdr);
-
-	if (DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_OBJECT) {
-		duk__refcount_finalize_hobject(thr, (duk_hobject *) hdr);
+	} else {
+		/* We may come here if the object should have a FASTREFS flag
+		 * but it's missing for some reason.  Assert for never getting
+		 * here; however, other than performance, this is harmless.
+		 */
+		DUK_D(DUK_DPRINT("missing FASTREFS flag for: %!iO", h));
+		DUK_ASSERT(0);
+	}
+}
+
+DUK_INTERNAL void duk_heaphdr_refcount_finalize_norz(duk_heap *heap, duk_heaphdr *hdr) {
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+	DUK_ASSERT(hdr != NULL);
+
+	if (DUK_HEAPHDR_IS_OBJECT(hdr)) {
+		duk_hobject_refcount_finalize_norz(heap, (duk_hobject *) hdr);
 	}
 	/* DUK_HTYPE_BUFFER: nothing to finalize */
 	/* DUK_HTYPE_STRING: nothing to finalize */
 }
 
+/*
+ *  Refzero processing for duk_hobject: queue a refzero'ed object to either
+ *  finalize_list or refzero_list and process the relevent list(s) if
+ *  necessary.
+ *
+ *  Refzero_list is single linked, with only 'prev' pointers set and valid.
+ *  All 'next' pointers are intentionally left as garbage.  This doesn't
+ *  matter because refzero_list is processed to completion before any other
+ *  code (like mark-and-sweep) might walk the list.
+ *
+ *  In more detail:
+ *
+ *  - On first insert refzero_list is NULL and the new object becomes the
+ *    first and only element on the list; duk__refcount_free_pending() is
+ *    called and it starts processing the list from the initial element,
+ *    i.e. the list tail.
+ *
+ *  - As each object is refcount finalized, new objects may be queued to
+ *    refzero_list head.  Their 'next' pointers are left as garbage, but
+ *    'prev' points are set correctly, with the element at refzero_list
+ *    having a NULL 'prev' pointer.  The fact that refzero_list is non-NULL
+ *    is used to reject (1) recursive duk__refcount_free_pending() and
+ *    (2) finalize_list processing calls.
+ *
+ *  - When we're done with the current object, read its 'prev' pointer and
+ *    free the object.  If 'prev' is NULL, we've reached head of list and are
+ *    done: set refzero_list to NULL and process pending finalizers.  Otherwise
+ *    continue processing the list.
+ *
+ *  A refzero cascade is free of side effects because it only involves
+ *  queueing more objects and freeing memory; finalizer execution is blocked
+ *  in the code path queueing objects to finalize_list.  As a result the
+ *  initial refzero call (which triggers duk__refcount_free_pending()) must
+ *  check finalize_list so that finalizers are executed snappily.
+ *
+ *  If finalize_list processing starts first, refzero may occur while we're
+ *  processing finalizers.  That's fine: that particular refzero cascade is
+ *  handled to completion without side effects.  Once the cascade is complete,
+ *  we'll run pending finalizers but notice that we're already doing that and
+ *  return.
+ *
+ *  This could be expanded to allow incremental freeing: just bail out
+ *  early and resume at a future alloc/decref/refzero.  However, if that
+ *  were done, the list structure would need to be kept consistent at all
+ *  times, mark-and-sweep would need to handle refzero_list, etc.
+ */
+
+DUK_LOCAL void duk__refcount_free_pending(duk_heap *heap) {
+	duk_heaphdr *curr;
+#if defined(DUK_USE_DEBUG)
+	duk_int_t count = 0;
+#endif
+
+	DUK_ASSERT(heap != NULL);
+
+	curr = heap->refzero_list;
+	DUK_ASSERT(curr != NULL);
+	DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, curr) == NULL);  /* We're called on initial insert only. */
+	/* curr->next is GARBAGE. */
+
+	do {
+		duk_heaphdr *prev;
+
+		DUK_DDD(DUK_DDDPRINT("refzero processing %p: %!O", (void *) curr, (duk_heaphdr *) curr));
+
+#if defined(DUK_USE_DEBUG)
+		count++;
+#endif
+
+		DUK_ASSERT(curr != NULL);
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);  /* currently, always the case */
+		/* FINALIZED may be set; don't care about flags here. */
+
+		/* Refcount finalize 'curr'.  Refzero_list must be non-NULL
+		 * here to prevent recursive entry to duk__refcount_free_pending().
+		 */
+		DUK_ASSERT(heap->refzero_list != NULL);
+		duk_hobject_refcount_finalize_norz(heap, (duk_hobject *) curr);
+
+		prev = DUK_HEAPHDR_GET_PREV(heap, curr);
+		DUK_ASSERT((prev == NULL && heap->refzero_list == curr) || \
+		           (prev != NULL && heap->refzero_list != curr));
+		/* prev->next is intentionally not updated and is garbage. */
+
+		duk_free_hobject(heap, (duk_hobject *) curr);  /* Invalidates 'curr'. */
+
+		curr = prev;
+	} while (curr != NULL);
+
+	heap->refzero_list = NULL;
+
+	DUK_DD(DUK_DDPRINT("refzero processed %ld objects", (long) count));
+}
+
+DUK_LOCAL DUK_INLINE void duk__refcount_refzero_hobject(duk_heap *heap, duk_hobject *obj, duk_bool_t skip_free_pending) {
+	duk_heaphdr *hdr;
+	duk_heaphdr *root;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) obj) == DUK_HTYPE_OBJECT);
+
+	hdr = (duk_heaphdr *) obj;
+
+	/* Refzero'd objects must be in heap_allocated.  They can't be in
+	 * finalize_list because all objects on finalize_list have an
+	 * artificial +1 refcount bump.
+	 */
+#if defined(DUK_USE_ASSERTIONS)
+	DUK_ASSERT(duk_heap_in_heap_allocated(heap, (duk_heaphdr *) obj));
+#endif
+
+	DUK_HEAP_REMOVE_FROM_HEAP_ALLOCATED(heap, hdr);
+
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+	/* This finalizer check MUST BE side effect free.  It should also be
+	 * as fast as possible because it's applied to every object freed.
+	 */
+	if (DUK_UNLIKELY(DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) hdr))) {
+		/* Special case: FINALIZED may be set if mark-and-sweep queued
+		 * object for finalization, the finalizer was executed (and
+		 * FINALIZED set), mark-and-sweep hasn't yet processed the
+		 * object again, but its refcount drops to zero.  Free without
+		 * running the finalizer again.
+		 */
+		if (DUK_HEAPHDR_HAS_FINALIZED(hdr)) {
+			DUK_D(DUK_DPRINT("refzero'd object has finalizer and FINALIZED is set -> free"));
+		} else {
+			/* Set FINALIZABLE flag so that all objects on finalize_list
+			 * will have it set and are thus detectable based on the
+			 * flag alone.
+			 */
+			DUK_HEAPHDR_SET_FINALIZABLE(hdr);
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			/* Bump refcount on finalize_list insert so that a
+			 * refzero can never occur when an object is waiting
+			 * for its finalizer call.  Refzero might otherwise
+			 * now happen because we allow duk_push_heapptr() for
+			 * objects pending finalization.
+			 */
+			DUK_HEAPHDR_PREINC_REFCOUNT(hdr);
+#endif
+			DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap, hdr);
+
+			/* Process finalizers unless skipping is explicitly
+			 * requested (NORZ) or refzero_list is being processed
+			 * (avoids side effects during a refzero cascade).
+			 * If refzero_list is processed, the initial refzero
+			 * call will run pending finalizers when refzero_list
+			 * is done.
+			 */
+			if (!skip_free_pending && heap->refzero_list == NULL) {
+				duk_heap_process_finalize_list(heap);
+			}
+			return;
+		}
+	}
+#endif  /* DUK_USE_FINALIZER_SUPPORT */
+
+	/* No need to finalize, free object via refzero_list. */
+
+	root = heap->refzero_list;
+
+	DUK_HEAPHDR_SET_PREV(heap, hdr, NULL);
+	/* 'next' is left as GARBAGE. */
+	heap->refzero_list = hdr;
+
+	if (root == NULL) {
+		/* Object is now queued.  Refzero_list was NULL so
+		 * no-one is currently processing it; do it here.
+		 * With refzero processing just doing a cascade of
+		 * free calls, we can process it directly even when
+		 * NORZ macros are used: there are no side effects.
+		 */
+		duk__refcount_free_pending(heap);
+		DUK_ASSERT(heap->refzero_list == NULL);
+
+		/* Process finalizers only after the entire cascade
+		 * is finished.  In most cases there's nothing to
+		 * finalize, so fast path check to avoid a call.
+		 */
+#if defined(DUK_USE_FINALIZER_SUPPORT)
+		if (!skip_free_pending && DUK_UNLIKELY(heap->finalize_list != NULL)) {
+			duk_heap_process_finalize_list(heap);
+		}
+#endif
+	} else {
+		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, root) == NULL);
+		DUK_HEAPHDR_SET_PREV(heap, root, hdr);
+
+		/* Object is now queued.  Because refzero_list was
+		 * non-NULL, it's already being processed by someone
+		 * in the C call stack, so we're done.
+		 */
+	}
+}
+
 #if defined(DUK_USE_FINALIZER_SUPPORT)
-#if defined(DUK_USE_REFZERO_FINALIZER_TORTURE)
-DUK_LOCAL duk_ret_t duk__refcount_fake_finalizer(duk_context *ctx) {
-	DUK_UNREF(ctx);
-	DUK_D(DUK_DPRINT("fake refcount torture finalizer executed"));
-#if 0
-	DUK_DD(DUK_DDPRINT("fake torture finalizer for: %!T", duk_get_tval(ctx, 0)));
-#endif
-	/* Require a lot of stack to force a value stack grow/shrink. */
-	duk_require_stack(ctx, 100000);
-
-	/* XXX: do something to force a callstack grow/shrink, perhaps
-	 * just a manual forced resize?
-	 */
-	return 0;
-}
-
-DUK_LOCAL void duk__refcount_run_torture_finalizer(duk_hthread *thr, duk_hobject *obj) {
-	duk_context *ctx;
-	duk_int_t rc;
-
-	DUK_ASSERT(thr != NULL);
-	DUK_ASSERT(obj != NULL);
-	ctx = (duk_context *) thr;
-
-	/* Avoid fake finalization for the duk__refcount_fake_finalizer function
-	 * itself, otherwise we're in infinite recursion.
-	 */
-	if (DUK_HOBJECT_HAS_NATFUNC(obj)) {
-		if (((duk_hnatfunc *) obj)->func == duk__refcount_fake_finalizer) {
-			DUK_DD(DUK_DDPRINT("avoid fake torture finalizer for duk__refcount_fake_finalizer itself"));
-			return;
-		}
-	}
-	/* Avoid fake finalization when callstack limit has been reached.
-	 * Otherwise a callstack limit error will be created, then refzero'ed,
-	 * and we're in an infinite loop.
-	 */
-	if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit ||
-	    thr->callstack_size + 2 * DUK_CALLSTACK_GROW_STEP >= thr->callstack_max /*approximate*/) {
-		DUK_D(DUK_DPRINT("call recursion depth reached, avoid fake torture finalizer"));
-		return;
-	}
-
-	/* Run fake finalizer.  Avoid creating new refzero queue entries
-	 * so that we are not forced into a forever loop.
-	 */
-	duk_push_c_function(ctx, duk__refcount_fake_finalizer, 1 /*nargs*/);
-	duk_push_hobject(ctx, obj);
-	rc = duk_pcall(ctx, 1);
-	DUK_UNREF(rc);  /* ignored */
-	duk_pop(ctx);
-}
-#endif  /* DUK_USE_REFZERO_FINALIZER_TORTURE */
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
-
-/*
- *  Refcount memory freeing loop.
- *
- *  Frees objects in the refzero_pending list until the list becomes
- *  empty.  When an object is freed, its references get decref'd and
- *  may cause further objects to be queued for freeing.
- *
- *  This could be expanded to allow incremental freeing: just bail out
- *  early and resume at a future alloc/decref/refzero.
- */
-
-DUK_INTERNAL void duk_refzero_free_pending(duk_hthread *thr) {
-	duk_heaphdr *h1, *h2;
-	duk_heap *heap;
-	duk_int_t count = 0;
-
+DUK_INTERNAL DUK_ALWAYS_INLINE void duk_refzero_check_fast(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(thr->heap->refzero_list == NULL);  /* Processed to completion inline. */
+
+	if (DUK_UNLIKELY(thr->heap->finalize_list != NULL)) {
+		duk_heap_process_finalize_list(thr->heap);
+	}
+}
+
+DUK_INTERNAL void duk_refzero_check_slow(duk_hthread *thr) {
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(thr->heap != NULL);
-	heap = thr->heap;
-	DUK_ASSERT(heap != NULL);
-
-	/*
-	 *  Detect recursive invocation
-	 */
-
-	if (DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)) {
-		DUK_DDD(DUK_DDDPRINT("refzero free running, skip run"));
-		return;
-	}
-
-	/*
-	 *  Churn refzero_list until empty
-	 */
-
-	DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap);
-	while (heap->refzero_list) {
-		duk_hobject *obj;
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-		duk_bool_t rescued = 0;
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
-
-		/*
-		 *  Pick an object from the head (don't remove yet).
-		 */
-
-		h1 = heap->refzero_list;
-		obj = (duk_hobject *) h1;
-		DUK_DD(DUK_DDPRINT("refzero processing %p: %!O", (void *) h1, (duk_heaphdr *) h1));
-		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, h1) == NULL);
-		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT);  /* currently, always the case */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-#if defined(DUK_USE_REFZERO_FINALIZER_TORTURE)
-		/* Torture option to shake out finalizer side effect issues:
-		 * make a bogus function call for every finalizable object,
-		 * essentially simulating the case where everything has a
-		 * finalizer.
-		 */
-		DUK_DD(DUK_DDPRINT("refzero torture enabled, fake finalizer"));
-		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(h1) == 0);
-		DUK_HEAPHDR_PREINC_REFCOUNT(h1);  /* bump refcount to prevent refzero during finalizer processing */
-		duk__refcount_run_torture_finalizer(thr, obj);  /* must never longjmp */
-		DUK_HEAPHDR_PREDEC_REFCOUNT(h1);  /* remove artificial bump */
-		DUK_ASSERT_DISABLE(h1->h_refcount >= 0);  /* refcount is unsigned, so always true */
-#endif  /* DUK_USE_REFZERO_FINALIZER_TORTURE */
+	DUK_ASSERT(thr->heap->refzero_list == NULL);  /* Processed to completion inline. */
+
+	if (DUK_UNLIKELY(thr->heap->finalize_list != NULL)) {
+		duk_heap_process_finalize_list(thr->heap);
+	}
+}
 #endif  /* DUK_USE_FINALIZER_SUPPORT */
 
-		/*
-		 *  Finalizer check.
-		 *
-		 *  Note: running a finalizer may have arbitrary side effects, e.g.
-		 *  queue more objects on refzero_list (tail), or even trigger a
-		 *  mark-and-sweep.
-		 *
-		 *  Note: quick reject check should match vast majority of
-		 *  objects and must be safe (not throw any errors, ever).
-		 *
-		 *  An object may have FINALIZED here if it was finalized by mark-and-sweep
-		 *  on a previous run and refcount then decreased to zero.  We won't run the
-		 *  finalizer again here.
-		 *
-		 *  A finalizer is looked up from the object and up its prototype chain
-		 *  (which allows inherited finalizers).
-		 */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-		if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
-			DUK_DDD(DUK_DDDPRINT("object has a finalizer, run it"));
-
-			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(h1) == 0);
-			DUK_HEAPHDR_PREINC_REFCOUNT(h1);  /* bump refcount to prevent refzero during finalizer processing */
-
-			duk_hobject_run_finalizer(thr, obj);  /* must never longjmp */
-			DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(h1));  /* duk_hobject_run_finalizer() sets */
-
-			DUK_HEAPHDR_PREDEC_REFCOUNT(h1);  /* remove artificial bump */
-			DUK_ASSERT_DISABLE(h1->h_refcount >= 0);  /* refcount is unsigned, so always true */
-
-			if (DUK_HEAPHDR_GET_REFCOUNT(h1) != 0) {
-				DUK_DDD(DUK_DDDPRINT("-> object refcount after finalization non-zero, object will be rescued"));
-				rescued = 1;
-			} else {
-				DUK_DDD(DUK_DDDPRINT("-> object refcount still zero after finalization, object will be freed"));
-			}
-		}
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
-
-		/* Refzero head is still the same.  This is the case even if finalizer
-		 * inserted more refzero objects; they are inserted to the tail.
-		 */
-		DUK_ASSERT(h1 == heap->refzero_list);
-
-		/*
-		 *  Remove the object from the refzero list.  This cannot be done
-		 *  before a possible finalizer has been executed; the finalizer
-		 *  may trigger a mark-and-sweep, and mark-and-sweep must be able
-		 *  to traverse a complete refzero_list.
-		 */
-
-		h2 = DUK_HEAPHDR_GET_NEXT(heap, h1);
-		if (h2) {
-			DUK_HEAPHDR_SET_PREV(heap, h2, NULL);  /* not strictly necessary */
-			heap->refzero_list = h2;
-		} else {
-			heap->refzero_list = NULL;
-			heap->refzero_list_tail = NULL;
-		}
-
-		/*
-		 *  Rescue or free.
-		 */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-		if (rescued) {
-			/* yes -> move back to heap allocated */
-			DUK_DD(DUK_DDPRINT("object rescued during refcount finalization: %p", (void *) h1));
-			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(h1));
-			DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(h1));
-			DUK_HEAPHDR_CLEAR_FINALIZED(h1);
-			h2 = heap->heap_allocated;
-			DUK_HEAPHDR_SET_PREV(heap, h1, NULL);
-			if (h2) {
-				DUK_HEAPHDR_SET_PREV(heap, h2, h1);
-			}
-			DUK_HEAPHDR_SET_NEXT(heap, h1, h2);
-			DUK_ASSERT_HEAPHDR_LINKS(heap, h1);
-			DUK_ASSERT_HEAPHDR_LINKS(heap, h2);
-			heap->heap_allocated = h1;
-		} else
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
-		{
-			/* no -> decref members, then free */
-			duk__refcount_finalize_hobject(thr, obj);
-			DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT);  /* currently, always the case */
-			duk_free_hobject(heap, (duk_hobject *) h1);
-		}
-
-		count++;
-	}
-	DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap);
-
-	DUK_DDD(DUK_DDDPRINT("refzero processed %ld objects", (long) count));
-
-	/*
-	 *  Once the whole refzero cascade has been freed, check for
-	 *  a voluntary mark-and-sweep.
-	 */
-
-#if defined(DUK_USE_VOLUNTARY_GC)
-	/* 'count' is more or less comparable to normal trigger counter update
-	 * which happens in memory block (re)allocation.
-	 */
-	heap->mark_and_sweep_trigger_counter -= count;
-	if (heap->mark_and_sweep_trigger_counter <= 0) {
-		duk_bool_t rc;
-		duk_small_uint_t flags = 0;  /* not emergency */
-		DUK_D(DUK_DPRINT("refcount triggering mark-and-sweep"));
-		rc = duk_heap_mark_and_sweep(heap, flags);
-		DUK_UNREF(rc);
-		DUK_D(DUK_DPRINT("refcount triggered mark-and-sweep => rc %ld", (long) rc));
-	}
-#endif  /* DUK_USE_VOLUNTARY_GC */
+/*
+ *  Refzero processing for duk_hstring.
+ */
+
+DUK_LOCAL DUK_INLINE void duk__refcount_refzero_hstring(duk_heap *heap, duk_hstring *str) {
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+	DUK_ASSERT(str != NULL);
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) str) == DUK_HTYPE_STRING);
+
+	duk_heap_strcache_string_remove(heap, str);
+	duk_heap_strtable_unlink(heap, str);
+	duk_free_hstring(heap, str);
+}
+
+/*
+ *  Refzero processing for duk_hbuffer.
+ */
+
+DUK_LOCAL DUK_INLINE void duk__refcount_refzero_hbuffer(duk_heap *heap, duk_hbuffer *buf) {
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) buf) == DUK_HTYPE_BUFFER);
+
+	DUK_HEAP_REMOVE_FROM_HEAP_ALLOCATED(heap, (duk_heaphdr *) buf);
+	duk_free_hbuffer(heap, buf);
 }
 
 /*
  *  Incref and decref functions.
  *
  *  Decref may trigger immediate refzero handling, which may free and finalize
- *  an arbitrary number of objects.
+ *  an arbitrary number of objects (a "DECREF cascade").
  *
  *  Refzero handling is skipped entirely if (1) mark-and-sweep is running or
  *  (2) execution is paused in the debugger.  The objects are left in the heap,
@@ -47031,46 +48743,67 @@
  *  mark-and-sweep also calls finalizers which would use the ordinary decref
  *  macros anyway.
  *
- *  The DUK__RZ_SUPPRESS_CHECK() must be enabled also when mark-and-sweep
- *  support has been disabled: the flag is also used in heap destruction when
- *  running finalizers for remaining objects, and the flag prevents objects
- *  from being moved around in heap linked lists.
- */
-
-/* The suppress condition is important to performance.  The flags being tested
- * are in the same duk_heap field so a single TEST instruction (on x86) tests
- * for them.
- */
+ *  We can't process refzeros (= free objects) when the debugger is running
+ *  as the debugger might make an object unreachable but still continue
+ *  inspecting it (or even cause it to be pushed back).  So we must rely on
+ *  mark-and-sweep to collect them.
+ *
+ *  The DUK__RZ_SUPPRESS_CHECK() condition is also used in heap destruction
+ *  when running finalizers for remaining objects: the flag prevents objects
+ *  from being moved around in heap linked lists while that's being done.
+ *
+ *  The suppress condition is important to performance.
+ */
+
+#define DUK__RZ_SUPPRESS_ASSERT1() do { \
+		DUK_ASSERT(thr != NULL); \
+		DUK_ASSERT(thr->heap != NULL); \
+		/* When mark-and-sweep runs, heap_thread must exist. */ \
+		DUK_ASSERT(thr->heap->ms_running == 0 || thr->heap->heap_thread != NULL); \
+		/* When mark-and-sweep runs, the 'thr' argument always matches heap_thread. \
+		 * This could be used to e.g. suppress check against 'thr' directly (and \
+		 * knowing it would be heap_thread); not really used now. \
+		 */ \
+		DUK_ASSERT(thr->heap->ms_running == 0 || thr == thr->heap->heap_thread); \
+		/* We may be called when the heap is initializing and we process \
+		 * refzeros normally, but mark-and-sweep and finalizers are prevented \
+		 * if that's the case. \
+		 */ \
+		DUK_ASSERT(thr->heap->heap_initializing == 0 || thr->heap->ms_prevent_count > 0); \
+		DUK_ASSERT(thr->heap->heap_initializing == 0 || thr->heap->pf_prevent_count > 0); \
+	} while (0)
+
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
-#define DUK__RZ_SUPPRESS_COND() \
-	(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap) || DUK_HEAP_IS_PAUSED(heap))
-#else
-#define DUK__RZ_SUPPRESS_COND() \
-	(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap))
-#endif
+#define DUK__RZ_SUPPRESS_ASSERT2() do { \
+		/* When debugger is paused, ms_running is set. */ \
+		DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap) || thr->heap->ms_running != 0); \
+	} while (0)
+#define DUK__RZ_SUPPRESS_COND()  (heap->ms_running != 0)
+#else
+#define DUK__RZ_SUPPRESS_ASSERT2() do { } while (0)
+#define DUK__RZ_SUPPRESS_COND()  (heap->ms_running != 0)
+#endif  /* DUK_USE_DEBUGGER_SUPPORT */
+
 #define DUK__RZ_SUPPRESS_CHECK() do { \
+		DUK__RZ_SUPPRESS_ASSERT1(); \
+		DUK__RZ_SUPPRESS_ASSERT2(); \
 		if (DUK_UNLIKELY(DUK__RZ_SUPPRESS_COND())) { \
-			DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h)); \
+			DUK_DDD(DUK_DDDPRINT("refzero handling suppressed (not even queued) when mark-and-sweep running, object: %p", (void *) h)); \
 			return; \
 		} \
 	} while (0)
 
 #define DUK__RZ_STRING() do { \
-		duk_heap_strcache_string_remove(thr->heap, (duk_hstring *) h); \
-		duk_heap_string_remove(heap, (duk_hstring *) h); \
-		duk_free_hstring(heap, (duk_hstring *) h); \
+		duk__refcount_refzero_hstring(heap, (duk_hstring *) h); \
 	} while (0)
 #define DUK__RZ_BUFFER() do { \
-		duk_heap_remove_any_from_heap_allocated(heap, (duk_heaphdr *) h); \
-		duk_free_hbuffer(heap, (duk_hbuffer *) h); \
+		duk__refcount_refzero_hbuffer(heap, (duk_hbuffer *) h); \
 	} while (0)
 #define DUK__RZ_OBJECT() do { \
-		duk_heap_remove_any_from_heap_allocated(heap, (duk_heaphdr *) h); \
-		duk__queue_refzero(heap, (duk_heaphdr *) h); \
-		if (!skip_free_pending) { \
-			duk_refzero_free_pending(thr); \
-		} \
-	} while (0)
+		duk__refcount_refzero_hobject(heap, (duk_hobject *) h, skip_free_pending); \
+	} while (0)
+
+/* XXX: test the effect of inlining here vs. NOINLINE in refzero helpers */
 #if defined(DUK_USE_FAST_REFCOUNT_DEFAULT)
 #define DUK__RZ_INLINE DUK_ALWAYS_INLINE
 #else
@@ -47140,42 +48873,39 @@
 		DUK__RZ_OBJECT();
 		break;
 
-	case DUK_HTYPE_BUFFER:
+	default:
 		/* Buffers have no internal references.  However, a dynamic
 		 * buffer has a separate allocation for the buffer.  This is
 		 * freed by duk_heap_free_heaphdr_raw().
 		 */
 
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h) == DUK_HTYPE_BUFFER);
 		DUK__RZ_BUFFER();
 		break;
-
-	default:
-		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
-		DUK_UNREACHABLE();
-	}
-}
-
-DUK_INTERNAL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) {
+	}
+}
+
+DUK_INTERNAL DUK_NOINLINE void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) {
 	duk__heaphdr_refzero_helper(thr, h, 0 /*skip_free_pending*/);
 }
 
-DUK_INTERNAL void duk_heaphdr_refzero_norz(duk_hthread *thr, duk_heaphdr *h) {
+DUK_INTERNAL DUK_NOINLINE void duk_heaphdr_refzero_norz(duk_hthread *thr, duk_heaphdr *h) {
 	duk__heaphdr_refzero_helper(thr, h, 1 /*skip_free_pending*/);
 }
 
-DUK_INTERNAL void duk_hstring_refzero(duk_hthread *thr, duk_hstring *h) {
+DUK_INTERNAL DUK_NOINLINE void duk_hstring_refzero(duk_hthread *thr, duk_hstring *h) {
 	duk__hstring_refzero_helper(thr, h);
 }
 
-DUK_INTERNAL void duk_hbuffer_refzero(duk_hthread *thr, duk_hbuffer *h) {
+DUK_INTERNAL DUK_NOINLINE void duk_hbuffer_refzero(duk_hthread *thr, duk_hbuffer *h) {
 	duk__hbuffer_refzero_helper(thr, h);
 }
 
-DUK_INTERNAL void duk_hobject_refzero(duk_hthread *thr, duk_hobject *h) {
+DUK_INTERNAL DUK_NOINLINE void duk_hobject_refzero(duk_hthread *thr, duk_hobject *h) {
 	duk__hobject_refzero_helper(thr, h, 0 /*skip_free_pending*/);
 }
 
-DUK_INTERNAL void duk_hobject_refzero_norz(duk_hthread *thr, duk_hobject *h) {
+DUK_INTERNAL DUK_NOINLINE void duk_hobject_refzero_norz(duk_hthread *thr, duk_hobject *h) {
 	duk__hobject_refzero_helper(thr, h, 1 /*skip_free_pending*/);
 }
 
@@ -47189,6 +48919,7 @@
 		DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
 		DUK_ASSERT_DISABLE(h->h_refcount >= 0);
 		DUK_HEAPHDR_PREINC_REFCOUNT(h);
+		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(h) != 0);  /* No wrapping. */
 	}
 }
 
@@ -47227,7 +48958,7 @@
 		}
 		duk_heaphdr_refzero_norz(thr, h);
 #else
-		duk_heaphdr_decref(thr, h);
+		duk_heaphdr_decref_norz(thr, h);
 #endif
 	}
 }
@@ -47246,6 +48977,7 @@
 			return; \
 		} \
 		DUK_HEAPHDR_PREINC_REFCOUNT((duk_heaphdr *) h); \
+		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) != 0);  /* No wrapping. */ \
 	} while (0)
 #define DUK__DECREF_SHARED() do { \
 		if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)) { \
@@ -47258,6 +48990,7 @@
 #else
 #define DUK__INCREF_SHARED() do { \
 		DUK_HEAPHDR_PREINC_REFCOUNT((duk_heaphdr *) h); \
+		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) != 0);  /* No wrapping. */ \
 	} while (0)
 #define DUK__DECREF_SHARED() do { \
 		if (DUK_HEAPHDR_PREDEC_REFCOUNT((duk_heaphdr *) h) != 0) { \
@@ -47282,6 +49015,11 @@
 	DUK__DECREF_ASSERTS();
 	DUK__DECREF_SHARED();
 	duk_heaphdr_refzero(thr, h);
+
+	/* Forced mark-and-sweep when GC torture enabled; this could happen
+	 * on any DECREF (but not DECREF_NORZ).
+	 */
+	DUK_GC_TORTURE(thr->heap);
 }
 DUK_INTERNAL void duk_heaphdr_decref_norz(duk_hthread *thr, duk_heaphdr *h) {
 	DUK__DECREF_ASSERTS();
@@ -47337,6 +49075,8 @@
 #undef DUK__RZ_INLINE
 #undef DUK__RZ_OBJECT
 #undef DUK__RZ_STRING
+#undef DUK__RZ_SUPPRESS_ASSERT1
+#undef DUK__RZ_SUPPRESS_ASSERT2
 #undef DUK__RZ_SUPPRESS_CHECK
 #undef DUK__RZ_SUPPRESS_COND
 #line 1 "duk_heap_stringcache.c"
@@ -47426,6 +49166,10 @@
  *
  *  Typing now assumes 32-bit string byte/char offsets (duk_uint_fast32_t).
  *  Better typing might be to use duk_size_t.
+ *
+ *  Caller should ensure 'char_offset' is within the string bounds [0,charlen]
+ *  (endpoint is inclusive).  If this is not the case, no memory unsafe
+ *  behavior will happen but an error will be thrown.
  */
 
 DUK_INTERNAL duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset) {
@@ -47435,20 +49179,27 @@
 	duk_small_int_t i;
 	duk_bool_t use_cache;
 	duk_uint_fast32_t dist_start, dist_end, dist_sce;
+	duk_uint_fast32_t char_length;
 	const duk_uint8_t *p_start;
 	const duk_uint8_t *p_end;
 	const duk_uint8_t *p_found;
 
-	if (char_offset > DUK_HSTRING_GET_CHARLEN(h)) {
-		goto error;
-	}
-
 	/*
 	 *  For ASCII strings, the answer is simple.
 	 */
 
-	if (DUK_HSTRING_IS_ASCII(h)) {
-		/* clen == blen -> pure ascii */
+	if (DUK_LIKELY(DUK_HSTRING_IS_ASCII(h))) {
+		return char_offset;
+	}
+
+	char_length = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h);
+	DUK_ASSERT(char_offset <= char_length);
+
+	if (DUK_LIKELY(DUK_HSTRING_IS_ASCII(h))) {
+		/* Must recheck because the 'is ascii' flag may be set
+		 * lazily.  Alternatively, we could just compare charlen
+		 * to bytelen.
+		 */
 		return char_offset;
 	}
 
@@ -47470,7 +49221,7 @@
 
 	heap = thr->heap;
 	sce = NULL;
-	use_cache = (DUK_HSTRING_GET_CHARLEN(h) > DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT);
+	use_cache = (char_length > DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT);
 
 	if (use_cache) {
 #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2)
@@ -47501,7 +49252,7 @@
 
 	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h) >= char_offset);
 	dist_start = char_offset;
-	dist_end = DUK_HSTRING_GET_CHARLEN(h) - char_offset;
+	dist_end = char_length - char_offset;
 	dist_sce = 0; DUK_UNREF(dist_sce);  /* initialize for debug prints, needed if sce==NULL */
 
 	p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
@@ -47574,12 +49325,12 @@
 
  scan_done:
 
-	if (!p_found) {
+	if (DUK_UNLIKELY(p_found == NULL)) {
 		/* Scan error: this shouldn't normally happen; it could happen if
 		 * string is not valid UTF-8 data, and clen/blen are not consistent
 		 * with the scanning algorithm.
 		 */
-		goto error;
+		goto scan_error;
 	}
 
 	DUK_ASSERT(p_found >= p_start);
@@ -47634,65 +49385,177 @@
 
 	return byte_offset;
 
- error:
+ scan_error:
 	DUK_ERROR_INTERNAL(thr);
 	return 0;
 }
 #line 1 "duk_heap_stringtable.c"
 /*
- *  Heap stringtable handling, string interning.
+ *  Heap string table handling, string interning.
  */
 
 /* #include duk_internal.h -> already included */
 
-#if defined(DUK_USE_STRTAB_PROBE)
-#define DUK__HASH_INITIAL(hash,h_size)        DUK_STRTAB_HASH_INITIAL((hash),(h_size))
-#define DUK__HASH_PROBE_STEP(hash)            DUK_STRTAB_HASH_PROBE_STEP((hash))
-#define DUK__DELETED_MARKER(heap)             DUK_STRTAB_DELETED_MARKER((heap))
-#endif
-
-#define DUK__PREVENT_MS_SIDE_EFFECTS(heap) do { \
-		(heap)->mark_and_sweep_base_flags |= \
-		        DUK_MS_FLAG_NO_STRINGTABLE_RESIZE |  /* avoid recursive string table call */ \
-		        DUK_MS_FLAG_NO_FINALIZERS |          /* avoid pressure to add/remove strings, invalidation of call data argument, etc. */ \
-		        DUK_MS_FLAG_NO_OBJECT_COMPACTION;    /* avoid array abandoning which interns strings */ \
-	} while (0)
-
-/*
- *  Create a hstring and insert into the heap.  The created object
- *  is directly garbage collectable with reference count zero.
- *
- *  The caller must place the interned string into the stringtable
- *  immediately (without chance of a longjmp); otherwise the string
- *  is lost.
- */
-
-DUK_LOCAL
-duk_hstring *duk__alloc_init_hstring(duk_heap *heap,
-                                     const duk_uint8_t *str,
-                                     duk_uint32_t blen,
-                                     duk_uint32_t strhash,
-                                     const duk_uint8_t *extdata) {
-	duk_hstring *res = NULL;
-	duk_uint8_t *data;
-	duk_size_t alloc_size;
+/* Resize checks not needed if minsize == maxsize, typical for low memory
+ * targets.
+ */
+#define DUK__STRTAB_RESIZE_CHECK
+#if (DUK_USE_STRTAB_MINSIZE == DUK_USE_STRTAB_MAXSIZE)
+#undef DUK__STRTAB_RESIZE_CHECK
+#endif
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+#define DUK__HEAPPTR_ENC16(heap,ptr)    DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (ptr))
+#define DUK__HEAPPTR_DEC16(heap,val)    DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (val))
+#define DUK__GET_STRTABLE(heap)         ((heap)->strtable16)
+#else
+#define DUK__HEAPPTR_ENC16(heap,ptr)    (ptr)
+#define DUK__HEAPPTR_DEC16(heap,val)    (val)
+#define DUK__GET_STRTABLE(heap)         ((heap)->strtable)
+#endif
+
+#define DUK__STRTAB_U32_MAX_STRLEN      10               /* 4'294'967'295 */
+
+/*
+ *  Debug dump stringtable.
+ */
+
+#if defined(DUK_USE_DEBUG)
+DUK_INTERNAL void duk_heap_strtable_dump(duk_heap *heap) {
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *strtable;
+#else
+	duk_hstring **strtable;
+#endif
+	duk_uint32_t i;
+	duk_hstring *h;
+	duk_size_t count_total = 0;
+	duk_size_t count_chain;
+	duk_size_t count_chain_min = DUK_SIZE_MAX;
+	duk_size_t count_chain_max = 0;
+	duk_size_t count_len[8];  /* chain lengths from 0 to 7 */
+
+	if (heap == NULL) {
+		DUK_D(DUK_DPRINT("string table, heap=NULL"));
+		return;
+	}
+
+	strtable = DUK__GET_STRTABLE(heap);
+	if (strtable == NULL) {
+		DUK_D(DUK_DPRINT("string table, strtab=NULL"));
+		return;
+	}
+
+	DUK_MEMZERO((void *) count_len, sizeof(count_len));
+	for (i = 0; i < heap->st_size; i++) {
+		h = DUK__HEAPPTR_DEC16(heap, strtable[i]);
+		count_chain = 0;
+		while (h != NULL) {
+			count_chain++;
+			h = h->hdr.h_next;
+		}
+		if (count_chain < sizeof(count_len) / sizeof(duk_size_t)) {
+			count_len[count_chain]++;
+		}
+		count_chain_max = (count_chain > count_chain_max ? count_chain : count_chain_max);
+		count_chain_min = (count_chain < count_chain_min ? count_chain : count_chain_min);
+		count_total += count_chain;
+	}
+
+	DUK_D(DUK_DPRINT("string table, strtab=%p, count=%lu, chain min=%lu max=%lu avg=%lf: "
+	                 "counts: %lu %lu %lu %lu %lu %lu %lu %lu ...",
+	                 (void *) heap->strtable, (unsigned long) count_total,
+	                 (unsigned long) count_chain_min, (unsigned long) count_chain_max,
+	                 (double) count_total / (double) heap->st_size,
+	                 (unsigned long) count_len[0], (unsigned long) count_len[1],
+	                 (unsigned long) count_len[2], (unsigned long) count_len[3],
+	                 (unsigned long) count_len[4], (unsigned long) count_len[5],
+	                 (unsigned long) count_len[6], (unsigned long) count_len[7]));
+}
+#endif  /* DUK_USE_DEBUG */
+
+/*
+ *  Assertion helper to ensure strtable is populated correctly.
+ */
+
+#if defined(DUK_USE_ASSERTIONS)
+DUK_LOCAL void duk__strtable_assert_checks(duk_heap *heap) {
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *strtable;
+#else
+	duk_hstring **strtable;
+#endif
+	duk_uint32_t i;
+	duk_hstring *h;
+	duk_size_t count = 0;
+
+	DUK_ASSERT(heap != NULL);
+
+	strtable = DUK__GET_STRTABLE(heap);
+	if (strtable != NULL) {
+		DUK_ASSERT(heap->st_size != 0);
+		DUK_ASSERT(heap->st_mask == heap->st_size - 1);
+
+		for (i = 0; i < heap->st_size; i++) {
+			h = DUK__HEAPPTR_DEC16(heap, strtable[i]);
+			while (h != NULL) {
+				DUK_ASSERT((DUK_HSTRING_GET_HASH(h) & heap->st_mask) == i);
+				count++;
+				h = h->hdr.h_next;
+			}
+		}
+	} else {
+		DUK_ASSERT(heap->st_size == 0);
+		DUK_ASSERT(heap->st_mask == 0);
+	}
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+	DUK_ASSERT(count == (duk_size_t) heap->st_count);
+#endif
+}
+#endif  /* DUK_USE_ASSERTIONS */
+
+/*
+ *  Allocate and initialize a duk_hstring.
+ *
+ *  Returns a NULL if allocation or initialization fails for some reason.
+ *
+ *  The string won't be inserted into the string table and isn't tracked in
+ *  any way (link pointers will be NULL).  The caller must place the string
+ *  into the string table without any risk of a longjmp, otherwise the string
+ *  is leaked.
+ */
+
+DUK_LOCAL duk_hstring *duk__strtable_alloc_hstring(duk_heap *heap,
+                                                   const duk_uint8_t *str,
+                                                   duk_uint32_t blen,
+                                                   duk_uint32_t strhash,
+                                                   const duk_uint8_t *extdata) {
+	duk_hstring *res;
+	const duk_uint8_t *data;
 #if !defined(DUK_USE_HSTRING_ARRIDX)
 	duk_uarridx_t dummy;
 #endif
-	duk_uint32_t clen;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_UNREF(extdata);
 
 #if defined(DUK_USE_STRLEN16)
 	/* If blen <= 0xffffUL, clen is also guaranteed to be <= 0xffffUL. */
 	if (blen > 0xffffUL) {
 		DUK_D(DUK_DPRINT("16-bit string blen/clen active and blen over 16 bits, reject intern"));
-		return NULL;
-	}
-#endif
-
+		goto alloc_error;
+	}
+#endif
+
+	/* XXX: Memzeroing the allocated structure is not really necessary
+	 * because we could just initialize all fields explicitly (almost
+	 * all fields are initialized explicitly anyway).
+	 */
+#if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK)
 	if (extdata) {
-		alloc_size = (duk_size_t) sizeof(duk_hstring_external);
-		res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
-		if (!res) {
+		res = (duk_hstring *) DUK_ALLOC(heap, sizeof(duk_hstring_external));
+		if (DUK_UNLIKELY(res == NULL)) {
 			goto alloc_error;
 		}
 		DUK_MEMZERO(res, sizeof(duk_hstring_external));
@@ -47701,12 +49564,18 @@
 #endif
 		DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, DUK_HSTRING_FLAG_EXTDATA);
 
+		DUK_ASSERT(extdata[blen] == 0);  /* Application responsibility. */
+		data = extdata;
 		((duk_hstring_external *) res)->extdata = extdata;
-	} else {
+	} else
+#endif  /* DUK_USE_HSTRING_EXTDATA && DUK_USE_EXTSTR_INTERN_CHECK */
+	{
+		duk_uint8_t *data_tmp;
+
 		/* NUL terminate for convenient C access */
-		alloc_size = (duk_size_t) (sizeof(duk_hstring) + blen + 1);
-		res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
-		if (!res) {
+		DUK_ASSERT(sizeof(duk_hstring) + blen + 1 > blen);  /* No wrap, limits ensure. */
+		res = (duk_hstring *) DUK_ALLOC(heap, sizeof(duk_hstring) + blen + 1);
+		if (DUK_UNLIKELY(res == NULL)) {
 			goto alloc_error;
 		}
 		DUK_MEMZERO(res, sizeof(duk_hstring));
@@ -47715,1090 +49584,787 @@
 #endif
 		DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0);
 
-		data = (duk_uint8_t *) (res + 1);
-		DUK_MEMCPY(data, str, blen);
-		data[blen] = (duk_uint8_t) 0;
-	}
+		data_tmp = (duk_uint8_t *) (res + 1);
+		DUK_MEMCPY(data_tmp, str, blen);
+		data_tmp[blen] = (duk_uint8_t) 0;
+		data = (const duk_uint8_t *) data_tmp;
+	}
+
+	DUK_HSTRING_SET_BYTELEN(res, blen);
+	DUK_HSTRING_SET_HASH(res, strhash);
 
 	DUK_ASSERT(!DUK_HSTRING_HAS_ARRIDX(res));
 #if defined(DUK_USE_HSTRING_ARRIDX)
-	if (duk_js_to_arrayindex_raw_string(str, blen, &res->arridx)) {
-#else
-	if (duk_js_to_arrayindex_raw_string(str, blen, &dummy)) {
-#endif
+	res->arridx = duk_js_to_arrayindex_string(data, blen);
+	if (res->arridx != DUK_HSTRING_NO_ARRAY_INDEX) {
+#else
+	dummy = duk_js_to_arrayindex_string(data, blen);
+	if (dummy != DUK_HSTRING_NO_ARRAY_INDEX) {
+#endif
+		/* Array index strings cannot be symbol strings,
+		 * and they're always pure ASCII so blen == clen.
+		 */
 		DUK_HSTRING_SET_ARRIDX(res);
-	}
-
-	/* All strings beginning with specific (invalid UTF-8) byte prefixes
-	 * are treated as symbols.
-	 */
-	DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(res));
-	DUK_ASSERT(!DUK_HSTRING_HAS_HIDDEN(res));
-	if (blen > 0) {
-		if (str[0] == 0xffU) {
-			DUK_HSTRING_SET_SYMBOL(res);
-			DUK_HSTRING_SET_HIDDEN(res);
-		} else if ((str[0] & 0xc0U) == 0x80U) {
-			DUK_HSTRING_SET_SYMBOL(res);
-		}
-	}
-
-	DUK_HSTRING_SET_HASH(res, strhash);
-	DUK_HSTRING_SET_BYTELEN(res, blen);
-
-	clen = (duk_uint32_t) duk_unicode_unvalidated_utf8_length(str, (duk_size_t) blen);
-	DUK_ASSERT(clen <= blen);
-#if defined(DUK_USE_HSTRING_CLEN)
-	DUK_HSTRING_SET_CHARLEN(res, clen);
-#endif
-
-	/* Using an explicit 'ASCII' flag has larger footprint (one call site
-	 * only) but is quite useful for the case when there's no explicit
-	 * 'clen' in duk_hstring.
-	 */
-	DUK_ASSERT(!DUK_HSTRING_HAS_ASCII(res));
-	if (clen == blen) {
 		DUK_HSTRING_SET_ASCII(res);
-	}
-
-	DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, clen=%ld, has_arridx=%ld, has_extdata=%ld",
+		DUK_ASSERT(duk_unicode_unvalidated_utf8_length(data, (duk_size_t) blen) == blen);
+	} else {
+		/* Because 'data' is NUL-terminated, we don't need a
+		 * blen > 0 check here.  For NUL (0x00) the symbol
+		 * checks will be false.
+		 */
+		if (DUK_UNLIKELY(data[0] >= 0x80U)) {
+			if (data[0] == 0xffU) {
+				DUK_HSTRING_SET_SYMBOL(res);
+				DUK_HSTRING_SET_HIDDEN(res);
+			} else if (data[0] <= 0xbf) {
+				/* Check equivalent to: (data[0] & 0xc0U) == 0x80U. */
+				DUK_HSTRING_SET_SYMBOL(res);
+			}
+		}
+
+		/* Using an explicit 'ASCII' flag has larger footprint (one call site
+		 * only) but is quite useful for the case when there's no explicit
+		 * 'clen' in duk_hstring.
+		 *
+		 * The flag is set lazily for RAM strings.
+		 */
+		DUK_ASSERT(!DUK_HSTRING_HAS_ASCII(res));
+	}
+
+	DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, has_arridx=%ld, has_extdata=%ld",
 	                     (unsigned long) DUK_HSTRING_GET_HASH(res),
 	                     (long) DUK_HSTRING_GET_BYTELEN(res),
-	                     (long) DUK_HSTRING_GET_CHARLEN(res),
 	                     (long) (DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0),
 	                     (long) (DUK_HSTRING_HAS_EXTDATA(res) ? 1 : 0)));
 
+	DUK_ASSERT(res != NULL);
 	return res;
 
  alloc_error:
-	DUK_FREE(heap, res);
 	return NULL;
 }
 
 /*
- *  String table algorithm: fixed size string table with array chaining
- *
- *  The top level string table has a fixed size, with each slot holding
- *  either NULL, string pointer, or pointer to a separately allocated
- *  string pointer list.
- *
- *  This is good for low memory environments using a pool allocator: the
- *  top level allocation has a fixed size and the pointer lists have quite
- *  small allocation size, which further matches the typical pool sizes
- *  needed by objects, strings, property tables, etc.
- */
-
-#if defined(DUK_USE_STRTAB_CHAIN)
-
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL duk_bool_t duk__insert_hstring_chain(duk_heap *heap, duk_hstring *h) {
-	duk_small_uint_t slotidx;
-	duk_strtab_entry *e;
-	duk_uint16_t *lst;
-	duk_uint16_t *new_lst;
-	duk_size_t i, n;
-	duk_uint16_t null16 = heap->heapptr_null16;
-	duk_uint16_t h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
-
-	DUK_ASSERT(heap != NULL);
-	DUK_ASSERT(h != NULL);
-
-	slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
-	DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
-
-	e = heap->strtable + slotidx;
-	if (e->listlen == 0) {
-		if (e->u.str16 == null16) {
-			e->u.str16 = h16;
-		} else {
-			/* Now two entries in the same slot, alloc list */
-			lst = (duk_uint16_t *) DUK_ALLOC(heap, sizeof(duk_uint16_t) * 2);
-			if (lst == NULL) {
-				return 1;  /* fail */
-			}
-			lst[0] = e->u.str16;
-			lst[1] = h16;
-			e->u.strlist16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) lst);
-			e->listlen = 2;
-		}
-	} else {
-		DUK_ASSERT(e->u.strlist16 != null16);
-		lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-		DUK_ASSERT(lst != NULL);
-		for (i = 0, n = e->listlen; i < n; i++) {
-			if (lst[i] == null16) {
-				lst[i] = h16;
-				return 0;
-			}
-		}
-
-		if (e->listlen + 1 == 0) {
-			/* Overflow, relevant mainly when listlen is 16 bits. */
-			return 1;  /* fail */
-		}
-
-		new_lst = (duk_uint16_t *) DUK_REALLOC(heap, lst, sizeof(duk_uint16_t) * (e->listlen + 1));
-		if (new_lst == NULL) {
-			return 1;  /* fail */
-		}
-		new_lst[e->listlen++] = h16;
-		e->u.strlist16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) new_lst);
-	}
-	return 0;
-}
-#else  /* DUK_USE_HEAPPTR16 */
-DUK_LOCAL duk_bool_t duk__insert_hstring_chain(duk_heap *heap, duk_hstring *h) {
-	duk_small_uint_t slotidx;
-	duk_strtab_entry *e;
-	duk_hstring **lst;
-	duk_hstring **new_lst;
-	duk_size_t i, n;
-
-	DUK_ASSERT(heap != NULL);
-	DUK_ASSERT(h != NULL);
-
-	slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
-	DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
-
-	e = heap->strtable + slotidx;
-	if (e->listlen == 0) {
-		if (e->u.str == NULL) {
-			e->u.str = h;
-		} else {
-			/* Now two entries in the same slot, alloc list */
-			lst = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * 2);
-			if (lst == NULL) {
-				return 1;  /* fail */
-			}
-			lst[0] = e->u.str;
-			lst[1] = h;
-			e->u.strlist = lst;
-			e->listlen = 2;
-		}
-	} else {
-		DUK_ASSERT(e->u.strlist != NULL);
-		lst = e->u.strlist;
-		for (i = 0, n = e->listlen; i < n; i++) {
-			if (lst[i] == NULL) {
-				lst[i] = h;
-				return 0;
-			}
-		}
-
-		if (e->listlen + 1 == 0) {
-			/* Overflow, relevant mainly when listlen is 16 bits. */
-			return 1;  /* fail */
-		}
-
-		new_lst = (duk_hstring **) DUK_REALLOC(heap, e->u.strlist, sizeof(duk_hstring *) * (e->listlen + 1));
-		if (new_lst == NULL) {
-			return 1;  /* fail */
-		}
-		new_lst[e->listlen++] = h;
-		e->u.strlist = new_lst;
-	}
-	return 0;
-}
-#endif  /* DUK_USE_HEAPPTR16 */
-
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL duk_hstring *duk__find_matching_string_chain(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
-	duk_small_uint_t slotidx;
-	duk_strtab_entry *e;
-	duk_uint16_t *lst;
-	duk_size_t i, n;
-	duk_uint16_t null16 = heap->heapptr_null16;
-
-	DUK_ASSERT(heap != NULL);
-
-	slotidx = strhash % DUK_STRTAB_CHAIN_SIZE;
-	DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
-
-	e = heap->strtable + slotidx;
-	if (e->listlen == 0) {
-		if (e->u.str16 != null16) {
-			duk_hstring *h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
-			DUK_ASSERT(h != NULL);
-			if (DUK_HSTRING_GET_BYTELEN(h) == blen &&
-			    DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
-				return h;
-			}
-		}
-	} else {
-		DUK_ASSERT(e->u.strlist16 != null16);
-		lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-		DUK_ASSERT(lst != NULL);
-		for (i = 0, n = e->listlen; i < n; i++) {
-			if (lst[i] != null16) {
-				duk_hstring *h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[i]);
-				DUK_ASSERT(h != NULL);
-				if (DUK_HSTRING_GET_BYTELEN(h) == blen &&
-				    DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
-					return h;
-				}
-			}
-		}
-	}
-
-	return NULL;
-}
-#else  /* DUK_USE_HEAPPTR16 */
-DUK_LOCAL duk_hstring *duk__find_matching_string_chain(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
-	duk_small_uint_t slotidx;
-	duk_strtab_entry *e;
-	duk_hstring **lst;
-	duk_size_t i, n;
-
-	DUK_ASSERT(heap != NULL);
-
-	slotidx = strhash % DUK_STRTAB_CHAIN_SIZE;
-	DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
-
-	e = heap->strtable + slotidx;
-	if (e->listlen == 0) {
-		if (e->u.str != NULL &&
-	           DUK_HSTRING_GET_BYTELEN(e->u.str) == blen &&
-	           DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(e->u.str), (size_t) blen) == 0) {
-			return e->u.str;
-		}
-	} else {
-		DUK_ASSERT(e->u.strlist != NULL);
-		lst = e->u.strlist;
-		for (i = 0, n = e->listlen; i < n; i++) {
-			if (lst[i] != NULL &&
-		           DUK_HSTRING_GET_BYTELEN(lst[i]) == blen &&
-		           DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(lst[i]), (size_t) blen) == 0) {
-				return lst[i];
-			}
-		}
-	}
-
-	return NULL;
-}
-#endif  /* DUK_USE_HEAPPTR16 */
-
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL void duk__remove_matching_hstring_chain(duk_heap *heap, duk_hstring *h) {
-	duk_small_uint_t slotidx;
-	duk_strtab_entry *e;
-	duk_uint16_t *lst;
-	duk_size_t i, n;
-	duk_uint16_t h16;
-	duk_uint16_t null16 = heap->heapptr_null16;
-
-	DUK_ASSERT(heap != NULL);
-	DUK_ASSERT(h != NULL);
-
-	slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
-	DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
-
-	DUK_ASSERT(h != NULL);
-	h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
-
-	e = heap->strtable + slotidx;
-	if (e->listlen == 0) {
-		if (e->u.str16 == h16) {
-			e->u.str16 = null16;
-			return;
-		}
-	} else {
-		DUK_ASSERT(e->u.strlist16 != null16);
-		lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-		DUK_ASSERT(lst != NULL);
-		for (i = 0, n = e->listlen; i < n; i++) {
-			if (lst[i] == h16) {
-				lst[i] = null16;
-				return;
-			}
-		}
-	}
-
-	DUK_D(DUK_DPRINT("failed to find string that should be in stringtable"));
-	DUK_UNREACHABLE();
-	return;
-}
-#else  /* DUK_USE_HEAPPTR16 */
-DUK_LOCAL void duk__remove_matching_hstring_chain(duk_heap *heap, duk_hstring *h) {
-	duk_small_uint_t slotidx;
-	duk_strtab_entry *e;
-	duk_hstring **lst;
-	duk_size_t i, n;
-
-	DUK_ASSERT(heap != NULL);
-	DUK_ASSERT(h != NULL);
-
-	slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
-	DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
-
-	e = heap->strtable + slotidx;
-	if (e->listlen == 0) {
-		DUK_ASSERT(h != NULL);
-		if (e->u.str == h) {
-			e->u.str = NULL;
-			return;
-		}
-	} else {
-		DUK_ASSERT(e->u.strlist != NULL);
-		lst = e->u.strlist;
-		for (i = 0, n = e->listlen; i < n; i++) {
-			DUK_ASSERT(h != NULL);
-			if (lst[i] == h) {
-				lst[i] = NULL;
-				return;
-			}
-		}
-	}
-
-	DUK_D(DUK_DPRINT("failed to find string that should be in stringtable"));
-	DUK_UNREACHABLE();
-	return;
-}
-#endif  /* DUK_USE_HEAPPTR16 */
-
-#if defined(DUK_USE_DEBUG)
-DUK_INTERNAL void duk_heap_dump_strtab(duk_heap *heap) {
-	duk_strtab_entry *e;
-	duk_small_uint_t i;
-	duk_size_t j, n, used;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t *lst;
-	duk_uint16_t null16 = heap->heapptr_null16;
-#else
-	duk_hstring **lst;
-#endif
+ *  Grow strtable allocation in-place.
+ */
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+DUK_LOCAL void duk__strtable_grow_inplace(duk_heap *heap) {
+	duk_uint32_t new_st_size;
+	duk_uint32_t old_st_size;
+	duk_uint32_t i;
+	duk_hstring *h;
+	duk_hstring *next;
+	duk_hstring *prev;
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *new_ptr;
+	duk_uint16_t *new_ptr_high;
+#else
+	duk_hstring **new_ptr;
+	duk_hstring **new_ptr_high;
+#endif
+
+	DUK_DD(DUK_DDPRINT("grow in-place: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size * 2));
 
 	DUK_ASSERT(heap != NULL);
-
-	for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
-		e = heap->strtable + i;
-
-		if (e->listlen == 0) {
-#if defined(DUK_USE_HEAPPTR16)
-			DUK_DD(DUK_DDPRINT("[%03d] -> plain %d", (int) i, (int) (e->u.str16 != null16 ? 1 : 0)));
-#else
-			DUK_DD(DUK_DDPRINT("[%03d] -> plain %d", (int) i, (int) (e->u.str ? 1 : 0)));
-#endif
-		} else {
-			used = 0;
-#if defined(DUK_USE_HEAPPTR16)
-			lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-#else
-			lst = e->u.strlist;
-#endif
-			DUK_ASSERT(lst != NULL);
-			for (j = 0, n = e->listlen; j < n; j++) {
-#if defined(DUK_USE_HEAPPTR16)
-				if (lst[j] != null16) {
-#else
-				if (lst[j] != NULL) {
-#endif
-					used++;
-				}
-			}
-			DUK_DD(DUK_DDPRINT("[%03d] -> array %d/%d", (int) i, (int) used, (int) e->listlen));
-		}
-	}
-}
-#endif  /* DUK_USE_DEBUG */
-
-#endif  /* DUK_USE_STRTAB_CHAIN */
-
-/*
- *  String table algorithm: closed hashing with a probe sequence
- *
- *  This is the default algorithm and works fine for environments with
- *  minimal memory constraints.
- */
-
-#if defined(DUK_USE_STRTAB_PROBE)
-
-/* Count actually used (non-NULL, non-DELETED) entries. */
-DUK_LOCAL duk_int_t duk__count_used_probe(duk_heap *heap) {
-	duk_int_t res = 0;
-	duk_uint_fast32_t i, n;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t null16 = heap->heapptr_null16;
-	duk_uint16_t deleted16 = heap->heapptr_deleted16;
-#endif
-
-	n = (duk_uint_fast32_t) heap->st_size;
-	for (i = 0; i < n; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-		if (heap->strtable16[i] != null16 && heap->strtable16[i] != deleted16) {
-#else
-		if (heap->strtable[i] != NULL && heap->strtable[i] != DUK__DELETED_MARKER(heap)) {
-#endif
-			res++;
-		}
-	}
-	return res;
-}
-
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL void duk__insert_hstring_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) {
-#else
-DUK_LOCAL void duk__insert_hstring_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) {
-#endif
-	duk_uint32_t i;
-	duk_uint32_t step;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t null16 = heap->heapptr_null16;
-	duk_uint16_t deleted16 = heap->heapptr_deleted16;
-#endif
-
-	DUK_ASSERT(size > 0);
-
-	i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size);
-	step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(h));
-	for (;;) {
-#if defined(DUK_USE_HEAPPTR16)
-		duk_uint16_t e16 = entries16[i];
-#else
-		duk_hstring *e = entries[i];
-#endif
-
-#if defined(DUK_USE_HEAPPTR16)
-		/* XXX: could check for e16 == 0 because NULL is guaranteed to
-		 * encode to zero.
-		 */
-		if (e16 == null16) {
-#else
-		if (e == NULL) {
-#endif
-			DUK_DDD(DUK_DDDPRINT("insert hit (null): %ld", (long) i));
-#if defined(DUK_USE_HEAPPTR16)
-			entries16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
-#else
-			entries[i] = h;
-#endif
-			(*p_used)++;
-			break;
-#if defined(DUK_USE_HEAPPTR16)
-		} else if (e16 == deleted16) {
-#else
-		} else if (e == DUK__DELETED_MARKER(heap)) {
-#endif
-			/* st_used remains the same, DELETED is counted as used */
-			DUK_DDD(DUK_DDDPRINT("insert hit (deleted): %ld", (long) i));
-#if defined(DUK_USE_HEAPPTR16)
-			entries16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
-#else
-			entries[i] = h;
-#endif
-			break;
-		}
-		DUK_DDD(DUK_DDDPRINT("insert miss: %ld", (long) i));
-		i = (i + step) % size;
-
-		/* looping should never happen */
-		DUK_ASSERT(i != DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size));
-	}
-}
-
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL duk_hstring *duk__find_matching_string_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
-#else
-DUK_LOCAL duk_hstring *duk__find_matching_string_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
-#endif
-	duk_uint32_t i;
-	duk_uint32_t step;
-
-	DUK_ASSERT(size > 0);
-
-	i = DUK__HASH_INITIAL(strhash, size);
-	step = DUK__HASH_PROBE_STEP(strhash);
-	for (;;) {
-		duk_hstring *e;
-#if defined(DUK_USE_HEAPPTR16)
-		e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, entries16[i]);
-#else
-		e = entries[i];
-#endif
-
-		if (!e) {
-			return NULL;
-		}
-		if (e != DUK__DELETED_MARKER(heap) && DUK_HSTRING_GET_BYTELEN(e) == blen) {
-			if (DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(e), (size_t) blen) == 0) {
-				DUK_DDD(DUK_DDDPRINT("find matching hit: %ld (step %ld, size %ld)",
-				                     (long) i, (long) step, (long) size));
-				return e;
-			}
-		}
-		DUK_DDD(DUK_DDDPRINT("find matching miss: %ld (step %ld, size %ld)",
-		                     (long) i, (long) step, (long) size));
-		i = (i + step) % size;
-
-		/* looping should never happen */
-		DUK_ASSERT(i != DUK__HASH_INITIAL(strhash, size));
-	}
-	DUK_UNREACHABLE();
-}
-
-#if defined(DUK_USE_HEAPPTR16)
-DUK_LOCAL void duk__remove_matching_hstring_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, duk_hstring *h) {
-#else
-DUK_LOCAL void duk__remove_matching_hstring_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_hstring *h) {
-#endif
-	duk_uint32_t i;
-	duk_uint32_t step;
-	duk_uint32_t hash;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t null16 = heap->heapptr_null16;
-	duk_uint16_t h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
-#endif
-
-	DUK_ASSERT(size > 0);
-
-	hash = DUK_HSTRING_GET_HASH(h);
-	i = DUK__HASH_INITIAL(hash, size);
-	step = DUK__HASH_PROBE_STEP(hash);
-	for (;;) {
-#if defined(DUK_USE_HEAPPTR16)
-		duk_uint16_t e16 = entries16[i];
-#else
-		duk_hstring *e = entries[i];
-#endif
-
-#if defined(DUK_USE_HEAPPTR16)
-		if (e16 == null16) {
-#else
-		if (!e) {
-#endif
-			DUK_UNREACHABLE();
-			break;
-		}
-#if defined(DUK_USE_HEAPPTR16)
-		if (e16 == h16) {
-#else
-		if (e == h) {
-#endif
-			/* st_used remains the same, DELETED is counted as used */
-			DUK_DDD(DUK_DDDPRINT("free matching hit: %ld", (long) i));
-#if defined(DUK_USE_HEAPPTR16)
-			entries16[i] = heap->heapptr_deleted16;
-#else
-			entries[i] = DUK__DELETED_MARKER(heap);
-#endif
-			break;
-		}
-
-		DUK_DDD(DUK_DDDPRINT("free matching miss: %ld", (long) i));
-		i = (i + step) % size;
-
-		/* looping should never happen */
-		DUK_ASSERT(i != DUK__HASH_INITIAL(hash, size));
-	}
-}
-
-DUK_LOCAL duk_bool_t duk__resize_strtab_raw_probe(duk_heap *heap, duk_uint32_t new_size) {
-#if defined(DUK_USE_DEBUG)
-	duk_uint32_t old_used = heap->st_used;
-#endif
-	duk_uint32_t old_size = heap->st_size;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t *old_entries = heap->strtable16;
-	duk_uint16_t *new_entries = NULL;
-#else
-	duk_hstring **old_entries = heap->strtable;
-	duk_hstring **new_entries = NULL;
-#endif
-	duk_uint32_t new_used = 0;
-	duk_uint32_t i;
-
-#if defined(DUK_USE_DEBUG)
-	DUK_UNREF(old_used);  /* unused with some debug level combinations */
-#endif
-
-#if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2)
-	DUK_DDD(DUK_DDDPRINT("attempt to resize stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load",
-	                     (long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used,
-	                     (long) (((double) old_used) / ((double) old_size) * 100.0),
-	                     (long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) duk__count_used_probe(heap),
-	                     (long) (((double) duk__count_used_probe(heap)) / ((double) new_size) * 100.0)));
-#endif
-
-	DUK_ASSERT(new_size > (duk_uint32_t) duk__count_used_probe(heap));  /* required for rehash to succeed, equality not that useful */
-	DUK_ASSERT(old_entries);
-
-	/*
-	 *  The attempt to allocate may cause a GC.  Such a GC must not attempt to resize
-	 *  the stringtable (though it can be swept); finalizer execution and object
-	 *  compaction must also be postponed to avoid the pressure to add strings to the
-	 *  string table.  Call site must prevent these.
-	 */
-
-	DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE);
-	DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_FINALIZERS);
-	DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION);
-
-#if defined(DUK_USE_HEAPPTR16)
-	new_entries = (duk_uint16_t *) DUK_ALLOC(heap, sizeof(duk_uint16_t) * new_size);
-#else
-	new_entries = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * new_size);
-#endif
-
-	if (!new_entries) {
-		goto resize_error;
-	}
-
-#if defined(DUK_USE_EXPLICIT_NULL_INIT)
-	for (i = 0; i < new_size; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-		new_entries[i] = heap->heapptr_null16;
-#else
-		new_entries[i] = NULL;
-#endif
-	}
-#else
-#if defined(DUK_USE_HEAPPTR16)
-	/* Relies on NULL encoding to zero. */
-	DUK_MEMZERO(new_entries, sizeof(duk_uint16_t) * new_size);
-#else
-	DUK_MEMZERO(new_entries, sizeof(duk_hstring *) * new_size);
-#endif
-#endif
-
-	/* Because new_size > duk__count_used_probe(heap), guaranteed to work */
-	for (i = 0; i < old_size; i++) {
-		duk_hstring *e;
-
-#if defined(DUK_USE_HEAPPTR16)
-		e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, old_entries[i]);
-#else
-		e = old_entries[i];
-#endif
-		if (e == NULL || e == DUK__DELETED_MARKER(heap)) {
-			continue;
-		}
-		/* checking for DUK__DELETED_MARKER is not necessary here, but helper does it now */
-		duk__insert_hstring_probe(heap, new_entries, new_size, &new_used, e);
-	}
-
-#if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 1)
-	DUK_DD(DUK_DDPRINT("resized stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load",
-	                   (long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used,
-	                   (long) (((double) old_used) / ((double) old_size) * 100.0),
-	                   (long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) new_used,
-	                   (long) (((double) new_used) / ((double) new_size) * 100.0)));
-#endif
-
-#if defined(DUK_USE_HEAPPTR16)
-	DUK_FREE(heap, heap->strtable16);
-	heap->strtable16 = new_entries;
-#else
-	DUK_FREE(heap, heap->strtable);
-	heap->strtable = new_entries;
-#endif
-	heap->st_size = new_size;
-	heap->st_used = new_used;  /* may be less, since DELETED entries are NULLed by rehash */
-
-	return 0;  /* OK */
-
- resize_error:
-	DUK_FREE(heap, new_entries);
-	return 1;  /* FAIL */
-}
-
-DUK_LOCAL duk_bool_t duk__resize_strtab_probe(duk_heap *heap) {
-	duk_uint32_t new_size;
-	duk_bool_t ret;
-
-	new_size = (duk_uint32_t) duk__count_used_probe(heap);
-	if (new_size >= 0x80000000UL) {
-		new_size = DUK_STRTAB_HIGHEST_32BIT_PRIME;
-	} else {
-		new_size = duk_util_get_hash_prime(DUK_STRTAB_GROW_ST_SIZE(new_size));
-		new_size = duk_util_get_hash_prime(new_size);
-	}
-	DUK_ASSERT(new_size > 0);
-
-	/* rehash even if old and new sizes are the same to get rid of
-	 * DELETED entries.
-	*/
-
-	ret = duk__resize_strtab_raw_probe(heap, new_size);
-
-	return ret;
-}
-
-DUK_LOCAL duk_bool_t duk__recheck_strtab_size_probe(duk_heap *heap, duk_uint32_t new_used) {
-	duk_uint32_t new_free;
-	duk_uint32_t tmp1;
-	duk_uint32_t tmp2;
-
-	DUK_ASSERT(new_used <= heap->st_size);  /* grow by at most one */
-	new_free = heap->st_size - new_used;    /* unsigned intentionally */
-
-	/* new_free / size <= 1 / DIV  <=>  new_free <= size / DIV */
-	/* new_used / size <= 1 / DIV  <=>  new_used <= size / DIV */
-
-	tmp1 = heap->st_size / DUK_STRTAB_MIN_FREE_DIVISOR;
-	tmp2 = heap->st_size / DUK_STRTAB_MIN_USED_DIVISOR;
-
-	if (new_free <= tmp1 || new_used <= tmp2) {
-		/* load factor too low or high, count actually used entries and resize */
-		return duk__resize_strtab_probe(heap);
-	} else {
-		return 0;  /* OK */
-	}
-}
-
-#if defined(DUK_USE_DEBUG)
-DUK_INTERNAL void duk_heap_dump_strtab(duk_heap *heap) {
+	DUK_ASSERT(heap->st_resizing == 1);
+	DUK_ASSERT(heap->st_size >= 2);
+	DUK_ASSERT((heap->st_size & (heap->st_size - 1)) == 0);  /* 2^N */
+	DUK_ASSERT(DUK__GET_STRTABLE(heap) != NULL);
+
+	new_st_size = heap->st_size << 1U;
+	DUK_ASSERT(new_st_size > heap->st_size);  /* No overflow. */
+
+	/* Reallocate the strtable first and then work in-place to rehash
+	 * strings.  We don't need an indirect allocation here: even if GC
+	 * is triggered to satisfy the allocation, recursive strtable resize
+	 * is prevented by flags.  This is also why we don't need to use
+	 * DUK_REALLOC_INDIRECT().
+	 */
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	new_ptr = (duk_uint16_t *) DUK_REALLOC(heap, heap->strtable16, sizeof(duk_uint16_t) * new_st_size);
+#else
+	new_ptr = (duk_hstring **) DUK_REALLOC(heap, heap->strtable, sizeof(duk_hstring *) * new_st_size);
+#endif
+	if (DUK_UNLIKELY(new_ptr == NULL)) {
+		/* If realloc fails we can continue normally: the string table
+		 * won't "fill up" although chains will gradually get longer.
+		 * When string insertions continue, we'll quite soon try again
+		 * with no special handling.
+		 */
+		DUK_D(DUK_DPRINT("string table grow failed, ignoring"));
+		return;
+	}
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	heap->strtable16 = new_ptr;
+#else
+	heap->strtable = new_ptr;
+#endif
+
+	/* Rehash a single bucket into two separate ones.  When we grow
+	 * by x2 the highest 'new' bit determines whether a string remains
+	 * in its old position (bit is 0) or goes to a new one (bit is 1).
+	 */
+
+	old_st_size = heap->st_size;
+	new_ptr_high = new_ptr + old_st_size;
+	for (i = 0; i < old_st_size; i++) {
+		duk_hstring *new_root;
+		duk_hstring *new_root_high;
+
+		h = DUK__HEAPPTR_DEC16(heap, new_ptr[i]);
+		new_root = h;
+		new_root_high = NULL;
+
+		prev = NULL;
+		while (h != NULL) {
+			duk_uint32_t mask;
+
+			DUK_ASSERT((DUK_HSTRING_GET_HASH(h) & heap->st_mask) == i);
+			next = h->hdr.h_next;
+
+			/* Example: if previous size was 256, previous mask is 0xFF
+			 * and size is 0x100 which corresponds to the new bit that
+			 * comes into play.
+			 */
+			DUK_ASSERT(heap->st_mask == old_st_size - 1);
+			mask = old_st_size;
+			if (DUK_HSTRING_GET_HASH(h) & mask) {
+				if (prev != NULL) {
+					prev->hdr.h_next = h->hdr.h_next;
+				} else {
+					DUK_ASSERT(h == new_root);
+					new_root = h->hdr.h_next;
+				}
+
+				h->hdr.h_next = new_root_high;
+				new_root_high = h;
+			} else {
+				prev = h;
+			}
+			h = next;
+		}
+
+		new_ptr[i] = DUK__HEAPPTR_ENC16(heap, new_root);
+		new_ptr_high[i] = DUK__HEAPPTR_ENC16(heap, new_root_high);
+	}
+
+	heap->st_size = new_st_size;
+	heap->st_mask = new_st_size - 1;
+
+#if defined(DUK_USE_ASSERTIONS)
+	duk__strtable_assert_checks(heap);
+#endif
+}
+#endif  /* DUK__STRTAB_RESIZE_CHECK */
+
+/*
+ *  Shrink strtable allocation in-place.
+ */
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+DUK_LOCAL void duk__strtable_shrink_inplace(duk_heap *heap) {
+	duk_uint32_t new_st_size;
 	duk_uint32_t i;
 	duk_hstring *h;
+	duk_hstring *other;
+	duk_hstring *root;
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *old_ptr;
+	duk_uint16_t *old_ptr_high;
+	duk_uint16_t *new_ptr;
+#else
+	duk_hstring **old_ptr;
+	duk_hstring **old_ptr_high;
+	duk_hstring **new_ptr;
+#endif
+
+	DUK_DD(DUK_DDPRINT("shrink in-place: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size / 2));
 
 	DUK_ASSERT(heap != NULL);
-#if defined(DUK_USE_HEAPPTR16)
+	DUK_ASSERT(heap->st_resizing == 1);
+	DUK_ASSERT(heap->st_size >= 2);
+	DUK_ASSERT((heap->st_size & (heap->st_size - 1)) == 0);  /* 2^N */
+	DUK_ASSERT(DUK__GET_STRTABLE(heap) != NULL);
+
+	new_st_size = heap->st_size >> 1U;
+
+	/* Combine two buckets into a single one.  When we shrink, one hash
+	 * bit (highest) disappears.
+	 */
+	old_ptr = DUK__GET_STRTABLE(heap);
+	old_ptr_high = old_ptr + new_st_size;
+	for (i = 0; i < new_st_size; i++) {
+		h = DUK__HEAPPTR_DEC16(heap, old_ptr[i]);
+		other = DUK__HEAPPTR_DEC16(heap, old_ptr_high[i]);
+
+		if (h == NULL) {
+			/* First chain is empty, so use second one as is. */
+			root = other;
+		} else {
+			/* Find end of first chain, and link in the second. */
+			root = h;
+			while (h->hdr.h_next != NULL) {
+				h = h->hdr.h_next;
+			}
+			h->hdr.h_next = other;
+		}
+
+		old_ptr[i] = DUK__HEAPPTR_ENC16(heap, root);
+	}
+
+	heap->st_size = new_st_size;
+	heap->st_mask = new_st_size - 1;
+
+	/* The strtable is now consistent and we can realloc safely.  Even
+	 * if side effects cause string interning or removal the strtable
+	 * updates are safe.  Recursive resize has been prevented by caller.
+	 * This is also why we don't need to use DUK_REALLOC_INDIRECT().
+	 *
+	 * We assume a realloc() to a smaller size is guaranteed to succeed.
+	 * It would be relatively straightforward to handle the error by
+	 * essentially performing a "grow" step to recover.
+	 */
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	new_ptr = (duk_uint16_t *) DUK_REALLOC(heap, heap->strtable16, sizeof(duk_uint16_t) * new_st_size);
+	DUK_ASSERT(new_ptr != NULL);
+	heap->strtable16 = new_ptr;
+#else
+	new_ptr = (duk_hstring **) DUK_REALLOC(heap, heap->strtable, sizeof(duk_hstring *) * new_st_size);
+	DUK_ASSERT(new_ptr != NULL);
+	heap->strtable = new_ptr;
+#endif
+
+#if defined(DUK_USE_ASSERTIONS)
+	duk__strtable_assert_checks(heap);
+#endif
+}
+#endif  /* DUK__STRTAB_RESIZE_CHECK */
+
+/*
+ *  Grow/shrink check.
+ */
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+DUK_LOCAL DUK_COLD DUK_NOINLINE void duk__strtable_resize_check(duk_heap *heap) {
+	duk_uint32_t load_factor;  /* fixed point */
+
+	DUK_ASSERT(heap != NULL);
+#if defined(DUK_USE_STRTAB_PTRCOMP)
 	DUK_ASSERT(heap->strtable16 != NULL);
 #else
 	DUK_ASSERT(heap->strtable != NULL);
 #endif
-	DUK_UNREF(h);
-
-	for (i = 0; i < heap->st_size; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-		h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]);
-#else
-		h = heap->strtable[i];
-#endif
-
-		DUK_DD(DUK_DDPRINT("[%03d] -> %p", (int) i, (void *) h));
-	}
-}
-#endif  /* DUK_USE_DEBUG */
-
-#endif  /* DUK_USE_STRTAB_PROBE */
-
-/*
- *  Raw intern and lookup
- */
-
-DUK_LOCAL duk_hstring *duk__do_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
+
+	/* Prevent recursive resizing. */
+	if (DUK_UNLIKELY(heap->st_resizing)) {
+		DUK_D(DUK_DPRINT("prevent recursive strtable resize"));
+		return;
+	}
+
+	heap->st_resizing = 1;
+
+	DUK_ASSERT(heap->st_size >= 16U);
+	DUK_ASSERT((heap->st_size >> 4U) >= 1);
+	load_factor = heap->st_count / (heap->st_size >> 4U);
+
+	DUK_DD(DUK_DDPRINT("resize check string table: size=%lu, count=%lu, load_factor=%lu (fixed point .4; float %lf)",
+	                   (unsigned long) heap->st_size, (unsigned long) heap->st_count,
+	                   (unsigned long) load_factor,
+	                   (double) heap->st_count / (double) heap->st_size));
+
+	if (load_factor >= DUK_USE_STRTAB_GROW_LIMIT) {
+		if (heap->st_size >= DUK_USE_STRTAB_MAXSIZE) {
+			DUK_DD(DUK_DDPRINT("want to grow strtable (based on load factor) but already maximum size"));
+		} else {
+			DUK_D(DUK_DPRINT("grow string table: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size * 2));
+#if defined(DUK_USE_DEBUG)
+			duk_heap_strtable_dump(heap);
+#endif
+			duk__strtable_grow_inplace(heap);
+		}
+	} else if (load_factor <= DUK_USE_STRTAB_SHRINK_LIMIT) {
+		if (heap->st_size <= DUK_USE_STRTAB_MINSIZE) {
+			DUK_DD(DUK_DDPRINT("want to shrink strtable (based on load factor) but already minimum size"));
+		} else {
+			DUK_D(DUK_DPRINT("shrink string table: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size / 2));
+#if defined(DUK_USE_DEBUG)
+			duk_heap_strtable_dump(heap);
+#endif
+			duk__strtable_shrink_inplace(heap);
+		}
+	} else {
+		DUK_DD(DUK_DDPRINT("no need for strtable resize"));
+	}
+
+	heap->st_resizing = 0;
+}
+#endif  /* DUK__STRTAB_RESIZE_CHECK */
+
+/*
+ *  Torture grow/shrink: unconditionally grow and shrink back.
+ */
+
+#if defined(DUK_USE_STRTAB_TORTURE) && defined(DUK__STRTAB_RESIZE_CHECK)
+DUK_LOCAL void duk__strtable_resize_torture(duk_heap *heap) {
+	duk_uint32_t old_st_size;
+
+	DUK_ASSERT(heap != NULL);
+
+	old_st_size = heap->st_size;
+	if (old_st_size >= DUK_USE_STRTAB_MAXSIZE) {
+		return;
+	}
+
+	heap->st_resizing = 1;
+	duk__strtable_grow_inplace(heap);
+	if (heap->st_size > old_st_size) {
+		duk__strtable_shrink_inplace(heap);
+	}
+	heap->st_resizing = 0;
+}
+#endif  /* DUK_USE_STRTAB_TORTURE && DUK__STRTAB_RESIZE_CHECK */
+
+/*
+ *  Raw intern; string already checked not to be present.
+ */
+
+DUK_LOCAL duk_hstring *duk__strtable_do_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
 	duk_hstring *res;
 	const duk_uint8_t *extdata;
-	duk_small_uint_t prev_mark_and_sweep_base_flags;
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *slot;
+#else
+	duk_hstring **slot;
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("do_intern: heap=%p, str=%p, blen=%lu, strhash=%lx, st_size=%lu, st_count=%lu, load=%lf",
+	                     (void *) heap, (const void *) str, (unsigned long) blen, (unsigned long) strhash,
+	                     (unsigned long) heap->st_size, (unsigned long) heap->st_count,
+	                     (double) heap->st_count / (double) heap->st_size));
+
+	DUK_ASSERT(heap != NULL);
 
 	/* Prevent any side effects on the string table and the caller provided
 	 * str/blen arguments while interning is in progress.  For example, if
-	 * the caller provided str/blen from a dynamic buffer, a finalizer might
-	 * resize that dynamic buffer, invalidating the call arguments.
-	 */
-	DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
-	prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
-	DUK__PREVENT_MS_SIDE_EFFECTS(heap);
-
-#if defined(DUK_USE_STRTAB_PROBE)
-	if (duk__recheck_strtab_size_probe(heap, heap->st_used + 1)) {
-		goto failed;
-	}
-#endif
+	 * the caller provided str/blen from a dynamic buffer, a finalizer
+	 * might resize or modify that dynamic buffer, invalidating the call
+	 * arguments.
+	 *
+	 * While finalizers must be prevented, mark-and-sweep itself is fine.
+	 * Recursive string table resize is prevented explicitly here.
+	 */
+
+	heap->pf_prevent_count++;
+	DUK_ASSERT(heap->pf_prevent_count != 0);  /* Wrap. */
+
+#if defined(DUK_USE_STRTAB_TORTURE) && defined(DUK__STRTAB_RESIZE_CHECK)
+	duk__strtable_resize_torture(heap);
+#endif
+
+	/* String table grow/shrink check.  Because of chaining (and no
+	 * accumulation issues as with hash probe chains and DELETED
+	 * markers) there's never a mandatory need to resize right now.
+	 * Check for the resize only periodically, based on st_count
+	 * bit pattern.  Because string table removal doesn't do a shrink
+	 * check, we do that also here.
+	 *
+	 * Do the resize and possible grow/shrink before the new duk_hstring
+	 * has been allocated.  Otherwise we may trigger a GC when the result
+	 * duk_hstring is not yet strongly referenced.
+	 */
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+	if (DUK_UNLIKELY((heap->st_count & DUK_USE_STRTAB_RESIZE_CHECK_MASK) == 0)) {
+		duk__strtable_resize_check(heap);
+	}
+#endif
+
+	/* External string check (low memory optimization). */
 
 #if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK)
 	extdata = (const duk_uint8_t *) DUK_USE_EXTSTR_INTERN_CHECK(heap->heap_udata, (void *) DUK_LOSE_CONST(str), (duk_size_t) blen);
 #else
 	extdata = (const duk_uint8_t *) NULL;
 #endif
-	res = duk__alloc_init_hstring(heap, str, blen, strhash, extdata);
-	if (!res) {
-		goto failed;
-	}
-
-#if defined(DUK_USE_STRTAB_CHAIN)
-	if (duk__insert_hstring_chain(heap, res)) {
-		/* failed */
-		DUK_FREE(heap, res);
-		goto failed;
-	}
-#elif defined(DUK_USE_STRTAB_PROBE)
-	/* guaranteed to succeed */
-	duk__insert_hstring_probe(heap,
-#if defined(DUK_USE_HEAPPTR16)
-	                          heap->strtable16,
-#else
-	                          heap->strtable,
-#endif
-	                          heap->st_size,
-	                          &heap->st_used,
-	                          res);
-#else
-#error internal error, invalid strtab options
-#endif
-
-	/* Note: hstring is in heap but has refcount zero and is not strongly reachable.
-	 * Caller should increase refcount and make the hstring reachable before any
-	 * operations which require allocation (and possible gc).
-	 */
-
- done:
-	heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+
+	/* Allocate and initialize string, not yet linked.  This may cause a
+	 * GC which may cause other strings to be interned and inserted into
+	 * the string table before we insert our string.  Finalizer execution
+	 * is disabled intentionally to avoid a finalizer from e.g. resizing
+	 * a buffer used as a data area for 'str'.
+	 */
+
+	res = duk__strtable_alloc_hstring(heap, str, blen, strhash, extdata);
+
+	/* Allow side effects again: GC must be avoided until duk_hstring
+	 * result (if successful) has been INCREF'd.
+	 */
+	DUK_ASSERT(heap->pf_prevent_count > 0);
+	heap->pf_prevent_count--;
+
+	/* Alloc error handling. */
+
+	if (DUK_UNLIKELY(res == NULL)) {
+#if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK)
+		if (extdata != NULL) {
+			DUK_USE_EXTSTR_FREE(heap->heap_udata, (const void *) extdata);
+		}
+#endif
+		return NULL;
+	}
+
+	/* Insert into string table. */
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	slot = heap->strtable16 + (strhash & heap->st_mask);
+#else
+	slot = heap->strtable + (strhash & heap->st_mask);
+#endif
+	DUK_ASSERT(res->hdr.h_next == NULL);  /* This is the case now, but unnecessary zeroing/NULLing. */
+	res->hdr.h_next = DUK__HEAPPTR_DEC16(heap, *slot);
+	*slot = DUK__HEAPPTR_ENC16(heap, res);
+
+	/* Update string count only for successful inserts. */
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+	heap->st_count++;
+#endif
+
+	/* The duk_hstring is in the string table but is not yet strongly
+	 * reachable.  Calling code MUST NOT make any allocations or other
+	 * side effects before the duk_hstring has been INCREF'd and made
+	 * reachable.
+	 */
+
 	return res;
-
- failed:
-	res = NULL;
-	goto done;
-}
-
-DUK_LOCAL duk_hstring *duk__do_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t *out_strhash) {
-	duk_hstring *res;
-
-	DUK_ASSERT(out_strhash);
-
-	*out_strhash = duk_heap_hashstring(heap, str, (duk_size_t) blen);
+}
+
+/*
+ *  Intern a string from str/blen, returning either an existing duk_hstring
+ *  or adding a new one into the string table.  The input string does -not-
+ *  need to be NUL terminated.
+ *
+ *  The input 'str' argument may point to a Duktape managed data area such as
+ *  the data area of a dynamic buffer.  It's crucial to avoid any side effects
+ *  that might affect the data area (e.g. resize the dynamic buffer, or write
+ *  to the buffer) before the string is fully interned.
+ */
 
 #if defined(DUK_USE_ROM_STRINGS)
-	{
-		duk_small_uint_t i;
-		/* XXX: This is VERY inefficient now, and should be e.g. a
-		 * binary search or perfect hash, to be fixed.
-		 */
-		for (i = 0; i < (duk_small_uint_t) (sizeof(duk_rom_strings) / sizeof(duk_hstring *)); i++) {
-			duk_hstring *romstr;
-			romstr = (duk_hstring *) DUK_LOSE_CONST(duk_rom_strings[i]);
-			if (blen == DUK_HSTRING_GET_BYTELEN(romstr) &&
-			    DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(romstr), blen) == 0) {
-				DUK_DD(DUK_DDPRINT("intern check: rom string: %!O, computed hash 0x%08lx, rom hash 0x%08lx",
-				                   romstr, (unsigned long) *out_strhash, (unsigned long) DUK_HSTRING_GET_HASH(romstr)));
-				DUK_ASSERT(*out_strhash == DUK_HSTRING_GET_HASH(romstr));
-				*out_strhash = DUK_HSTRING_GET_HASH(romstr);
-				return romstr;
-			}
-		}
-	}
+DUK_LOCAL duk_hstring *duk__strtab_romstring_lookup(duk_heap *heap, const duk_uint8_t *str, duk_size_t blen, duk_uint32_t strhash) {
+	duk_size_t lookup_hash;
+	duk_hstring *curr;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_UNREF(heap);
+
+	lookup_hash = (blen << 4);
+	if (blen > 0) {
+		lookup_hash += str[0];
+	}
+	lookup_hash &= 0xff;
+
+	curr = DUK_LOSE_CONST(duk_rom_strings_lookup[lookup_hash]);
+	while (curr != NULL) {
+		if (strhash == DUK_HSTRING_GET_HASH(curr) &&
+		    blen == DUK_HSTRING_GET_BYTELEN(curr) &&
+		    DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(curr), blen) == 0) {
+			DUK_DDD(DUK_DDDPRINT("intern check: rom string: %!O, computed hash 0x%08lx, rom hash 0x%08lx",
+			                     curr, (unsigned long) strhash, (unsigned long) DUK_HSTRING_GET_HASH(curr)));
+			return curr;
+		}
+		curr = curr->hdr.h_next;
+	}
+
+	return NULL;
+}
 #endif  /* DUK_USE_ROM_STRINGS */
 
-#if defined(DUK_USE_STRTAB_CHAIN)
-	res = duk__find_matching_string_chain(heap, str, blen, *out_strhash);
-#elif defined(DUK_USE_STRTAB_PROBE)
-	res = duk__find_matching_string_probe(heap,
-#if defined(DUK_USE_HEAPPTR16)
-	                                      heap->strtable16,
-#else
-	                                      heap->strtable,
-#endif
-	                                      heap->st_size,
-	                                      str,
-	                                      blen,
-	                                      *out_strhash);
-#else
-#error internal error, invalid strtab options
-#endif
-
-	return res;
-}
-
-/*
- *  Exposed calls
- */
-
-#if 0  /*unused*/
-DUK_INTERNAL duk_hstring *duk_heap_string_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
-	duk_uint32_t strhash;  /* dummy */
-	return duk__do_lookup(heap, str, blen, &strhash);
-}
-#endif
-
-DUK_INTERNAL duk_hstring *duk_heap_string_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
-	duk_hstring *res;
+DUK_INTERNAL duk_hstring *duk_heap_strtable_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
 	duk_uint32_t strhash;
-
-	/* caller is responsible for ensuring this */
-	DUK_ASSERT(blen <= DUK_HSTRING_MAX_BYTELEN);
-
-	res = duk__do_lookup(heap, str, blen, &strhash);
-	if (res) {
-		return res;
-	}
-
-	res = duk__do_intern(heap, str, blen, strhash);
-	return res;  /* may be NULL */
-}
-
-DUK_INTERNAL duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) {
-	duk_hstring *res = duk_heap_string_intern(thr->heap, str, blen);
-	if (!res) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-	}
-	return res;
-}
-
-#if 0  /*unused*/
-DUK_INTERNAL duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_uint32_t val) {
-	char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
-	DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
-	buf[sizeof(buf) - 1] = (char) 0;
-	DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX);  /* formatted result limited */
-	return duk_heap_string_lookup(heap, (const duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
-}
-#endif
-
-DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_uint32_t val) {
-	char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
-	DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
-	buf[sizeof(buf) - 1] = (char) 0;
-	DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX);  /* formatted result limited */
-	return duk_heap_string_intern(heap, (const duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
-}
-
-DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) {
-	duk_hstring *res = duk_heap_string_intern_u32(thr->heap, val);
-	if (!res) {
+	duk_hstring *h;
+
+	DUK_DDD(DUK_DDDPRINT("intern check: heap=%p, str=%p, blen=%lu", (void *) heap, (const void *) str, (unsigned long) blen));
+
+	/* Preliminaries. */
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(blen == 0 || str != NULL);
+	DUK_ASSERT(blen <= DUK_HSTRING_MAX_BYTELEN);  /* Caller is responsible for ensuring this. */
+	strhash = duk_heap_hashstring(heap, str, (duk_size_t) blen);
+
+	/* String table lookup. */
+
+	DUK_ASSERT(DUK__GET_STRTABLE(heap) != NULL);
+	DUK_ASSERT(heap->st_size > 0);
+	DUK_ASSERT(heap->st_size == heap->st_mask + 1);
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	h = DUK__HEAPPTR_DEC16(heap, heap->strtable16[strhash & heap->st_mask]);
+#else
+	h = heap->strtable[strhash & heap->st_mask];
+#endif
+	while (h != NULL) {
+		if (DUK_HSTRING_GET_HASH(h) == strhash &&
+		    DUK_HSTRING_GET_BYTELEN(h) == blen &&
+		    DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
+			/* Found existing entry. */
+			return h;
+		}
+		h = h->hdr.h_next;
+	}
+
+	/* ROM table lookup.  Because this lookup is slower, do it only after
+	 * RAM lookup.  This works because no ROM string is ever interned into
+	 * the RAM string table.
+	 */
+
+#if defined(DUK_USE_ROM_STRINGS)
+	h = duk__strtab_romstring_lookup(heap, str, blen, strhash);
+	if (h != NULL) {
+		return h;
+	}
+#endif
+
+	/* Not found in string table; insert. */
+
+	h = duk__strtable_do_intern(heap, str, blen, strhash);
+	return h;  /* may be NULL */
+}
+
+/*
+ *  Intern a string from u32.
+ */
+
+/* XXX: Could arrange some special handling because we know that the result
+ * will have an arridx flag and an ASCII flag, won't need a clen check, etc.
+ */
+
+DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_u32(duk_heap *heap, duk_uint32_t val) {
+	char buf[DUK__STRTAB_U32_MAX_STRLEN];
+	char *p;
+
+	DUK_ASSERT(heap != NULL);
+
+	/* This is smaller and faster than a %lu sprintf. */
+	p = buf + sizeof(buf);
+	do {
+		p--;
+		*p = duk_lc_digits[val % 10];
+		val = val / 10;
+	} while (val != 0);  /* For val == 0, emit exactly one '0'. */
+	DUK_ASSERT(p >= buf);
+
+	return duk_heap_strtable_intern(heap, (const duk_uint8_t *) p, (duk_uint32_t) ((buf + sizeof(buf)) - p));
+}
+
+/*
+ *  Checked convenience variants.
+ *
+ *  XXX: Because the main use case is for the checked variants, make them the
+ *  main functionality and provide a safe variant separately (it is only needed
+ *  during heap init).  The problem with that is that longjmp state and error
+ *  creation must already be possible to throw.
+ */
+
+DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) {
+	duk_hstring *res;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(blen == 0 || str != NULL);
+
+	res = duk_heap_strtable_intern(thr->heap, str, blen);
+	if (DUK_UNLIKELY(res == NULL)) {
 		DUK_ERROR_ALLOC_FAILED(thr);
 	}
 	return res;
 }
 
-/* find and remove string from stringtable; caller must free the string itself */
+DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) {
+	duk_hstring *res;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+
+	res = duk_heap_strtable_intern_u32(thr->heap, val);
+	if (DUK_UNLIKELY(res == NULL)) {
+		DUK_ERROR_ALLOC_FAILED(thr);
+	}
+	return res;
+}
+
+/*
+ *  Remove (unlink) a string from the string table.
+ *
+ *  Just unlinks the duk_hstring, leaving link pointers as garbage.
+ *  Caller must free the string itself.
+ */
+
 #if defined(DUK_USE_REFERENCE_COUNTING)
-DUK_INTERNAL void duk_heap_string_remove(duk_heap *heap, duk_hstring *h) {
-	DUK_DDD(DUK_DDDPRINT("remove string from stringtable: %!O", (duk_heaphdr *) h));
-
-#if defined(DUK_USE_STRTAB_CHAIN)
-	duk__remove_matching_hstring_chain(heap, h);
-#elif defined(DUK_USE_STRTAB_PROBE)
-	duk__remove_matching_hstring_probe(heap,
-#if defined(DUK_USE_HEAPPTR16)
-	                                   heap->strtable16,
-#else
-	                                   heap->strtable,
-#endif
-	                                   heap->st_size,
-	                                   h);
-#else
-#error internal error, invalid strtab options
-#endif
-}
-#endif
-
-#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
-DUK_INTERNAL void duk_heap_force_strtab_resize(duk_heap *heap) {
-	duk_small_uint_t prev_mark_and_sweep_base_flags;
-	/* Force a resize so that DELETED entries are eliminated.
-	 * Another option would be duk__recheck_strtab_size_probe();
-	 * but since that happens on every intern anyway, this whole
-	 * check can now be disabled.
-	 */
-
-	DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
-	prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
-	DUK__PREVENT_MS_SIDE_EFFECTS(heap);
-
-#if defined(DUK_USE_STRTAB_CHAIN)
+/* Unlink without a 'prev' pointer. */
+DUK_INTERNAL void duk_heap_strtable_unlink(duk_heap *heap, duk_hstring *h) {
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *slot;
+#else
+	duk_hstring **slot;
+#endif
+	duk_hstring *other;
+	duk_hstring *prev;
+
+	DUK_DDD(DUK_DDDPRINT("remove: heap=%p, h=%p, blen=%lu, strhash=%lx",
+	                     (void *) heap, (void *) h,
+	                     (unsigned long) (h != NULL ? DUK_HSTRING_GET_BYTELEN(h) : 0),
+	                     (unsigned long) (h != NULL ? DUK_HSTRING_GET_HASH(h) : 0)));
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(h != NULL);
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+	DUK_ASSERT(heap->st_count > 0);
+	heap->st_count--;
+#endif
+
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	slot = heap->strtable16 + (DUK_HSTRING_GET_HASH(h) & heap->st_mask);
+#else
+	slot = heap->strtable + (DUK_HSTRING_GET_HASH(h) & heap->st_mask);
+#endif
+	other = DUK__HEAPPTR_DEC16(heap, *slot);
+	DUK_ASSERT(other != NULL);  /* At least argument string is in the chain. */
+
+	prev = NULL;
+	while (other != h) {
+		prev = other;
+		other = other->hdr.h_next;
+		DUK_ASSERT(other != NULL);  /* We'll eventually find 'h'. */
+	}
+	if (prev != NULL) {
+		/* Middle of list. */
+		prev->hdr.h_next = h->hdr.h_next;
+	} else {
+		/* Head of list. */
+		*slot = DUK__HEAPPTR_ENC16(heap, h->hdr.h_next);
+	}
+
+	/* There's no resize check on a string free.  The next string
+	 * intern will do one.
+	 */
+}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+/* Unlink with a 'prev' pointer. */
+DUK_INTERNAL void duk_heap_strtable_unlink_prev(duk_heap *heap, duk_hstring *h, duk_hstring *prev) {
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *slot;
+#else
+	duk_hstring **slot;
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("remove: heap=%p, prev=%p, h=%p, blen=%lu, strhash=%lx",
+	                     (void *) heap, (void *) prev, (void *) h,
+	                     (unsigned long) (h != NULL ? DUK_HSTRING_GET_BYTELEN(h) : 0),
+	                     (unsigned long) (h != NULL ? DUK_HSTRING_GET_HASH(h) : 0)));
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(h != NULL);
+	DUK_ASSERT(prev == NULL || prev->hdr.h_next == h);
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+	DUK_ASSERT(heap->st_count > 0);
+	heap->st_count--;
+#endif
+
+	if (prev != NULL) {
+		/* Middle of list. */
+		prev->hdr.h_next = h->hdr.h_next;
+	} else {
+		/* Head of list. */
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+		slot = heap->strtable16 + (DUK_HSTRING_GET_HASH(h) & heap->st_mask);
+#else
+		slot = heap->strtable + (DUK_HSTRING_GET_HASH(h) & heap->st_mask);
+#endif
+		DUK_ASSERT(DUK__HEAPPTR_DEC16(heap, *slot) == h);
+		*slot = DUK__HEAPPTR_ENC16(heap, h->hdr.h_next);
+	}
+}
+
+/*
+ *  Force string table resize check in mark-and-sweep.
+ */
+
+DUK_INTERNAL void duk_heap_strtable_force_resize(duk_heap *heap) {
+	/* Does only one grow/shrink step if needed.  The heap->st_resizing
+	 * flag protects against recursive resizing.
+	 */
+
+	DUK_ASSERT(heap != NULL);
 	DUK_UNREF(heap);
-#elif defined(DUK_USE_STRTAB_PROBE)
-	(void) duk__resize_strtab_probe(heap);
-#endif
-
-	heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
-}
-#endif
-
-#if defined(DUK_USE_STRTAB_CHAIN)
-DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap) {
-	/* Free strings in the stringtable and any allocations needed
-	 * by the stringtable itself.
-	 */
-	duk_uint_fast32_t i, j;
-	duk_strtab_entry *e;
-#if defined(DUK_USE_HEAPPTR16)
-	duk_uint16_t *lst;
-	duk_uint16_t null16 = heap->heapptr_null16;
-#else
-	duk_hstring **lst;
+
+#if defined(DUK__STRTAB_RESIZE_CHECK)
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	if (heap->strtable16 != NULL) {
+#else
+	if (heap->strtable != NULL) {
+#endif
+		duk__strtable_resize_check(heap);
+	}
+#endif
+}
+
+/*
+ *  Free strings in the string table and the string table itself.
+ */
+
+DUK_INTERNAL void duk_heap_strtable_free(duk_heap *heap) {
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+	duk_uint16_t *strtable;
+	duk_uint16_t *st;
+#else
+	duk_hstring **strtable;
+	duk_hstring **st;
 #endif
 	duk_hstring *h;
 
-	for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
-		e = heap->strtable + i;
-		if (e->listlen > 0) {
-#if defined(DUK_USE_HEAPPTR16)
-			lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
-#else
-			lst = e->u.strlist;
-#endif
-			DUK_ASSERT(lst != NULL);
-
-			for (j = 0; j < e->listlen; j++) {
-#if defined(DUK_USE_HEAPPTR16)
-				h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[j]);
-				lst[j] = null16;
-#else
-				h = lst[j];
-				lst[j] = NULL;
-#endif
-				/* strings may have inner refs (extdata) in some cases */
-				if (h != NULL) {
-					duk_free_hstring(heap, h);
-				}
-			}
-#if defined(DUK_USE_HEAPPTR16)
-			e->u.strlist16 = null16;
-#else
-			e->u.strlist = NULL;
-#endif
-			DUK_FREE(heap, lst);
-		} else {
-#if defined(DUK_USE_HEAPPTR16)
-			h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
-			e->u.str16 = null16;
-#else
-			h = e->u.str;
-			e->u.str = NULL;
-#endif
-			if (h != NULL) {
-				duk_free_hstring(heap, h);
-			}
-		}
-		e->listlen = 0;
-	}
-}
-#endif  /* DUK_USE_STRTAB_CHAIN */
-
-#if defined(DUK_USE_STRTAB_PROBE)
-DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap) {
-	duk_uint_fast32_t i;
-	duk_hstring *h;
-
-#if defined(DUK_USE_HEAPPTR16)
-	if (heap->strtable16) {
-#else
-	if (heap->strtable) {
-#endif
-		for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) {
-#if defined(DUK_USE_HEAPPTR16)
-			h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
-#else
-			h = heap->strtable[i];
-#endif
-			if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
-				continue;
-			}
-			DUK_ASSERT(h != NULL);
-
-			/* strings may have inner refs (extdata) in some cases */
+	DUK_ASSERT(heap != NULL);
+
+#if defined(DUK_USE_ASSERTIONS)
+	duk__strtable_assert_checks(heap);
+#endif
+
+	/* Strtable can be NULL if heap init fails.  However, in that case
+	 * heap->st_size is 0, so strtable == strtable_end and we skip the
+	 * loop without a special check.
+	 */
+	strtable = DUK__GET_STRTABLE(heap);
+	st = strtable + heap->st_size;
+	DUK_ASSERT(strtable != NULL || heap->st_size == 0);
+
+	while (strtable != st) {
+		--st;
+		h = DUK__HEAPPTR_DEC16(heap, *st);
+		while (h) {
+			duk_hstring *h_next;
+			h_next = h->hdr.h_next;
+
+			/* Strings may have inner refs (extdata) in some cases. */
 			duk_free_hstring(heap, h);
-#if 0  /* not strictly necessary */
-			heap->strtable[i] = NULL;
-#endif
-		}
-#if defined(DUK_USE_HEAPPTR16)
-		DUK_FREE(heap, heap->strtable16);
-#else
-		DUK_FREE(heap, heap->strtable);
-#endif
-#if 0  /* not strictly necessary */
-		heap->strtable = NULL;
-#endif
-	}
-}
-#endif  /* DUK_USE_STRTAB_PROBE */
+
+			h = h_next;
+		}
+	}
+
+	DUK_FREE(heap, strtable);
+}
 
 /* automatic undefs */
-#undef DUK__DELETED_MARKER
-#undef DUK__HASH_INITIAL
-#undef DUK__HASH_PROBE_STEP
-#undef DUK__PREVENT_MS_SIDE_EFFECTS
+#undef DUK__GET_STRTABLE
+#undef DUK__HEAPPTR_DEC16
+#undef DUK__HEAPPTR_ENC16
+#undef DUK__STRTAB_U32_MAX_STRLEN
 #line 1 "duk_hobject_alloc.c"
 /*
  *  Hobject allocation.
@@ -48808,19 +50374,29 @@
  *  in "heap allocated" list and has a refcount of zero, so caller must careful.
  */
 
+/* XXX: In most cases there's no need for plain allocation without pushing
+ * to the value stack.  Maybe rework contract?
+ */
+
 /* #include duk_internal.h -> already included */
 
-DUK_LOCAL void duk__init_object_parts(duk_heap *heap, duk_hobject *obj, duk_uint_t hobject_flags) {
+/*
+ *  Helpers.
+ */
+
+DUK_LOCAL void duk__init_object_parts(duk_heap *heap, duk_uint_t hobject_flags, duk_hobject *obj) {
+	DUK_ASSERT(obj != NULL);
+	/* Zeroed by caller. */
+
+	obj->hdr.h_flags = hobject_flags | DUK_HTYPE_OBJECT;
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(&obj->hdr) == DUK_HTYPE_OBJECT);  /* Assume zero shift. */
+
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
+	DUK_HOBJECT_SET_PROTOTYPE(heap, obj, NULL);
 	DUK_HOBJECT_SET_PROPS(heap, obj, NULL);
 #endif
-
-	/* XXX: macro? sets both heaphdr and object flags */
-	obj->hdr.h_flags = hobject_flags;
-	DUK_HEAPHDR_SET_TYPE(&obj->hdr, DUK_HTYPE_OBJECT);  /* also goes into flags */
-
 #if defined(DUK_USE_HEAPPTR16)
-	/* Zero encoded pointer is required to match NULL */
+	/* Zero encoded pointer is required to match NULL. */
 	DUK_HEAPHDR_SET_NEXT(heap, &obj->hdr, NULL);
 #if defined(DUK_USE_DOUBLE_LINKED_HEAP)
 	DUK_HEAPHDR_SET_PREV(heap, &obj->hdr, NULL);
@@ -48829,12 +50405,20 @@
 	DUK_ASSERT_HEAPHDR_LINKS(heap, &obj->hdr);
 	DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &obj->hdr);
 
-	/*
-	 *  obj->props is intentionally left as NULL, and duk_hobject_props.c must deal
-	 *  with this properly.  This is intentional: empty objects consume a minimum
-	 *  amount of memory.  Further, an initial allocation might fail and cause
-	 *  'obj' to "leak" (require a mark-and-sweep) since it is not reachable yet.
-	 */
+	/* obj->props is intentionally left as NULL, and duk_hobject_props.c must deal
+	 * with this properly.  This is intentional: empty objects consume a minimum
+	 * amount of memory.  Further, an initial allocation might fail and cause
+	 * 'obj' to "leak" (require a mark-and-sweep) since it is not reachable yet.
+	 */
+}
+
+DUK_LOCAL void *duk__hobject_alloc_init(duk_hthread *thr, duk_uint_t hobject_flags, duk_size_t size) {
+	void *res;
+
+	res = (void *) DUK_ALLOC_CHECKED_ZEROED(thr, size);
+	DUK_ASSERT(res != NULL);
+	duk__init_object_parts(thr->heap, hobject_flags, (duk_hobject *) res);
+	return res;
 }
 
 /*
@@ -48848,7 +50432,7 @@
  *  count before invoking any operation that might require memory allocation.
  */
 
-DUK_INTERNAL duk_hobject *duk_hobject_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+DUK_INTERNAL duk_hobject *duk_hobject_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags) {
 	duk_hobject *res;
 
 	DUK_ASSERT(heap != NULL);
@@ -48856,30 +50440,30 @@
 	/* different memory layout, alloc size, and init */
 	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_COMPFUNC) == 0);
 	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_NATFUNC) == 0);
-	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_THREAD) == 0);
-
-	res = (duk_hobject *) DUK_ALLOC(heap, sizeof(duk_hobject));
-	if (!res) {
+
+	res = (duk_hobject *) DUK_ALLOC_ZEROED(heap, sizeof(duk_hobject));
+	if (DUK_UNLIKELY(res == NULL)) {
 		return NULL;
 	}
-	DUK_MEMZERO(res, sizeof(duk_hobject));
-
-	duk__init_object_parts(heap, res, hobject_flags);
-
+	DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(res));
+
+	duk__init_object_parts(heap, hobject_flags, res);
+
+	DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(res));
 	return res;
 }
 
-DUK_INTERNAL duk_hcompfunc *duk_hcompfunc_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+DUK_INTERNAL duk_hobject *duk_hobject_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
+	duk_hobject *res;
+
+	res = (duk_hobject *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hobject));
+	return res;
+}
+
+DUK_INTERNAL duk_hcompfunc *duk_hcompfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
 	duk_hcompfunc *res;
 
-	res = (duk_hcompfunc *) DUK_ALLOC(heap, sizeof(duk_hcompfunc));
-	if (!res) {
-		return NULL;
-	}
-	DUK_MEMZERO(res, sizeof(duk_hcompfunc));
-
-	duk__init_object_parts(heap, &res->obj, hobject_flags);
-
+	res = (duk_hcompfunc *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hcompfunc));
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
 #if defined(DUK_USE_HEAPPTR16)
 	/* NULL pointer is required to encode to zero, so memset is enough. */
@@ -48895,17 +50479,10 @@
 	return res;
 }
 
-DUK_INTERNAL duk_hnatfunc *duk_hnatfunc_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+DUK_INTERNAL duk_hnatfunc *duk_hnatfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
 	duk_hnatfunc *res;
 
-	res = (duk_hnatfunc *) DUK_ALLOC(heap, sizeof(duk_hnatfunc));
-	if (!res) {
-		return NULL;
-	}
-	DUK_MEMZERO(res, sizeof(duk_hnatfunc));
-
-	duk__init_object_parts(heap, &res->obj, hobject_flags);
-
+	res = (duk_hnatfunc *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hnatfunc));
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
 	res->func = NULL;
 #endif
@@ -48914,17 +50491,10 @@
 }
 
 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
-DUK_INTERNAL duk_hbufobj *duk_hbufobj_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+DUK_INTERNAL duk_hbufobj *duk_hbufobj_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
 	duk_hbufobj *res;
 
-	res = (duk_hbufobj *) DUK_ALLOC(heap, sizeof(duk_hbufobj));
-	if (!res) {
-		return NULL;
-	}
-	DUK_MEMZERO(res, sizeof(duk_hbufobj));
-
-	duk__init_object_parts(heap, &res->obj, hobject_flags);
-
+	res = (duk_hbufobj *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hbufobj));
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
 	res->buf = NULL;
 	res->buf_prop = NULL;
@@ -48935,24 +50505,22 @@
 }
 #endif  /* DUK_USE_BUFFEROBJECT_SUPPORT */
 
-/*
- *  Allocate a new thread.
- *
- *  Leaves the built-ins array uninitialized.  The caller must either
- *  initialize a new global context or share existing built-ins from
- *  another thread.
- */
-
-DUK_INTERNAL duk_hthread *duk_hthread_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+/* Allocate a new thread.
+ *
+ * Leaves the built-ins array uninitialized.  The caller must either
+ * initialize a new global context or share existing built-ins from
+ * another thread.
+ */
+DUK_INTERNAL duk_hthread *duk_hthread_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags) {
 	duk_hthread *res;
 
 	res = (duk_hthread *) DUK_ALLOC(heap, sizeof(duk_hthread));
-	if (!res) {
+	if (DUK_UNLIKELY(res == NULL)) {
 		return NULL;
 	}
 	DUK_MEMZERO(res, sizeof(duk_hthread));
 
-	duk__init_object_parts(heap, &res->obj, hobject_flags);
+	duk__init_object_parts(heap, hobject_flags, &res->obj);
 
 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
 	res->ptr_curr_pc = NULL;
@@ -48962,6 +50530,7 @@
 	res->valstack_bottom = NULL;
 	res->valstack_top = NULL;
 	res->callstack = NULL;
+	res->callstack_curr = NULL;
 	res->catchstack = NULL;
 	res->resumer = NULL;
 	res->compile_ctx = NULL,
@@ -48971,7 +50540,7 @@
 	res->strs = NULL;
 #endif
 	{
-		int i;
+		duk_small_uint_t i;
 		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
 			res->builtins[i] = NULL;
 		}
@@ -48988,35 +50557,54 @@
 	return res;
 }
 
-#if 0  /* unused now */
-DUK_INTERNAL duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, duk_uint_t hobject_flags) {
-	duk_hobject *res = duk_hobject_alloc(thr->heap, hobject_flags);
-	if (!res) {
+DUK_INTERNAL duk_hthread *duk_hthread_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
+	duk_hthread *res;
+
+	res = duk_hthread_alloc_unchecked(thr->heap, hobject_flags);
+	if (res == NULL) {
 		DUK_ERROR_ALLOC_FAILED(thr);
 	}
 	return res;
 }
-#endif
-
-/*
- *  Allocate a new array.
- */
-
-DUK_INTERNAL duk_harray *duk_harray_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+
+DUK_INTERNAL duk_harray *duk_harray_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
 	duk_harray *res;
 
-	res = (duk_harray *) DUK_ALLOC(heap, sizeof(duk_harray));
-	if (!res) {
-		return NULL;
-	}
-	DUK_MEMZERO(res, sizeof(duk_harray));
-
-	duk__init_object_parts(heap, &res->obj, hobject_flags);
+	res = (duk_harray *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_harray));
 
 	DUK_ASSERT(res->length == 0);
 
 	return res;
 }
+
+DUK_INTERNAL duk_hdecenv *duk_hdecenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
+	duk_hdecenv *res;
+
+	res = (duk_hdecenv *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hdecenv));
+#if defined(DUK_USE_EXPLICIT_NULL_INIT)
+	res->thread = NULL;
+	res->varmap = NULL;
+#endif
+
+	DUK_ASSERT(res->thread == NULL);
+	DUK_ASSERT(res->varmap == NULL);
+	DUK_ASSERT(res->regbase == 0);
+
+	return res;
+}
+
+DUK_INTERNAL duk_hobjenv *duk_hobjenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags) {
+	duk_hobjenv *res;
+
+	res = (duk_hobjenv *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hobjenv));
+#if defined(DUK_USE_EXPLICIT_NULL_INIT)
+	res->target = NULL;
+#endif
+
+	DUK_ASSERT(res->target == NULL);
+
+	return res;
+}
 #line 1 "duk_hobject_enum.c"
 /*
  *  Object enumeration support.
@@ -49262,6 +50850,9 @@
  */
 
 DUK_LOCAL void duk__add_enum_key(duk_context *ctx, duk_hstring *k) {
+	/* 'k' may be unreachable on entry so must push without any
+	 * potential for GC.
+	 */
 	duk_push_hstring(ctx, k);
 	duk_push_true(ctx);
 	duk_put_prop(ctx, -3);
@@ -49462,7 +51053,7 @@
 				/* This is a bit fragile: the string is not
 				 * reachable until it is pushed by the helper.
 				 */
-				k = duk_heap_string_intern_u32_checked(thr, i);
+				k = duk_heap_strtable_intern_u32_checked(thr, i);
 				DUK_ASSERT(k);
 
 				duk__add_enum_key(ctx, k);
@@ -49496,7 +51087,7 @@
 			if (DUK_TVAL_IS_UNUSED(tv)) {
 				continue;
 			}
-			k = duk_heap_string_intern_u32_checked(thr, i);  /* Fragile reachability. */
+			k = duk_heap_strtable_intern_u32_checked(thr, i);  /* Fragile reachability. */
 			DUK_ASSERT(k);
 
 			duk__add_enum_key(ctx, k);
@@ -49526,7 +51117,7 @@
 			    !DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(thr->heap, curr, i)) {
 				continue;
 			}
-			if (DUK_HSTRING_HAS_SYMBOL(k)) {
+			if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) {
 				if (!(enum_flags & DUK_ENUM_INCLUDE_HIDDEN) &&
 				    DUK_HSTRING_HAS_HIDDEN(k)) {
 					continue;
@@ -49791,120 +51382,6 @@
 
 /* automatic undefs */
 #undef DUK__ENUM_START_INDEX
-#line 1 "duk_hobject_finalizer.c"
-/*
- *  Run an duk_hobject finalizer.  Used for both reference counting
- *  and mark-and-sweep algorithms.  Must never throw an error.
- *
- *  There is no return value.  Any return value or error thrown by
- *  the finalizer is ignored (although errors are debug logged).
- *
- *  Notes:
- *
- *    - The thread used for calling the finalizer is the same as the
- *      'thr' argument.  This may need to change later.
- *
- *    - The finalizer thread 'top' assertions are there because it is
- *      critical that strict stack policy is observed (i.e. no cruft
- *      left on the finalizer stack).
- */
-
-/* #include duk_internal.h -> already included */
-
-#if defined(DUK_USE_FINALIZER_SUPPORT)
-DUK_LOCAL duk_ret_t duk__finalize_helper(duk_context *ctx, void *udata) {
-	duk_hthread *thr;
-
-	DUK_ASSERT(ctx != NULL);
-	thr = (duk_hthread *) ctx;
-	DUK_UNREF(udata);
-
-	DUK_DDD(DUK_DDDPRINT("protected finalization helper running"));
-
-	/* [... obj] */
-
-	/* XXX: Finalizer lookup should traverse the prototype chain (to allow
-	 * inherited finalizers) but should not invoke accessors or proxy object
-	 * behavior.  At the moment this lookup will invoke proxy behavior, so
-	 * caller must ensure that this function is not called if the target is
-	 * a Proxy.
-	 */
-
-	duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_FINALIZER);  /* -> [... obj finalizer] */
-	if (!duk_is_callable(ctx, -1)) {
-		DUK_DDD(DUK_DDDPRINT("-> no finalizer or finalizer not callable"));
-		return 0;
-	}
-	duk_dup_m2(ctx);
-	duk_push_boolean(ctx, DUK_HEAP_HAS_FINALIZER_NORESCUE(thr->heap));
-	DUK_DDD(DUK_DDDPRINT("-> finalizer found, calling finalizer"));
-	duk_call(ctx, 2);  /* [ ... obj finalizer obj heapDestruct ]  -> [ ... obj retval ] */
-	DUK_DDD(DUK_DDDPRINT("finalizer finished successfully"));
-	return 0;
-
-	/* Note: we rely on duk_safe_call() to fix up the stack for the caller,
-	 * so we don't need to pop stuff here.  There is no return value;
-	 * caller determines rescued status based on object refcount.
-	 */
-}
-
-DUK_INTERNAL void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj) {
-	duk_context *ctx = (duk_context *) thr;
-	duk_ret_t rc;
-#if defined(DUK_USE_ASSERTIONS)
-	duk_idx_t entry_top;
-#endif
-
-	DUK_DDD(DUK_DDDPRINT("running object finalizer for object: %p", (void *) obj));
-
-	DUK_ASSERT(thr != NULL);
-	DUK_ASSERT(ctx != NULL);
-	DUK_ASSERT(obj != NULL);
-	DUK_ASSERT_VALSTACK_SPACE(thr, 1);
-
-#if defined(DUK_USE_ASSERTIONS)
-	entry_top = duk_get_top(ctx);
-#endif
-	/*
-	 *  Get and call the finalizer.  All of this must be wrapped
-	 *  in a protected call, because even getting the finalizer
-	 *  may trigger an error (getter may throw one, for instance).
-	 */
-
-	DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj));
-	if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) obj)) {
-		DUK_D(DUK_DPRINT("object already finalized, avoid running finalizer twice: %!O", obj));
-		return;
-	}
-	DUK_HEAPHDR_SET_FINALIZED((duk_heaphdr *) obj);  /* ensure never re-entered until rescue cycle complete */
-	if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj)) {
-		/* This shouldn't happen; call sites should avoid looking up
-		 * _Finalizer "through" a Proxy, but ignore if we come here
-		 * with a Proxy to avoid finalizer re-entry.
-		 */
-		DUK_D(DUK_DPRINT("object is a proxy, skip finalizer call"));
-		return;
-	}
-
-	/* XXX: use a NULL error handler for the finalizer call? */
-
-	DUK_DDD(DUK_DDDPRINT("-> finalizer found, calling wrapped finalize helper"));
-	duk_push_hobject(ctx, obj);  /* this also increases refcount by one */
-	rc = duk_safe_call(ctx, duk__finalize_helper, NULL /*udata*/, 0 /*nargs*/, 1 /*nrets*/);  /* -> [... obj retval/error] */
-	DUK_ASSERT_TOP(ctx, entry_top + 2);  /* duk_safe_call discipline */
-
-	if (rc != DUK_EXEC_SUCCESS) {
-		/* Note: we ask for one return value from duk_safe_call to get this
-		 * error debugging here.
-		 */
-		DUK_D(DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T",
-		                 (void *) obj, (duk_tval *) duk_get_tval(ctx, -1)));
-	}
-	duk_pop_2(ctx);  /* -> [...] */
-
-	DUK_ASSERT_TOP(ctx, entry_top);
-}
-#endif  /* DUK_USE_FINALIZER_SUPPORT */
 #line 1 "duk_hobject_misc.c"
 /*
  *  Misc support functions
@@ -50111,7 +51588,7 @@
 
 	if (DUK_HBUFFER_FIXED_GET_SIZE(buf) <= sizeof(duk_uint32_t)) {
 		DUK_DD(DUK_DDPRINT("pc2line lookup failed: buffer is smaller than minimal header"));
-		goto error;
+		goto pc2line_error;
 	}
 
 	hdr = (duk_uint32_t *) (void *) DUK_HBUFFER_FIXED_GET_DATA_PTR(thr->heap, buf);
@@ -50120,7 +51597,7 @@
 		/* Note: pc is unsigned and cannot be negative */
 		DUK_DD(DUK_DDPRINT("pc2line lookup failed: pc out of bounds (pc=%ld, limit=%ld)",
 		                   (long) pc, (long) pc_limit));
-		goto error;
+		goto pc2line_error;
 	}
 
 	curr_line = hdr[1 + hdr_index * 2];
@@ -50128,7 +51605,7 @@
 	if ((duk_size_t) start_offset > DUK_HBUFFER_FIXED_GET_SIZE(buf)) {
 		DUK_DD(DUK_DDPRINT("pc2line lookup failed: start_offset out of bounds (start_offset=%ld, buffer_size=%ld)",
 		                   (long) start_offset, (long) DUK_HBUFFER_GET_SIZE((duk_hbuffer *) buf)));
-		goto error;
+		goto pc2line_error;
 	}
 
 	/*
@@ -50179,7 +51656,7 @@
 	DUK_DDD(DUK_DDDPRINT("pc2line lookup result: pc %ld -> line %ld", (long) pc, (long) curr_line));
 	return curr_line;
 
- error:
+ pc2line_error:
 	DUK_D(DUK_DPRINT("pc2line conversion failed for pc=%ld", (long) pc));
 	return 0;
 }
@@ -50210,7 +51687,7 @@
 #endif  /* DUK_USE_PC2LINE */
 #line 1 "duk_hobject_props.c"
 /*
- *  Hobject property set/get functionality.
+ *  duk_hobject property access functionality.
  *
  *  This is very central functionality for size, performance, and compliance.
  *  It is also rather intricate; see hobject-algorithms.rst for discussion on
@@ -50251,10 +51728,6 @@
  *  might be more appropriate.
  */
 
-/*
- *  XXX: duk_uint_fast32_t should probably be used in many places here.
- */
-
 /* #include duk_internal.h -> already included */
 
 /*
@@ -50263,10 +51736,6 @@
 
 #define DUK__NO_ARRAY_INDEX             DUK_HSTRING_NO_ARRAY_INDEX
 
-/* hash probe sequence */
-#define DUK__HASH_INITIAL(hash,h_size)  DUK_HOBJECT_HASH_INITIAL((hash),(h_size))
-#define DUK__HASH_PROBE_STEP(hash)      DUK_HOBJECT_HASH_PROBE_STEP((hash))
-
 /* marker values for hash part */
 #define DUK__HASH_UNUSED                DUK_HOBJECT_HASHIDX_UNUSED
 #define DUK__HASH_DELETED               DUK_HOBJECT_HASHIDX_DELETED
@@ -50429,14 +51898,26 @@
 DUK_LOCAL duk_uint32_t duk__get_default_h_size(duk_uint32_t e_size) {
 	DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);
 
-	if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
+	if (e_size >= DUK_USE_HOBJECT_HASH_PROP_LIMIT) {
 		duk_uint32_t res;
-
-		/* result: hash_prime(floor(1.2 * e_size)) */
-		res = duk_util_get_hash_prime(e_size + e_size / DUK_HOBJECT_H_SIZE_DIVISOR);
-
-		/* if fails, e_size will be zero = not an issue, except performance-wise */
-		DUK_ASSERT(res == 0 || res > e_size);
+		duk_uint32_t tmp;
+
+		/* Hash size should be 2^N where N is chosen so that 2^N is
+		 * larger than e_size.  Extra shifting is used to ensure hash
+		 * is relatively sparse.
+		 */
+		tmp = e_size;
+		res = 2;  /* Result will be 2 ** (N + 1). */
+		while (tmp >= 0x40) {
+			tmp >>= 6;
+			res <<= 6;
+		}
+		while (tmp != 0) {
+			tmp >>= 1;
+			res <<= 1;
+		}
+		DUK_ASSERT((DUK_HOBJECT_MAX_PROPERTIES << 2U) > DUK_HOBJECT_MAX_PROPERTIES);  /* Won't wrap, even shifted by 2. */
+		DUK_ASSERT(res > e_size);
 		return res;
 	} else {
 		return 0;
@@ -50450,7 +51931,7 @@
 
 	DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);
 
-	res = (e_size + DUK_HOBJECT_E_MIN_GROW_ADD) / DUK_HOBJECT_E_MIN_GROW_DIVISOR;
+	res = (e_size + DUK_USE_HOBJECT_ENTRY_MINGROW_ADD) / DUK_USE_HOBJECT_ENTRY_MINGROW_DIVISOR;
 	DUK_ASSERT(res >= 1);  /* important for callers */
 	return res;
 }
@@ -50461,7 +51942,7 @@
 
 	DUK_ASSERT((duk_size_t) a_size <= DUK_HOBJECT_MAX_PROPERTIES);
 
-	res = (a_size + DUK_HOBJECT_A_MIN_GROW_ADD) / DUK_HOBJECT_A_MIN_GROW_DIVISOR;
+	res = (a_size + DUK_USE_HOBJECT_ARRAY_MINGROW_ADD) / DUK_USE_HOBJECT_ARRAY_MINGROW_DIVISOR;
 	DUK_ASSERT(res >= 1);  /* important for callers */
 	return res;
 }
@@ -50536,7 +52017,7 @@
 	 *  of the check, but may confuse debugging.
 	 */
 
-	return (a_used < DUK_HOBJECT_A_ABANDON_LIMIT * (a_size >> 3));
+	return (a_used < DUK_USE_HOBJECT_ARRAY_ABANDON_LIMIT * (a_size >> 3));
 }
 
 /* Fast check for extending array: check whether or not a slow density check is required. */
@@ -50562,7 +52043,7 @@
 	 *    arr_idx > limit'' * ((old_size + 7) / 8)
 	 */
 
-	return (arr_idx > DUK_HOBJECT_A_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
+	return (arr_idx > DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
 }
 
 /*
@@ -50714,29 +52195,26 @@
 /*
  *  Reallocate property allocation, moving properties to the new allocation.
  *
- *  Includes key compaction, rehashing, and can also optionally abandoning
+ *  Includes key compaction, rehashing, and can also optionally abandon
  *  the array part, 'migrating' array entries into the beginning of the
- *  new entry part.  Arguments are not validated here, so e.g. new_h_size
- *  MUST be a valid prime.
+ *  new entry part.
  *
  *  There is no support for in-place reallocation or just compacting keys
  *  without resizing the property allocation.  This is intentional to keep
- *  code size minimal.
+ *  code size minimal, but would be useful future work.
  *
  *  The implementation is relatively straightforward, except for the array
  *  abandonment process.  Array abandonment requires that new string keys
  *  are interned, which may trigger GC.  All keys interned so far must be
- *  reachable for GC at all times; valstack is used for that now.
+ *  reachable for GC at all times and correctly refcounted for; valstack is
+ *  used for that now.
  *
  *  Also, a GC triggered during this reallocation process must not interfere
- *  with the object being resized.  This is currently controlled by using
- *  heap->mark_and_sweep_base_flags to indicate that no finalizers will be
- *  executed (as they can affect ANY object) and no objects are compacted
- *  (it would suffice to protect this particular object only, though).
- *
- *  Note: a non-checked variant would be nice but is a bit tricky to
- *  implement for the array abandonment process.  It's easy for
- *  everything else.
+ *  with the object being resized.  This is currently controlled by preventing
+ *  finalizers (as they may affect ANY object) and object compaction in
+ *  mark-and-sweep.  It would suffice to protect only this particular object
+ *  from compaction, however.  DECREF refzero cascades are side effect free
+ *  and OK.
  *
  *  Note: because we need to potentially resize the valstack (as part
  *  of abandoning the array part), any tval pointers to the valstack
@@ -50750,7 +52228,7 @@
                                             duk_uint32_t new_h_size,
                                             duk_bool_t abandon_array) {
 	duk_context *ctx = (duk_context *) thr;
-	duk_small_uint_t prev_mark_and_sweep_base_flags;
+	duk_small_uint_t prev_ms_base_flags;
 	duk_uint32_t new_alloc_size;
 	duk_uint32_t new_e_size_adjusted;
 	duk_uint8_t *new_p;
@@ -50761,6 +52239,10 @@
 	duk_uint32_t *new_h;
 	duk_uint32_t new_e_next;
 	duk_uint_fast32_t i;
+	duk_size_t array_copy_size;
+#if defined(DUK_USE_ASSERTIONS)
+	duk_bool_t prev_error_not_allowed;
+#endif
 
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(ctx != NULL);
@@ -50830,9 +52312,8 @@
 	/*
 	 *  Property count check.  This is the only point where we ensure that
 	 *  we don't get more (allocated) property space that we can handle.
-	 *  There aren't hard limits as such, but some algorithms fail (e.g.
-	 *  finding next higher prime, selecting hash part size) if we get too
-	 *  close to the 4G property limit.
+	 *  There aren't hard limits as such, but some algorithms may fail
+	 *  if we get too close to the 4G property limit.
 	 *
 	 *  Since this works based on allocation size (not actually used size),
 	 *  the limit is a bit approximate but good enough in practice.
@@ -50845,43 +52326,46 @@
 	/*
 	 *  Compute new alloc size and alloc new area.
 	 *
-	 *  The new area is allocated as a dynamic buffer and placed into the
-	 *  valstack for reachability.  The actual buffer is then detached at
-	 *  the end.
-	 *
-	 *  Note: heap_mark_and_sweep_base_flags are altered here to ensure
-	 *  no-one touches this object while we're resizing and rehashing it.
-	 *  The flags must be reset on every exit path after it.  Finalizers
-	 *  and compaction is prevented currently for all objects while it
-	 *  would be enough to restrict it only for the current object.
-	 */
-
-	prev_mark_and_sweep_base_flags = thr->heap->mark_and_sweep_base_flags;
-	thr->heap->mark_and_sweep_base_flags |=
-	        DUK_MS_FLAG_NO_FINALIZERS |         /* avoid attempts to add/remove object keys */
-	        DUK_MS_FLAG_NO_OBJECT_COMPACTION;   /* avoid attempt to compact the current object */
+	 *  The new area is not tracked in the heap at all, so it's critical
+	 *  we get to free/keep it in a controlled manner.
+	 */
+
+#if defined(DUK_USE_ASSERTIONS)
+	/* Whole path must be error throw free, but we may be called from
+	 * within error handling so can't assert for error_not_allowed == 0.
+	 */
+	prev_error_not_allowed = thr->heap->error_not_allowed;
+	thr->heap->error_not_allowed = 1;
+#endif
+	prev_ms_base_flags = thr->heap->ms_base_flags;
+	thr->heap->ms_base_flags |=
+	        DUK_MS_FLAG_NO_OBJECT_COMPACTION;      /* Avoid attempt to compact the current object (all objects really). */
+	thr->heap->pf_prevent_count++;                 /* Avoid finalizers. */
+	DUK_ASSERT(thr->heap->pf_prevent_count != 0);  /* Wrap. */
 
 	new_alloc_size = DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size);
 	DUK_DDD(DUK_DDDPRINT("new hobject allocation size is %ld", (long) new_alloc_size));
 	if (new_alloc_size == 0) {
-		/* for zero size, don't push anything on valstack */
 		DUK_ASSERT(new_e_size_adjusted == 0);
 		DUK_ASSERT(new_a_size == 0);
 		DUK_ASSERT(new_h_size == 0);
 		new_p = NULL;
 	} else {
-		/* This may trigger mark-and-sweep with arbitrary side effects,
-		 * including an attempted resize of the object we're resizing,
-		 * executing a finalizer which may add or remove properties of
-		 * the object we're resizing etc.
-		 */
-
-		/* Note: buffer is dynamic so that we can 'steal' the actual
-		 * allocation later.
-		 */
-
-		new_p = (duk_uint8_t *) duk_push_dynamic_buffer(ctx, new_alloc_size);  /* errors out if out of memory */
-		DUK_ASSERT(new_p != NULL);  /* since new_alloc_size > 0 */
+		/* Alloc may trigger mark-and-sweep but no compaction, and
+		 * cannot throw.
+		 */
+#if 0  /* XXX: inject test */
+		if (1) {
+			goto alloc_failed;
+		}
+#endif
+		new_p = (duk_uint8_t *) DUK_ALLOC(thr->heap, new_alloc_size);
+		if (new_p == NULL) {
+			/* NULL always indicates alloc failure because
+			 * new_alloc_size > 0.
+			 */
+			goto alloc_failed;
+		}
 	}
 
 	/* Set up pointers to the new property area: this is hidden behind a macro
@@ -50902,27 +52386,27 @@
 	                     (void *) new_a, (void *) new_h));
 
 	/*
-	 *  Migrate array to start of entries if requested.
+	 *  Migrate array part to start of entries if requested.
 	 *
 	 *  Note: from an enumeration perspective the order of entry keys matters.
 	 *  Array keys should appear wherever they appeared before the array abandon
-	 *  operation.
+	 *  operation.  (This no longer matters much because keys are ES2015 sorted.)
 	 */
 
 	if (abandon_array) {
-		/*
-		 *  Note: assuming new_a_size == 0, and that entry part contains
-		 *  no conflicting keys, refcounts do not need to be adjusted for
-		 *  the values, as they remain exactly the same.
-		 *
-		 *  The keys, however, need to be interned, incref'd, and be
-		 *  reachable for GC.  Any intern attempt may trigger a GC and
-		 *  claim any non-reachable strings, so every key must be reachable
-		 *  at all times.
-		 *
-		 *  A longjmp must not occur here, as the new_p allocation would
-		 *  be freed without these keys being decref'd, hence the messy
-		 *  decref handling if intern fails.
+		/* Assuming new_a_size == 0, and that entry part contains
+		 * no conflicting keys, refcounts do not need to be adjusted for
+		 * the values, as they remain exactly the same.
+		 *
+		 * The keys, however, need to be interned, incref'd, and be
+		 * reachable for GC.  Any intern attempt may trigger a GC and
+		 * claim any non-reachable strings, so every key must be reachable
+		 * at all times.  Refcounts must be correct to satisfy refcount
+		 * assertions.
+		 *
+		 * A longjmp must not occur here, as the new_p allocation would
+		 * leak.  Refcounts would come out correctly as the interned
+		 * strings are valstack tracked.
 		 */
 		DUK_ASSERT(new_a_size == 0);
 
@@ -50951,20 +52435,29 @@
 			 *  must be careful.
 			 */
 
-			/* never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which is generous */
+#if 0  /* XXX: inject test */
+			if (1) {
+				goto abandon_error;
+			}
+#endif
+			/* Never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which
+			 * is generous.
+			 */
 			if (!duk_check_stack(ctx, 1)) {
 				goto abandon_error;
 			}
 			DUK_ASSERT_VALSTACK_SPACE(thr, 1);
-			key = duk_heap_string_intern_u32(thr->heap, i);
-			if (!key) {
+			key = duk_heap_strtable_intern_u32(thr->heap, i);
+			if (key == NULL) {
 				goto abandon_error;
 			}
 			duk_push_hstring(ctx, key);  /* keep key reachable for GC etc; guaranteed not to fail */
 
-			/* key is now reachable in the valstack */
-
-			DUK_HSTRING_INCREF(thr, key);   /* second incref for the entry reference */
+			/* Key is now reachable in the valstack, don't INCREF
+			 * the new allocation yet (we'll steal the refcounts
+			 * from the value stack once all keys are done).
+			 */
+
 			new_e_k[new_e_next] = key;
 			tv2 = &new_e_pv[new_e_next].v;  /* array entries are all plain values */
 			DUK_TVAL_SET_TVAL(tv2, tv1);
@@ -50978,8 +52471,9 @@
 			 */
 		}
 
+		/* Steal refcounts from value stack. */
 		DUK_DDD(DUK_DDDPRINT("abandon array: pop %ld key temps from valstack", (long) new_e_next));
-		duk_pop_n(ctx, new_e_next);
+		duk_pop_n_nodecref_unsafe(ctx, new_e_next);
 	}
 
 	/*
@@ -50992,7 +52486,7 @@
 		DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
 
 		key = DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i);
-		if (!key) {
+		if (key == NULL) {
 			continue;
 		}
 
@@ -51007,53 +52501,46 @@
 	/* the entries [new_e_next, new_e_size_adjusted[ are left uninitialized on purpose (ok, not gc reachable) */
 
 	/*
-	 *  Copy array elements to new array part.
-	 */
-
-	if (new_a_size > DUK_HOBJECT_GET_ASIZE(obj)) {
-		/* copy existing entries as is */
-		DUK_ASSERT(new_p != NULL && new_a != NULL);
-		if (DUK_HOBJECT_GET_ASIZE(obj) > 0) {
-			/* Avoid zero copy with an invalid pointer.  If obj->p is NULL,
-			 * the 'new_a' pointer will be invalid which is not allowed even
-			 * when copy size is zero.
-			 */
-			DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
-			DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj) > 0);
-			DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj), sizeof(duk_tval) * DUK_HOBJECT_GET_ASIZE(obj));
-		}
-
-		/* fill new entries with -unused- (required, gc reachable) */
-		for (i = DUK_HOBJECT_GET_ASIZE(obj); i < new_a_size; i++) {
-			duk_tval *tv = &new_a[i];
-			DUK_TVAL_SET_UNUSED(tv);
-		}
-	} else {
+	 *  Copy array elements to new array part.  If the new array part is
+	 *  larger, initialize the unused entries as UNUSED because they are
+	 *  GC reachable.
+	 */
+
 #if defined(DUK_USE_ASSERTIONS)
-		/* caller must have decref'd values above new_a_size (if that is necessary) */
-		if (!abandon_array) {
-			for (i = new_a_size; i < DUK_HOBJECT_GET_ASIZE(obj); i++) {
-				duk_tval *tv;
-				tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i);
-
-				/* current assertion is quite strong: decref's and set to unused */
-				DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv));
-			}
-		}
-#endif
-		if (new_a_size > 0) {
-			/* Avoid zero copy with an invalid pointer.  If obj->p is NULL,
-			 * the 'new_a' pointer will be invalid which is not allowed even
-			 * when copy size is zero.
-			 */
-			DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
-			DUK_ASSERT(new_a_size > 0);
-			DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj), sizeof(duk_tval) * new_a_size);
-		}
-	}
-
-	/*
-	 *  Rebuild the hash part always from scratch (guaranteed to finish).
+	/* Caller must have decref'd values above new_a_size (if that is necessary). */
+	if (!abandon_array) {
+		for (i = new_a_size; i < DUK_HOBJECT_GET_ASIZE(obj); i++) {
+			duk_tval *tv;
+			tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i);
+			DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv));
+		}
+	}
+#endif
+	if (new_a_size > DUK_HOBJECT_GET_ASIZE(obj)) {
+		array_copy_size = sizeof(duk_tval) * DUK_HOBJECT_GET_ASIZE(obj);
+	} else {
+		array_copy_size = sizeof(duk_tval) * new_a_size;
+	}
+	if (array_copy_size > 0) {
+		/* Avoid zero copy with an invalid pointer.  If obj->p is NULL,
+		 * the 'new_a' pointer will be invalid which is not allowed even
+		 * when copy size is zero.
+		 */
+		DUK_ASSERT(new_a != NULL);
+		DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
+		DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj) > 0);
+		DUK_MEMCPY((void *) new_a,
+		           (const void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj),
+		           array_copy_size);
+	}
+	for (i = DUK_HOBJECT_GET_ASIZE(obj); i < new_a_size; i++) {
+		duk_tval *tv = &new_a[i];
+		DUK_TVAL_SET_UNUSED(tv);
+	}
+
+	/*
+	 *  Rebuild the hash part always from scratch (guaranteed to finish
+	 *  as long as caller gave consistent parameters).
 	 *
 	 *  Any resize of hash part requires rehashing.  In addition, by rehashing
 	 *  get rid of any elements marked deleted (DUK__HASH_DELETED) which is critical
@@ -51061,7 +52548,11 @@
 	 */
 
 #if defined(DUK_USE_HOBJECT_HASH_PART)
-	if (DUK_UNLIKELY(new_h_size > 0)) {
+	if (new_h_size == 0) {
+		DUK_DDD(DUK_DDDPRINT("no hash part, no rehash"));
+	} else {
+		duk_uint32_t mask;
+
 		DUK_ASSERT(new_h != NULL);
 
 		/* fill new_h with u32 0xff = UNUSED */
@@ -51070,13 +52561,15 @@
 		DUK_MEMSET(new_h, 0xff, sizeof(duk_uint32_t) * new_h_size);
 
 		DUK_ASSERT(new_e_next <= new_h_size);  /* equality not actually possible */
+
+		mask = new_h_size - 1;
 		for (i = 0; i < new_e_next; i++) {
 			duk_hstring *key = new_e_k[i];
 			duk_uint32_t j, step;
 
 			DUK_ASSERT(key != NULL);
-			j = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size);
-			step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
+			j = DUK_HSTRING_GET_HASH(key) & mask;
+			step = 1;  /* Cache friendly but clustering prone. */
 
 			for (;;) {
 				DUK_ASSERT(new_h[j] != DUK__HASH_DELETED);  /* should never happen */
@@ -51086,14 +52579,11 @@
 					break;
 				}
 				DUK_DDD(DUK_DDDPRINT("rebuild miss %ld, step %ld", (long) j, (long) step));
-				j = (j + step) % new_h_size;
-
-				/* guaranteed to finish */
-				DUK_ASSERT(j != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size));
-			}
-		}
-	} else {
-		DUK_DDD(DUK_DDDPRINT("no hash part, no rehash"));
+				j = (j + step) & mask;
+
+				/* Guaranteed to finish (hash is larger than #props). */
+			}
+		}
 	}
 #endif  /* DUK_USE_HOBJECT_HASH_PART */
 
@@ -51132,30 +52622,20 @@
 	DUK_HOBJECT_SET_ASIZE(obj, new_a_size);
 	DUK_HOBJECT_SET_HSIZE(obj, new_h_size);
 
-	if (new_p) {
-		/*
-		 *  Detach actual buffer from dynamic buffer in valstack, and
-		 *  pop it from the stack.
-		 *
-		 *  XXX: the buffer object is certainly not reachable at this point,
-		 *  so it would be nice to free it forcibly even with only
-		 *  mark-and-sweep enabled.  Not a big issue though.
-		 */
-		(void) duk_steal_buffer(ctx, -1, NULL);
-		duk_pop(ctx);
-	} else {
-		DUK_ASSERT(new_alloc_size == 0);
-		/* no need to pop, nothing was pushed */
-	}
-
-	/* clear array part flag only after switching */
+	/* Clear array part flag only after switching. */
 	if (abandon_array) {
 		DUK_HOBJECT_CLEAR_ARRAY_PART(obj);
 	}
 
 	DUK_DDD(DUK_DDDPRINT("resize result: %!O", (duk_heaphdr *) obj));
 
-	thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+	DUK_ASSERT(thr->heap->pf_prevent_count > 0);
+	thr->heap->pf_prevent_count--;
+	thr->heap->ms_base_flags = prev_ms_base_flags;
+#if defined(DUK_USE_ASSERTIONS)
+	DUK_ASSERT(thr->heap->error_not_allowed == 1);
+	thr->heap->error_not_allowed = prev_error_not_allowed;
+#endif
 
 	/*
 	 *  Post resize assertions.
@@ -51167,21 +52647,25 @@
 	return;
 
 	/*
-	 *  Abandon array failed, need to decref keys already inserted
-	 *  into the beginning of new_e_k before unwinding valstack.
+	 *  Abandon array failed.  We don't need to DECREF anything
+	 *  because the references in the new allocation are not
+	 *  INCREF'd until abandon is complete.  The string interned
+	 *  keys are on the value stack and are handled normally by
+	 *  unwind.
 	 */
 
  abandon_error:
-	DUK_D(DUK_DPRINT("hobject resize failed during abandon array, decref keys"));
-	i = new_e_next;
-	while (i > 0) {
-		i--;
-		DUK_ASSERT(new_e_k != NULL);
-		DUK_ASSERT(new_e_k[i] != NULL);
-		DUK_HSTRING_DECREF(thr, new_e_k[i]);  /* side effects */
-	}
-
-	thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+ alloc_failed:
+	DUK_D(DUK_DPRINT("object property table resize failed"));
+
+	DUK_FREE(thr->heap, new_p);  /* OK for NULL. */
+
+	thr->heap->pf_prevent_count--;
+	thr->heap->ms_base_flags = prev_ms_base_flags;
+#if defined(DUK_USE_ASSERTIONS)
+	DUK_ASSERT(thr->heap->error_not_allowed == 1);
+	thr->heap->error_not_allowed = prev_error_not_allowed;
+#endif
 
 	DUK_ERROR_ALLOC_FAILED(thr);
 }
@@ -51333,7 +52817,7 @@
 	}
 
 #if defined(DUK_USE_HOBJECT_HASH_PART)
-	if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
+	if (e_size >= DUK_USE_HOBJECT_HASH_PROP_LIMIT) {
 		h_size = duk__get_default_h_size(e_size);
 	} else {
 		h_size = 0;
@@ -51394,13 +52878,15 @@
 		duk_uint32_t n;
 		duk_uint32_t i, step;
 		duk_uint32_t *h_base;
+		duk_uint32_t mask;
 
 		DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using hash part for lookup"));
 
 		h_base = DUK_HOBJECT_H_GET_BASE(heap, obj);
 		n = DUK_HOBJECT_GET_HSIZE(obj);
-		i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
-		step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
+		mask = n - 1;
+		i = DUK_HSTRING_GET_HASH(key) & mask;
+		step = 1;  /* Cache friendly but clustering prone. */
 
 		for (;;) {
 			duk_uint32_t t;
@@ -51428,10 +52914,9 @@
 				DUK_DDD(DUK_DDDPRINT("lookup miss i=%ld, t=%ld",
 				                     (long) i, (long) t));
 			}
-			i = (i + step) % n;
-
-			/* guaranteed to finish, as hash is never full */
-			DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n));
+			i = (i + step) & mask;
+
+			/* Guaranteed to finish (hash is larger than #props). */
 		}
 	}
 #endif  /* DUK_USE_HOBJECT_HASH_PART */
@@ -51536,13 +53021,14 @@
 
 #if defined(DUK_USE_HOBJECT_HASH_PART)
 	if (DUK_UNLIKELY(DUK_HOBJECT_GET_HSIZE(obj) > 0)) {
-		duk_uint32_t n;
+		duk_uint32_t n, mask;
 		duk_uint32_t i, step;
 		duk_uint32_t *h_base = DUK_HOBJECT_H_GET_BASE(thr->heap, obj);
 
 		n = DUK_HOBJECT_GET_HSIZE(obj);
-		i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
-		step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
+		mask = n - 1;
+		i = DUK_HSTRING_GET_HASH(key) & mask;
+		step = 1;  /* Cache friendly but clustering prone. */
 
 		for (;;) {
 			duk_uint32_t t = h_base[i];
@@ -51557,10 +53043,9 @@
 				break;
 			}
 			DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() miss %ld", (long) i));
-			i = (i + step) % n;
-
-			/* guaranteed to find an empty slot */
-			DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), DUK_HOBJECT_GET_HSIZE(obj)));
+			i = (i + step) & mask;
+
+			/* Guaranteed to finish (hash is larger than #props). */
 		}
 	}
 #endif  /* DUK_USE_HOBJECT_HASH_PART */
@@ -51960,6 +53445,8 @@
 		DUK_DDD(DUK_DDDPRINT("string object exotic property get for key: %!O, arr_idx: %ld",
 		                     (duk_heaphdr *) key, (long) arr_idx));
 
+		/* XXX: charlen; avoid multiple lookups? */
+
 		if (arr_idx != DUK__NO_ARRAY_INDEX) {
 			duk_hstring *h_val;
 
@@ -52200,7 +53687,7 @@
 		}
 
 		/* not found in 'curr', next in prototype chain; impose max depth */
-		if (sanity-- == 0) {
+		if (DUK_UNLIKELY(sanity-- == 0)) {
 			if (flags & DUK_GETDESC_FLAG_IGNORE_PROTOLOOP) {
 				/* treat like property not found */
 				break;
@@ -52209,7 +53696,7 @@
 			}
 		}
 		curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
-	} while (curr);
+	} while (curr != NULL);
 
 	/* out_desc is left untouched (possibly garbage), caller must use return
 	 * value to determine whether out_desc can be looked up
@@ -52559,7 +54046,7 @@
 		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
 		duk_int_t pop_count;
 
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			/* Symbols (ES2015 or hidden) don't have virtual properties. */
 			DUK_DDD(DUK_DDDPRINT("base object is a symbol, start lookup from symbol prototype"));
 			curr = thr->builtins[DUK_BIDX_SYMBOL_PROTOTYPE];
@@ -52900,11 +54387,11 @@
 		/* XXX: option to pretend property doesn't exist if sanity limit is
 		 * hit might be useful.
 		 */
-		if (sanity-- == 0) {
+		if (DUK_UNLIKELY(sanity-- == 0)) {
 			DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
 		}
 		curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
-	} while (curr);
+	} while (curr != NULL);
 
 	/*
 	 *  Not found
@@ -53584,7 +55071,7 @@
 		arr_idx = duk__push_tval_to_property_key(ctx, tv_key, &key);
 		DUK_ASSERT(key != NULL);
 
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			/* Symbols (ES2015 or hidden) don't have virtual properties. */
 			curr = thr->builtins[DUK_BIDX_SYMBOL_PROTOTYPE];
 			goto lookup;
@@ -54000,11 +55487,11 @@
 		/* XXX: option to pretend property doesn't exist if sanity limit is
 		 * hit might be useful.
 		 */
-		if (sanity-- == 0) {
+		if (DUK_UNLIKELY(sanity-- == 0)) {
 			DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
 		}
 		curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
-	} while (curr);
+	} while (curr != NULL);
 
 	/*
 	 *  Property not found in prototype chain.
@@ -54631,10 +56118,10 @@
 			/* Note: proxy handling must happen before key is string coerced. */
 
 			if (duk__proxy_check_prop(thr, obj, DUK_STRIDX_DELETE_PROPERTY, tv_key, &h_target)) {
-				/* -> [ ... trap handler ] */
+				/* -> [ ... obj key trap handler ] */
 				DUK_DDD(DUK_DDDPRINT("-> proxy object 'deleteProperty' for key %!T", (duk_tval *) tv_key));
 				duk_push_hobject(ctx, h_target);  /* target */
-				duk_push_tval(ctx, tv_key);       /* P */
+				duk_dup_m4(ctx);  /* P */
 				duk_call_method(ctx, 2 /*nargs*/);
 				tmp_bool = duk_to_boolean(ctx, -1);
 				duk_pop(ctx);
@@ -54645,6 +56132,7 @@
 				/* Target object must be checked for a conflicting
 				 * non-configurable property.
 				 */
+				tv_key = DUK_GET_TVAL_NEGIDX(ctx, -1);
 				arr_idx = duk__push_tval_to_property_key(ctx, tv_key, &key);
 				DUK_ASSERT(key != NULL);
 
@@ -54993,6 +56481,41 @@
 }
 
 /*
+ *  Fast finalizer check for an object.  Walks the prototype chain, checking
+ *  for finalizer presence using DUK_HOBJECT_FLAG_HAVE_FINALIZER which is kept
+ *  in sync with the actual property when setting/removing the finalizer.
+ */
+
+#if defined(DUK_USE_HEAPPTR16)
+DUK_INTERNAL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_heap *heap, duk_hobject *obj) {
+#else
+DUK_INTERNAL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_hobject *obj) {
+#endif
+	duk_uint_t sanity;
+
+	DUK_ASSERT(obj != NULL);
+
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	do {
+		if (DUK_UNLIKELY(DUK_HOBJECT_HAS_HAVE_FINALIZER(obj))) {
+			return 1;
+		}
+		if (DUK_UNLIKELY(sanity-- == 0)) {
+			DUK_D(DUK_DPRINT("prototype loop when checking for finalizer existence; returning false"));
+			return 0;
+		}
+#if defined(DUK_USE_HEAPPTR16)
+		DUK_ASSERT(heap != NULL);
+		obj = DUK_HOBJECT_GET_PROTOTYPE(heap, obj);
+#else
+		obj = DUK_HOBJECT_GET_PROTOTYPE(NULL, obj);  /* 'heap' arg ignored */
+#endif
+	} while (obj != NULL);
+
+	return 0;
+}
+
+/*
  *  Object.getOwnPropertyDescriptor()  (E5 Sections 15.2.3.3, 8.10.4)
  *
  *  [ ... key ] -> [ ... desc/undefined ]
@@ -56250,8 +57773,6 @@
 
 /* automatic undefs */
 #undef DUK__HASH_DELETED
-#undef DUK__HASH_INITIAL
-#undef DUK__HASH_PROBE_STEP
 #undef DUK__HASH_UNUSED
 #undef DUK__NO_ARRAY_INDEX
 #undef DUK__VALSTACK_PROXY_LOOKUP
@@ -56263,6 +57784,10 @@
 
 /* #include duk_internal.h -> already included */
 
+/*
+ *  duk_hstring charCodeAt, with and without surrogate awareness
+ */
+
 DUK_INTERNAL duk_ucodepoint_t duk_hstring_char_code_at_raw(duk_hthread *thr, duk_hstring *h, duk_uint_t pos, duk_bool_t surrogate_aware) {
 	duk_uint32_t boff;
 	const duk_uint8_t *p, *p_start, *p_end;
@@ -56310,16 +57835,87 @@
 	return cp1;
 }
 
-#if !defined(DUK_USE_HSTRING_CLEN)
-DUK_INTERNAL duk_size_t duk_hstring_get_charlen(duk_hstring *h) {
-	if (DUK_HSTRING_HAS_ASCII(h)) {
+/*
+ *  duk_hstring charlen access
+ */
+
+#if defined(DUK_USE_HSTRING_CLEN)
+DUK_LOCAL DUK_COLD duk_size_t duk__hstring_get_charlen_slowpath(duk_hstring *h) {
+	duk_size_t res;
+
+	DUK_ASSERT(h->clen == 0);  /* Checked by caller. */
+
+#if defined(DUK_USE_ROM_STRINGS)
+	/* ROM strings have precomputed clen, but if the computed clen is zero
+	 * we can still come here and can't write anything.
+	 */
+	if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)) {
+		return 0;
+	}
+#endif
+
+	res = duk_unicode_unvalidated_utf8_length(DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
+#if defined(DUK_USE_STRLEN16)
+	DUK_ASSERT(res <= 0xffffUL);  /* Bytelength checked during interning. */
+	h->clen16 = (duk_uint16_t) res;
+#else
+	h->clen = (duk_uint32_t) res;
+#endif
+	if (DUK_LIKELY(res == DUK_HSTRING_GET_BYTELEN(h))) {
+		DUK_HSTRING_SET_ASCII(h);
+	}
+	return res;
+}
+#else  /* DUK_USE_HSTRING_CLEN */
+DUK_LOCAL duk_size_t duk__hstring_get_charlen_slowpath(duk_hstring *h) {
+	if (DUK_LIKELY(DUK_HSTRING_HAS_ASCII(h))) {
 		/* Most practical strings will go here. */
 		return DUK_HSTRING_GET_BYTELEN(h);
 	} else {
-		return duk_unicode_unvalidated_utf8_length(DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
-	}
-}
-#endif  /* !DUK_USE_HSTRING_CLEN */
+		/* ASCII flag is lazy, so set it here. */
+		duk_size_t res;
+
+		/* XXX: here we could use the strcache to speed up the
+		 * computation (matters for 'i < str.length' loops).
+		 */
+
+		res = duk_unicode_unvalidated_utf8_length(DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
+
+#if defined(DUK_USE_ROM_STRINGS)
+		if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)) {
+			/* For ROM strings, can't write anything; ASCII flag
+			 * is preset so we don't need to update it.
+			 */
+			return res;
+		}
+#endif
+		if (DUK_LIKELY(res == DUK_HSTRING_GET_BYTELEN(h))) {
+			DUK_HSTRING_SET_ASCII(h);
+		}
+		return res;
+	}
+}
+#endif  /* DUK_USE_HSTRING_CLEN */
+
+#if defined(DUK_USE_HSTRING_CLEN)
+DUK_INTERNAL DUK_HOT duk_size_t duk_hstring_get_charlen(duk_hstring *h) {
+#if defined(DUK_USE_STRLEN16)
+	if (DUK_LIKELY(h->clen16 != 0)) {
+		return h->clen16;
+	}
+#else
+	if (DUK_LIKELY(h->clen != 0)) {
+		return h->clen;
+	}
+#endif
+	return duk__hstring_get_charlen_slowpath(h);
+}
+#else  /* DUK_USE_HSTRING_CLEN */
+DUK_INTERNAL DUK_HOT duk_size_t duk_hstring_get_charlen(duk_hstring *h) {
+	/* Always use slow path. */
+	return duk__hstring_get_charlen_slowpath(h);
+}
+#endif  /* DUK_USE_HSTRING_CLEN */
 #line 1 "duk_hthread_alloc.c"
 /*
  *  duk_hthread allocation and freeing.
@@ -56344,6 +57940,7 @@
 	DUK_ASSERT(thr->valstack_bottom == NULL);
 	DUK_ASSERT(thr->valstack_top == NULL);
 	DUK_ASSERT(thr->callstack == NULL);
+	DUK_ASSERT(thr->callstack_curr == NULL);
 	DUK_ASSERT(thr->catchstack == NULL);
 
 	/* valstack */
@@ -56373,6 +57970,7 @@
 	DUK_MEMZERO(thr->callstack, alloc_size);
 	thr->callstack_size = DUK_CALLSTACK_INITIAL_SIZE;
 	DUK_ASSERT(thr->callstack_top == 0);
+	DUK_ASSERT(thr->callstack_curr == NULL);
 
 	/* catchstack */
 	alloc_size = sizeof(duk_catcher) * DUK_CATCHSTACK_INITIAL_SIZE;
@@ -56454,12 +58052,13 @@
 #if defined(DUK_USE_ROM_GLOBAL_CLONE) || defined(DUK_USE_ROM_GLOBAL_INHERIT)
 DUK_LOCAL void duk__duplicate_ram_global_object(duk_hthread *thr) {
 	duk_context *ctx;
-	duk_hobject *h1;
+	duk_hobject *h_global;
 #if defined(DUK_USE_ROM_GLOBAL_CLONE)
-	duk_hobject *h2;
+	duk_hobject *h_oldglobal;
 	duk_uint8_t *props;
 	duk_size_t alloc_size;
 #endif
+	duk_hobject *h_objenv;
 
 	ctx = (duk_context *) thr;
 
@@ -56467,83 +58066,84 @@
 
 #if defined(DUK_USE_ROM_GLOBAL_INHERIT)
 	/* Inherit from ROM-based global object: less RAM usage, less transparent. */
-	h1 = duk_push_object_helper(ctx,
-	                            DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_GLOBAL),
-	                            DUK_BIDX_GLOBAL);
-	DUK_ASSERT(h1 != NULL);
+	h_global = duk_push_object_helper(ctx,
+	                                  DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                  DUK_HOBJECT_FLAG_FASTREFS |
+	                                  DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_GLOBAL),
+	                                  DUK_BIDX_GLOBAL);
+	DUK_ASSERT(h_global != NULL);
 #elif defined(DUK_USE_ROM_GLOBAL_CLONE)
 	/* Clone the properties of the ROM-based global object to create a
 	 * fully RAM-based global object.  Uses more memory than the inherit
 	 * model but more compliant.
 	 */
-	h1 = duk_push_object_helper(ctx,
-	                            DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_GLOBAL),
-	                            DUK_BIDX_OBJECT_PROTOTYPE);
-	DUK_ASSERT(h1 != NULL);
-	h2 = thr->builtins[DUK_BIDX_GLOBAL];
-	DUK_ASSERT(h2 != NULL);
+	h_global = duk_push_object_helper(ctx,
+	                                  DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                  DUK_HOBJECT_FLAG_FASTREFS |
+	                                  DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_GLOBAL),
+	                                  DUK_BIDX_OBJECT_PROTOTYPE);
+	DUK_ASSERT(h_global != NULL);
+	h_oldglobal = thr->builtins[DUK_BIDX_GLOBAL];
+	DUK_ASSERT(h_oldglobal != NULL);
 
 	/* Copy the property table verbatim; this handles attributes etc.
 	 * For ROM objects it's not necessary (or possible) to update
 	 * refcounts so leave them as is.
 	 */
-	alloc_size = DUK_HOBJECT_P_ALLOC_SIZE(h2);
+	alloc_size = DUK_HOBJECT_P_ALLOC_SIZE(h_oldglobal);
 	DUK_ASSERT(alloc_size > 0);
-	props = DUK_ALLOC(thr->heap, alloc_size);
-	if (!props) {
-		DUK_ERROR_ALLOC_FAILED(thr);
-		return;
-	}
-	DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, h2) != NULL);
-	DUK_MEMCPY((void *) props, (const void *) DUK_HOBJECT_GET_PROPS(thr->heap, h2), alloc_size);
+	props = DUK_ALLOC_CHECKED(thr, alloc_size);
+	DUK_ASSERT(props != NULL);
+	DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, h_oldglobal) != NULL);
+	DUK_MEMCPY((void *) props, (const void *) DUK_HOBJECT_GET_PROPS(thr->heap, h_oldglobal), alloc_size);
 
 	/* XXX: keep property attributes or tweak them here?
 	 * Properties will now be non-configurable even when they're
 	 * normally configurable for the global object.
 	 */
 
-	DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, h1) == NULL);
-	DUK_HOBJECT_SET_PROPS(thr->heap, h1, props);
-	DUK_HOBJECT_SET_ESIZE(h1, DUK_HOBJECT_GET_ESIZE(h2));
-	DUK_HOBJECT_SET_ENEXT(h1, DUK_HOBJECT_GET_ENEXT(h2));
-	DUK_HOBJECT_SET_ASIZE(h1, DUK_HOBJECT_GET_ASIZE(h2));
-	DUK_HOBJECT_SET_HSIZE(h1, DUK_HOBJECT_GET_HSIZE(h2));
-#else
-#error internal error in defines
-#endif
-
-	duk_hobject_compact_props(thr, h1);
+	DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, h_global) == NULL);
+	DUK_HOBJECT_SET_PROPS(thr->heap, h_global, props);
+	DUK_HOBJECT_SET_ESIZE(h_global, DUK_HOBJECT_GET_ESIZE(h_oldglobal));
+	DUK_HOBJECT_SET_ENEXT(h_global, DUK_HOBJECT_GET_ENEXT(h_oldglobal));
+	DUK_HOBJECT_SET_ASIZE(h_global, DUK_HOBJECT_GET_ASIZE(h_oldglobal));
+	DUK_HOBJECT_SET_HSIZE(h_global, DUK_HOBJECT_GET_HSIZE(h_oldglobal));
+#else
+#error internal error in config defines
+#endif
+
+	duk_hobject_compact_props(thr, h_global);
 	DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL);
-	DUK_ASSERT(!DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE((duk_heaphdr *) thr->builtins[DUK_BIDX_GLOBAL]));  /* no need to decref */
-	thr->builtins[DUK_BIDX_GLOBAL] = h1;
-	DUK_HOBJECT_INCREF(thr, h1);
-	DUK_D(DUK_DPRINT("duplicated global object: %!O", h1));
-
+	DUK_ASSERT(!DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE((duk_heaphdr *) thr->builtins[DUK_BIDX_GLOBAL]));  /* no need to decref: ROM object */
+	thr->builtins[DUK_BIDX_GLOBAL] = h_global;
+	DUK_HOBJECT_INCREF(thr, h_global);
+	DUK_D(DUK_DPRINT("duplicated global object: %!O", h_global));
 
 	/* Create a fresh object environment for the global scope.  This is
 	 * needed so that the global scope points to the newly created RAM-based
 	 * global object.
 	 */
-	h1 = duk_push_object_helper(ctx,
-	                            DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV),
-	                            -1);  /* no prototype */
-	DUK_ASSERT(h1 != NULL);
-	duk_dup_m2(ctx);
-	duk_dup_top(ctx);  /* -> [ ... new_global new_globalenv new_global new_global ] */
-	duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
-	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);  /* always provideThis=true */
-
-	duk_hobject_compact_props(thr, h1);
+	h_objenv = (duk_hobject *) duk_hobjenv_alloc(thr,
+	                                             DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                             DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV));
+	DUK_ASSERT(h_objenv != NULL);
+	DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_objenv) == NULL);
+	duk_push_hobject(ctx, h_objenv);
+
+	DUK_ASSERT(h_global != NULL);
+	((duk_hobjenv *) h_objenv)->target = h_global;
+	DUK_HOBJECT_INCREF(thr, h_global);
+	DUK_ASSERT(((duk_hobjenv *) h_objenv)->has_this == 0);
+
 	DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL_ENV] != NULL);
-	DUK_ASSERT(!DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE((duk_heaphdr *) thr->builtins[DUK_BIDX_GLOBAL_ENV]));  /* no need to decref */
-	thr->builtins[DUK_BIDX_GLOBAL_ENV] = h1;
-	DUK_HOBJECT_INCREF(thr, h1);
-	DUK_D(DUK_DPRINT("duplicated global env: %!O", h1));
-
-	duk_pop_2(ctx);
+	DUK_ASSERT(!DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE((duk_heaphdr *) thr->builtins[DUK_BIDX_GLOBAL_ENV]));  /* no need to decref: ROM object */
+	thr->builtins[DUK_BIDX_GLOBAL_ENV] = h_objenv;
+	DUK_HOBJECT_INCREF(thr, h_objenv);
+	DUK_D(DUK_DPRINT("duplicated global env: %!O", h_objenv));
+
+	DUK_ASSERT_HOBJENV_VALID((duk_hobjenv *) h_objenv);
+
+	duk_pop_2(ctx);  /* Pop global object and global env. */
 }
 #endif  /* DUK_USE_ROM_GLOBAL_CLONE || DUK_USE_ROM_GLOBAL_INHERIT */
 
@@ -56649,11 +58249,11 @@
 		duk_small_int_t len = -1;  /* must be signed */
 
 		class_num = (duk_small_uint_t) duk_bd_decode_varuint(bd);
-		len = (duk_small_int_t) duk_bd_decode_flagged(bd, DUK__LENGTH_PROP_BITS, (duk_int32_t) -1 /*def_value*/);
+		len = (duk_small_int_t) duk_bd_decode_flagged_signed(bd, DUK__LENGTH_PROP_BITS, (duk_int32_t) -1 /*def_value*/);
 
 		if (class_num == DUK_HOBJECT_CLASS_FUNCTION) {
 			duk_small_uint_t natidx;
-			duk_int_t c_nargs;  /* must hold DUK_VARARGS */
+			duk_small_int_t c_nargs;  /* must hold DUK_VARARGS */
 			duk_c_function c_func;
 			duk_int16_t magic;
 
@@ -56665,7 +58265,7 @@
 			c_func = duk_bi_native_functions[natidx];
 			DUK_ASSERT(c_func != NULL);
 
-			c_nargs = (duk_small_uint_t) duk_bd_decode_flagged(bd, DUK__NARGS_BITS, len /*def_value*/);
+			c_nargs = (duk_small_int_t) duk_bd_decode_flagged_signed(bd, DUK__NARGS_BITS, len /*def_value*/);
 			if (c_nargs == DUK__NARGS_VARARGS_MARKER) {
 				c_nargs = DUK_VARARGS;
 			}
@@ -56708,8 +58308,31 @@
 			((duk_hnatfunc *) h)->magic = magic;
 		} else if (class_num == DUK_HOBJECT_CLASS_ARRAY) {
 			duk_push_array(ctx);
-		} else {
+		} else if (class_num == DUK_HOBJECT_CLASS_OBJENV) {
+			duk_hobjenv *env;
+			duk_hobject *global;
+
+			DUK_ASSERT(i == DUK_BIDX_GLOBAL_ENV);
+			DUK_ASSERT(DUK_BIDX_GLOBAL_ENV > DUK_BIDX_GLOBAL);
+
+			env = duk_hobjenv_alloc(thr,
+	                                        DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV));
+			DUK_ASSERT(env->target == NULL);
+			duk_push_hobject(ctx, (duk_hobject *) env);
+
+			global = duk_known_hobject(ctx, DUK_BIDX_GLOBAL);
+			DUK_ASSERT(global != NULL);
+			env->target = global;
+			DUK_HOBJECT_INCREF(thr, global);
+			DUK_ASSERT(env->has_this == 0);
+
+			DUK_ASSERT_HOBJENV_VALID(env);
+		} else {
+			DUK_ASSERT(class_num != DUK_HOBJECT_CLASS_DECENV);
+
 			(void) duk_push_object_helper(ctx,
+			                              DUK_HOBJECT_FLAG_FASTREFS |
 			                              DUK_HOBJECT_FLAG_EXTENSIBLE,
 			                              -1);  /* no prototype or class yet */
 
@@ -56758,14 +58381,13 @@
 		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(h));
 		DUK_ASSERT(!DUK_HOBJECT_HAS_COMPFUNC(h));
 		/* DUK_HOBJECT_FLAG_NATFUNC varies */
-		DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(h));
+		DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(h));
 		DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(h) || class_num == DUK_HOBJECT_CLASS_ARRAY);
 		/* DUK_HOBJECT_FLAG_STRICT varies */
 		DUK_ASSERT(!DUK_HOBJECT_HAS_NATFUNC(h) ||  /* all native functions have NEWENV */
 		           DUK_HOBJECT_HAS_NEWENV(h));
 		DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(h));
 		DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(h));
-		DUK_ASSERT(!DUK_HOBJECT_HAS_ENVRECCLOSED(h));
 		/* DUK_HOBJECT_FLAG_EXOTIC_ARRAY varies */
 		/* DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ varies */
 		DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h));
@@ -57114,12 +58736,8 @@
 #endif
 			" "
 			/* Low memory options */
-#if defined(DUK_USE_STRTAB_CHAIN)
-			"c"  /* chain */
-#elif defined(DUK_USE_STRTAB_PROBE)
-			"p"  /* probe */
-#else
-			"?"
+#if defined(DUK_USE_STRTAB_PTRCOMP)
+			"s"
 #endif
 #if !defined(DUK_USE_HEAPPTR16) && !defined(DUK_DATAPTR16) && !defined(DUK_FUNCPTR16)
 			"n"
@@ -57254,7 +58872,6 @@
 	/* Order of unwinding is important */
 
 	duk_hthread_catchstack_unwind(thr, 0);
-
 	duk_hthread_callstack_unwind(thr, 0);  /* side effects, possibly errors */
 
 	thr->valstack_bottom = thr->valstack;
@@ -57277,16 +58894,6 @@
 	 */
 }
 
-DUK_INTERNAL duk_activation *duk_hthread_get_current_activation(duk_hthread *thr) {
-	DUK_ASSERT(thr != NULL);
-
-	if (thr->callstack_top > 0) {
-		return thr->callstack + thr->callstack_top - 1;
-	} else {
-		return NULL;
-	}
-}
-
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
 DUK_INTERNAL duk_uint_fast32_t duk_hthread_get_act_curr_pc(duk_hthread *thr, duk_activation *act) {
 	duk_instr_t *bcode;
@@ -57332,7 +58939,9 @@
 	if (thr->ptr_curr_pc != NULL) {
 		/* ptr_curr_pc != NULL only when bytecode executor is active. */
 		DUK_ASSERT(thr->callstack_top > 0);
-		act = thr->callstack + thr->callstack_top - 1;
+		DUK_ASSERT(thr->callstack_curr != NULL);
+		act = thr->callstack_curr;
+		DUK_ASSERT(act != NULL);
 		act->curr_pc = *thr->ptr_curr_pc;
 	}
 }
@@ -57345,7 +58954,9 @@
 	if (thr->ptr_curr_pc != NULL) {
 		/* ptr_curr_pc != NULL only when bytecode executor is active. */
 		DUK_ASSERT(thr->callstack_top > 0);
-		act = thr->callstack + thr->callstack_top - 1;
+		DUK_ASSERT(thr->callstack_curr != NULL);
+		act = thr->callstack_curr;
+		DUK_ASSERT(act != NULL);
 		act->curr_pc = *thr->ptr_curr_pc;
 		thr->ptr_curr_pc = NULL;
 	}
@@ -57375,8 +58986,7 @@
 
 /* #include duk_internal.h -> already included */
 
-/* check that there is space for at least one new entry */
-DUK_INTERNAL void duk_hthread_callstack_grow(duk_hthread *thr) {
+DUK_LOCAL DUK_COLD DUK_NOINLINE void duk__hthread_do_callstack_grow(duk_hthread *thr) {
 	duk_activation *new_ptr;
 	duk_size_t old_size;
 	duk_size_t new_size;
@@ -57385,10 +58995,6 @@
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);   /* avoid warning (unsigned) */
 	DUK_ASSERT(thr->callstack_size >= thr->callstack_top);
 
-	if (thr->callstack_top < thr->callstack_size) {
-		return;
-	}
-
 	old_size = thr->callstack_size;
 	new_size = old_size + DUK_CALLSTACK_GROW_STEP;
 
@@ -57413,10 +59019,28 @@
 	thr->callstack = new_ptr;
 	thr->callstack_size = new_size;
 
+	if (thr->callstack_top > 0) {
+		thr->callstack_curr = thr->callstack + thr->callstack_top - 1;
+	} else {
+		thr->callstack_curr = NULL;
+	}
+
 	/* note: any entries above the callstack top are garbage and not zeroed */
 }
 
-DUK_INTERNAL void duk_hthread_callstack_shrink_check(duk_hthread *thr) {
+/* check that there is space for at least one new entry */
+DUK_INTERNAL void duk_hthread_callstack_grow(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);   /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->callstack_size >= thr->callstack_top);
+
+	if (DUK_LIKELY(thr->callstack_top < thr->callstack_size)) {
+		return;
+	}
+	duk__hthread_do_callstack_grow(thr);
+}
+
+DUK_LOCAL DUK_COLD DUK_NOINLINE void duk__hthread_do_callstack_shrink(duk_hthread *thr) {
 	duk_size_t new_size;
 	duk_activation *p;
 
@@ -57424,10 +59048,6 @@
 	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);  /* avoid warning (unsigned) */
 	DUK_ASSERT(thr->callstack_size >= thr->callstack_top);
 
-	if (thr->callstack_size - thr->callstack_top < DUK_CALLSTACK_SHRINK_THRESHOLD) {
-		return;
-	}
-
 	new_size = thr->callstack_top + DUK_CALLSTACK_SHRINK_SPARE;
 	DUK_ASSERT(new_size >= thr->callstack_top);
 
@@ -57443,6 +59063,12 @@
 	if (p) {
 		thr->callstack = p;
 		thr->callstack_size = new_size;
+
+		if (thr->callstack_top > 0) {
+			thr->callstack_curr = thr->callstack + thr->callstack_top - 1;
+		} else {
+			thr->callstack_curr = NULL;
+		}
 	} else {
 		/* Because new_size != 0, if condition doesn't need to be
 		 * (p != NULL || new_size == 0).
@@ -57454,7 +59080,19 @@
 	/* note: any entries above the callstack top are garbage and not zeroed */
 }
 
-DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_top) {
+DUK_INTERNAL void duk_hthread_callstack_shrink_check(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->callstack_size >= thr->callstack_top);
+
+	if (DUK_LIKELY(thr->callstack_size - thr->callstack_top < DUK_CALLSTACK_SHRINK_THRESHOLD)) {
+		return;
+	}
+
+	duk__hthread_do_callstack_shrink(thr);
+}
+
+DUK_INTERNAL void duk_hthread_callstack_unwind_norz(duk_hthread *thr, duk_size_t new_top) {
 	duk_size_t idx;
 
 	DUK_DDD(DUK_DDDPRINT("unwind callstack top of thread %p from %ld to %ld",
@@ -57483,9 +59121,7 @@
 	while (idx > new_top) {
 		duk_activation *act;
 		duk_hobject *func;
-#if defined(DUK_USE_REFERENCE_COUNTING)
 		duk_hobject *tmp;
-#endif
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
 		duk_heap *heap;
 #endif
@@ -57527,12 +59163,12 @@
 					DUK_TVAL_SET_NULL(tv_caller);   /* no incref needed */
 					DUK_ASSERT(act->prev_caller == NULL);
 				}
-				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+				DUK_TVAL_DECREF_NORZ(thr, &tv_tmp);
 			} else {
 				h_tmp = act->prev_caller;
 				if (h_tmp) {
 					act->prev_caller = NULL;
-					DUK_HOBJECT_DECREF(thr, h_tmp);  /* side effects */
+					DUK_HOBJECT_DECREF_NORZ(thr, h_tmp);
 				}
 			}
 			act = thr->callstack + idx;  /* avoid side effects */
@@ -57552,8 +59188,12 @@
 			/* Pause for all step types: step into, step over, step out.
 			 * This is the only place explicitly handling a step out.
 			 */
-			DUK_HEAP_SET_PAUSED(heap);
-			DUK_ASSERT(heap->dbg_step_thread == NULL);
+			if (duk_debug_is_paused(heap)) {
+				DUK_D(DUK_DPRINT("step pause trigger but already paused, ignoring"));
+			} else {
+				duk_debug_set_paused(heap);
+				DUK_ASSERT(heap->dbg_step_thread == NULL);
+			}
 		}
 #endif
 
@@ -57574,42 +59214,19 @@
 		}
 		/* func is NULL for lightfunc */
 
+		/* Catch sites are required to clean up their environments
+		 * in FINALLY part before propagating, so this should
+		 * always hold here.
+		 */
 		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, func, act->idx_bottom);
+			duk_js_close_environment_record(thr, act->var_env);
 			act = thr->callstack + idx;  /* avoid side effect issues */
 		}
 
-#if 0
-		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 *) 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((act->lex_env == NULL) ||
-		           ((duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
-		            (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
-		            (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
-		            (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
-
-		DUK_ASSERT((act->var_env == NULL) ||
-		           ((duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
-		            (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
-		            (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
-		            (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
-
 	 skip_env_close:
 
 		/*
@@ -57622,55 +59239,38 @@
 		}
 
 		/*
-		 *  Reference count updates
-		 *
-		 *  Note: careful manipulation of refcounts.  The top is
-		 *  not updated yet, so all the activations are reachable
-		 *  for mark-and-sweep (which may be triggered by decref).
-		 *  However, the pointers are NULL so this is not an issue.
-		 */
-
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		tmp = act->var_env;
-#endif
+		 *  Reference count updates, using NORZ macros so we don't
+		 *  need to handle side effects.
+		 */
+
+		DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, act->var_env);
 		act->var_env = NULL;
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);
-		act = thr->callstack + idx;  /* avoid side effect issues */
-#endif
-
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		tmp = act->lex_env;
-#endif
+		DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, act->lex_env);
 		act->lex_env = NULL;
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);
-		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.
 		 */
-#if defined(DUK_USE_REFERENCE_COUNTING)
 		tmp = DUK_ACT_GET_FUNC(act);
-#endif
+		DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, tmp);
+		DUK_UNREF(tmp);
 		act->func = NULL;
-#if defined(DUK_USE_REFERENCE_COUNTING)
-		DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);
-		act = thr->callstack + idx;  /* avoid side effect issues */
-		DUK_UNREF(act);
-#endif
 	}
 
 	thr->callstack_top = new_top;
+	if (new_top > 0) {
+		thr->callstack_curr = thr->callstack + new_top - 1;
+	} else {
+		thr->callstack_curr = NULL;
+	}
 
 	/*
 	 *  We could clear the book-keeping variables for the topmost activation,
 	 *  but don't do so now.
 	 */
 #if 0
-	if (thr->callstack_top > 0) {
-		duk_activation *act = thr->callstack + thr->callstack_top - 1;
+	if (thr->callstack_curr != NULL) {
+		duk_activation *act = thr->callstack_curr;
 		act->idx_retval = 0;
 	}
 #endif
@@ -57681,7 +59281,12 @@
 	 */
 }
 
-DUK_INTERNAL void duk_hthread_catchstack_grow(duk_hthread *thr) {
+DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_top) {
+	duk_hthread_callstack_unwind_norz(thr, new_top);
+	DUK_REFZERO_CHECK_FAST(thr);
+}
+
+DUK_LOCAL DUK_COLD DUK_NOINLINE void duk__hthread_do_catchstack_grow(duk_hthread *thr) {
 	duk_catcher *new_ptr;
 	duk_size_t old_size;
 	duk_size_t new_size;
@@ -57690,10 +59295,6 @@
 	DUK_ASSERT_DISABLE(thr->catchstack_top);  /* avoid warning (unsigned) */
 	DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top);
 
-	if (thr->catchstack_top < thr->catchstack_size) {
-		return;
-	}
-
 	old_size = thr->catchstack_size;
 	new_size = old_size + DUK_CATCHSTACK_GROW_STEP;
 
@@ -57721,7 +59322,19 @@
 	/* note: any entries above the catchstack top are garbage and not zeroed */
 }
 
-DUK_INTERNAL void duk_hthread_catchstack_shrink_check(duk_hthread *thr) {
+DUK_INTERNAL void duk_hthread_catchstack_grow(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->catchstack_top);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top);
+
+	if (DUK_LIKELY(thr->catchstack_top < thr->catchstack_size)) {
+		return;
+	}
+
+	duk__hthread_do_catchstack_grow(thr);
+}
+
+DUK_LOCAL DUK_COLD DUK_NOINLINE void duk__hthread_do_catchstack_shrink(duk_hthread *thr) {
 	duk_size_t new_size;
 	duk_catcher *p;
 
@@ -57729,10 +59342,6 @@
 	DUK_ASSERT_DISABLE(thr->catchstack_top >= 0);  /* avoid warning (unsigned) */
 	DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top);
 
-	if (thr->catchstack_size - thr->catchstack_top < DUK_CATCHSTACK_SHRINK_THRESHOLD) {
-		return;
-	}
-
 	new_size = thr->catchstack_top + DUK_CATCHSTACK_SHRINK_SPARE;
 	DUK_ASSERT(new_size >= thr->catchstack_top);
 
@@ -57759,7 +59368,19 @@
 	/* note: any entries above the catchstack top are garbage and not zeroed */
 }
 
-DUK_INTERNAL void duk_hthread_catchstack_unwind(duk_hthread *thr, duk_size_t new_top) {
+DUK_INTERNAL void duk_hthread_catchstack_shrink_check(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->catchstack_top >= 0);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top);
+
+	if (DUK_LIKELY(thr->catchstack_size - thr->catchstack_top < DUK_CATCHSTACK_SHRINK_THRESHOLD)) {
+		return;
+	}
+
+	duk__hthread_do_catchstack_shrink(thr);
+}
+
+DUK_INTERNAL void duk_hthread_catchstack_unwind_norz(duk_hthread *thr, duk_size_t new_top) {
 	duk_size_t idx;
 
 	DUK_DDD(DUK_DDDPRINT("unwind catchstack top of thread %p from %ld to %ld",
@@ -57815,7 +59436,8 @@
 			env = act->lex_env;             /* current lex_env of the activation (created for catcher) */
 			DUK_ASSERT(env != NULL);        /* must be, since env was created when catcher was created */
 			act->lex_env = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, env);  /* prototype is lex_env before catcher created */
-			DUK_HOBJECT_DECREF(thr, env);
+			DUK_HOBJECT_INCREF(thr, act->lex_env);
+			DUK_HOBJECT_DECREF_NORZ(thr, env);
 
 			/* There is no need to decref anything else than 'env': if 'env'
 			 * becomes unreachable, refzero will handle decref'ing its prototype.
@@ -57827,6 +59449,100 @@
 
 	/* note: any entries above the catchstack top are garbage and not zeroed */
 }
+
+DUK_INTERNAL void duk_hthread_catchstack_unwind(duk_hthread *thr, duk_size_t new_top) {
+	duk_hthread_catchstack_unwind_norz(thr, new_top);
+	DUK_REFZERO_CHECK_FAST(thr);
+}
+
+#if defined(DUK_USE_FINALIZER_TORTURE)
+DUK_INTERNAL void duk_hthread_valstack_torture_realloc(duk_hthread *thr) {
+	duk_size_t alloc_size;
+	duk_tval *new_ptr;
+	duk_ptrdiff_t end_off;
+	duk_ptrdiff_t bottom_off;
+	duk_ptrdiff_t top_off;
+
+	if (thr->valstack == NULL) {
+		return;
+	}
+
+	end_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_end - (duk_uint8_t *) thr->valstack);
+	bottom_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_bottom - (duk_uint8_t *) thr->valstack);
+	top_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) thr->valstack);
+	alloc_size = (duk_size_t) end_off;
+	if (alloc_size == 0) {
+		return;
+	}
+
+	new_ptr = (duk_tval *) DUK_ALLOC(thr->heap, alloc_size);
+	if (new_ptr != NULL) {
+		DUK_MEMCPY((void *) new_ptr, (const void *) thr->valstack, alloc_size);
+		DUK_MEMSET((void *) thr->valstack, 0x55, alloc_size);
+		DUK_FREE(thr->heap, (void *) thr->valstack);
+		thr->valstack = new_ptr;
+		thr->valstack_end = (duk_tval *) ((duk_uint8_t *) new_ptr + end_off);
+		thr->valstack_bottom = (duk_tval *) ((duk_uint8_t *) new_ptr + bottom_off);
+		thr->valstack_top = (duk_tval *) ((duk_uint8_t *) new_ptr + top_off);
+		/* No change in size. */
+	} else {
+		DUK_D(DUK_DPRINT("failed to realloc valstack for torture, ignore"));
+	}
+}
+
+DUK_INTERNAL void duk_hthread_callstack_torture_realloc(duk_hthread *thr) {
+	duk_size_t alloc_size;
+	duk_activation *new_ptr;
+	duk_ptrdiff_t curr_off;
+
+	if (thr->callstack == NULL) {
+		return;
+	}
+
+	curr_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->callstack_curr - (duk_uint8_t *) thr->callstack);
+	alloc_size = sizeof(duk_activation) * thr->callstack_size;
+	if (alloc_size == 0) {
+		return;
+	}
+
+	new_ptr = (duk_activation *) DUK_ALLOC(thr->heap, alloc_size);
+	if (new_ptr != NULL) {
+		DUK_MEMCPY((void *) new_ptr, (const void *) thr->callstack, alloc_size);
+		DUK_MEMSET((void *) thr->callstack, 0x55, alloc_size);
+		DUK_FREE(thr->heap, (void *) thr->callstack);
+		thr->callstack = new_ptr;
+		thr->callstack_curr = (duk_activation *) ((duk_uint8_t *) new_ptr + curr_off);
+		/* No change in size. */
+	} else {
+		DUK_D(DUK_DPRINT("failed to realloc callstack for torture, ignore"));
+	}
+}
+
+DUK_INTERNAL void duk_hthread_catchstack_torture_realloc(duk_hthread *thr) {
+	duk_size_t alloc_size;
+	duk_catcher *new_ptr;
+
+	if (thr->catchstack == NULL) {
+		return;
+	}
+
+	alloc_size = sizeof(duk_catcher) * thr->catchstack_size;
+	if (alloc_size == 0) {
+		return;
+	}
+
+	new_ptr = (duk_catcher *) DUK_ALLOC(thr->heap, alloc_size);
+	if (new_ptr != NULL) {
+		DUK_MEMCPY((void *) new_ptr, (const void *) thr->catchstack, alloc_size);
+		DUK_MEMSET((void *) thr->catchstack, 0x55, alloc_size);
+		DUK_FREE(thr->heap, (void *) thr->catchstack);
+		thr->catchstack = new_ptr;
+		/* No change in size. */
+	} else {
+		DUK_D(DUK_DPRINT("failed to realloc catchstack for torture, ignore"));
+	}
+}
+#endif  /* DUK_USE_FINALIZER_TORTURE */
 #line 1 "duk_js_arith.c"
 /*
  *  Shared helpers for arithmetic operations
@@ -57989,6 +59705,8 @@
 
 /* #include duk_internal.h -> already included */
 
+/* XXX: heap->error_not_allowed for success path too? */
+
 /*
  *  Forward declarations.
  */
@@ -58144,16 +59862,19 @@
 
 	arg = duk_push_object_helper(ctx,
 	                             DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                             DUK_HOBJECT_FLAG_FASTREFS |
 	                             DUK_HOBJECT_FLAG_ARRAY_PART |
 	                             DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARGUMENTS),
 	                             DUK_BIDX_OBJECT_PROTOTYPE);
 	DUK_ASSERT(arg != NULL);
 	(void) duk_push_object_helper(ctx,
 	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_FLAG_FASTREFS |
 	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                              -1);  /* no prototype */
 	(void) duk_push_object_helper(ctx,
 	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_FLAG_FASTREFS |
 	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
 	                              -1);  /* no prototype */
 	i_arg = duk_get_top(ctx) - 3;
@@ -58463,7 +60184,7 @@
 		                     (long) num_stack_args, (long) idx_func, duk_get_tval(ctx, idx_func)));
 	} while (--sanity > 0);
 
-	if (sanity == 0) {
+	if (DUK_UNLIKELY(sanity == 0)) {
 		DUK_ERROR_RANGE(thr, DUK_STR_BOUND_CHAIN_LIMIT);
 	}
 
@@ -58537,7 +60258,9 @@
 		return;
 	}
 
-	act_callee = thr->callstack + thr->callstack_top - 1;
+	DUK_ASSERT(thr->callstack_top > 0);
+	act_callee = thr->callstack_curr;
+	DUK_ASSERT(act_callee != NULL);
 	act_caller = (thr->callstack_top >= 2 ? act_callee - 1 : NULL);
 
 	/* XXX: check .caller writability? */
@@ -59018,16 +60741,6 @@
 		 */
 		duk__handle_call_inner(thr, num_stack_args, call_flags, idx_func);
 
-		/* Success path handles */
-		DUK_ASSERT(thr->heap->call_recursion_depth == entry_call_recursion_depth);
-		DUK_ASSERT(thr->ptr_curr_pc == entry_ptr_curr_pc);
-
-		/* Longjmp state is kept clean in success path */
-		DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN);
-		DUK_ASSERT(thr->heap->lj.iserror == 0);
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1));
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2));
-
 		thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
 
 		return DUK_EXEC_SUCCESS;
@@ -59054,11 +60767,6 @@
 		                       idx_func,
 		                       old_jmpbuf_ptr);
 
-		/* Longjmp state is cleaned up by error handling */
-		DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN);
-		DUK_ASSERT(thr->heap->lj.iserror == 0);
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1));
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2));
 		return DUK_EXEC_ERROR;
 	}
 #if defined(DUK_USE_CPP_EXCEPTIONS)
@@ -59084,6 +60792,7 @@
 			                       entry_ptr_curr_pc,
 			                       idx_func,
 			                       old_jmpbuf_ptr);
+
 			return DUK_EXEC_ERROR;
 		}
 	} catch (...) {
@@ -59104,6 +60813,7 @@
 			                       entry_ptr_curr_pc,
 			                       idx_func,
 			                       old_jmpbuf_ptr);
+
 			return DUK_EXEC_ERROR;
 		}
 	}
@@ -59294,7 +61004,8 @@
 
 	duk_hthread_callstack_grow(thr);
 
-	if (thr->callstack_top > 0) {
+	act = thr->callstack_curr;
+	if (act != NULL) {
 		/*
 		 *  Update idx_retval of current activation.
 		 *
@@ -59305,12 +61016,13 @@
 		 *  the Ecmascript call's idx_retval must be set for things to work.
 		 */
 
-		(thr->callstack + thr->callstack_top - 1)->idx_retval = entry_valstack_bottom_index + idx_func;
+		act->idx_retval = entry_valstack_bottom_index + idx_func;
 	}
 
 	DUK_ASSERT(thr->callstack_top < thr->callstack_size);
 	act = thr->callstack + thr->callstack_top;
 	thr->callstack_top++;
+	thr->callstack_curr = act;
 	DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
 	DUK_ASSERT(thr->valstack_top > thr->valstack_bottom);  /* at least effective 'this' */
 	DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUNDFUNC(func));
@@ -59401,8 +61113,8 @@
 #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
 	if (func) {
 		duk__update_func_caller_prop(thr, func);
-	}
-	act = thr->callstack + thr->callstack_top - 1;
+		act = thr->callstack_curr;
+	}
 #endif
 
 	/* [ ... func this arg1 ... argN ] */
@@ -59447,7 +61159,7 @@
 
 				/* [ ... func this arg1 ... argN envobj ] */
 
-				act = thr->callstack + thr->callstack_top - 1;
+				act = thr->callstack_curr;
 				act->lex_env = env;
 				act->var_env = env;
 				DUK_HOBJECT_INCREF(thr, env);
@@ -59462,6 +61174,7 @@
 			DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func));
 
 			duk__handle_oldenv_for_call(thr, func, act);
+			/* No need to re-lookup 'act' at present: no side effects. */
 
 			DUK_ASSERT(act->lex_env != NULL);
 			DUK_ASSERT(act->var_env != NULL);
@@ -59498,6 +61211,7 @@
 	 *  new value stack bottom, and call the target.
 	 */
 
+	act = thr->callstack_curr;
 	if (func != NULL && DUK_HOBJECT_IS_COMPFUNC(func)) {
 		/*
 		 *  Ecmascript call
@@ -59540,10 +61254,9 @@
 
 		DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);  /* may need unwind */
 		DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
-		DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
-		duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+		duk_hthread_catchstack_unwind_norz(thr, entry_catchstack_top);
 		duk_hthread_catchstack_shrink_check(thr);
-		duk_hthread_callstack_unwind(thr, entry_callstack_top);
+		duk_hthread_callstack_unwind_norz(thr, entry_callstack_top);  /* XXX: may now fail */
 		duk_hthread_callstack_shrink_check(thr);
 
 		thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
@@ -59605,7 +61318,7 @@
 
 		DUK_ASSERT(thr->catchstack_top == entry_catchstack_top);  /* no need to unwind */
 		DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
-		duk_hthread_callstack_unwind(thr, entry_callstack_top);
+		duk_hthread_callstack_unwind_norz(thr, entry_callstack_top);
 		duk_hthread_callstack_shrink_check(thr);
 
 		thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
@@ -59660,9 +61373,12 @@
 	DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread);  /* may be NULL */
 	thr->state = (duk_uint8_t) entry_thread_state;
 
+	/* Disabled assert: triggered with some torture tests. */
+#if 0
 	DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
 	           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
 	           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
+#endif
 
 	thr->heap->call_recursion_depth = entry_call_recursion_depth;
 
@@ -59675,7 +61391,7 @@
 	 * on every return should have no ill effect.
 	 */
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
-	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
+	if (duk_debug_is_attached(thr->heap)) {
 		DUK_DD(DUK_DDPRINT("returning with debugger enabled, force interrupt"));
 		DUK_ASSERT(thr->interrupt_counter <= thr->interrupt_init);
 		thr->interrupt_init -= thr->interrupt_counter;
@@ -59688,6 +61404,14 @@
 	duk__interrupt_fixup(thr, entry_curr_thread);
 #endif
 
+	/* Restored by success path. */
+	DUK_ASSERT(thr->heap->call_recursion_depth == entry_call_recursion_depth);
+	DUK_ASSERT(thr->ptr_curr_pc == entry_ptr_curr_pc);
+
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
+
+	DUK_REFZERO_CHECK_FAST(thr);
+
 	return;
 
  thread_state_error:
@@ -59719,6 +61443,7 @@
 	 * the error here.
 	 */
 	DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);
+	DUK_ASSERT_LJSTATE_SET(thr->heap);
 	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
 	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);
 
@@ -59740,9 +61465,9 @@
 	 * scopes; this is a sandboxing issue, described in:
 	 * https://github.com/svaarala/duktape/issues/476
 	 */
-	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+	duk_hthread_catchstack_unwind_norz(thr, entry_catchstack_top);
 	duk_hthread_catchstack_shrink_check(thr);
-	duk_hthread_callstack_unwind(thr, entry_callstack_top);
+	duk_hthread_callstack_unwind_norz(thr, entry_callstack_top);
 	duk_hthread_callstack_shrink_check(thr);
 
 	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
@@ -59793,9 +61518,12 @@
 	DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread);  /* may be NULL */
 	thr->state = (duk_uint8_t) entry_thread_state;
 
+	/* Disabled assert: triggered with some torture tests. */
+#if 0
 	DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
 	           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
 	           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
+#endif
 
 	thr->heap->call_recursion_depth = entry_call_recursion_depth;
 
@@ -59808,7 +61536,7 @@
 	 * on every return should have no ill effect.
 	 */
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
-	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
+	if (duk_debug_is_attached(thr->heap)) {
 		DUK_DD(DUK_DDPRINT("returning with debugger enabled, force interrupt"));
 		DUK_ASSERT(thr->interrupt_counter <= thr->interrupt_init);
 		thr->interrupt_init -= thr->interrupt_counter;
@@ -59820,6 +61548,21 @@
 #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG)
 	duk__interrupt_fixup(thr, entry_curr_thread);
 #endif
+
+	/* Error handling complete, remove side effect protections and
+	 * process pending finalizers.
+	 */
+#if defined(DUK_USE_ASSERTIONS)
+	DUK_ASSERT(thr->heap->error_not_allowed == 1);
+	thr->heap->error_not_allowed = 0;
+#endif
+	DUK_ASSERT(thr->heap->pf_prevent_count > 0);
+	thr->heap->pf_prevent_count--;
+	DUK_DD(DUK_DDPRINT("call error handled, pf_prevent_count updated to %ld", (long) thr->heap->pf_prevent_count));
+
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
+
+	DUK_REFZERO_CHECK_SLOW(thr);
 }
 
 /*
@@ -59917,12 +61660,6 @@
 		                            entry_callstack_top,
 		                            entry_catchstack_top);
 
-		/* Longjmp state is kept clean in success path */
-		DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN);
-		DUK_ASSERT(thr->heap->lj.iserror == 0);
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1));
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2));
-
 		/* Note: either pointer may be NULL (at entry), so don't assert */
 		thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
 
@@ -59942,12 +61679,6 @@
 		                            entry_catchstack_top,
 		                            old_jmpbuf_ptr);
 
-		/* Longjmp state is cleaned up by error handling */
-		DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN);
-		DUK_ASSERT(thr->heap->lj.iserror == 0);
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1));
-		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2));
-
 		retval = DUK_EXEC_ERROR;
 	}
 #if defined(DUK_USE_CPP_EXCEPTIONS)
@@ -59992,6 +61723,8 @@
 
 	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == old_jmpbuf_ptr);  /* success/error path both do this */
 
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
+
 	duk__handle_safe_call_shared(thr,
 	                             idx_retbase,
 	                             num_stack_rets,
@@ -60106,6 +61839,10 @@
 	DUK_ASSERT(thr->callstack_top == entry_callstack_top);
 
 	duk__safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, rc);
+
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
+
+	DUK_REFZERO_CHECK_FAST(thr);
 	return;
 
  thread_state_error:
@@ -60140,6 +61877,7 @@
 	 * the error here.
 	 */
 	DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);
+	DUK_ASSERT_LJSTATE_SET(thr->heap);
 	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
 	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);
 
@@ -60148,9 +61886,9 @@
 
 	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);
 	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
-	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+	duk_hthread_catchstack_unwind_norz(thr, entry_catchstack_top);
 	duk_hthread_catchstack_shrink_check(thr);
-	duk_hthread_callstack_unwind(thr, entry_callstack_top);
+	duk_hthread_callstack_unwind_norz(thr, entry_callstack_top);
 	duk_hthread_callstack_shrink_check(thr);
 	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
 
@@ -60182,6 +61920,21 @@
 	thr->heap->lj.iserror = 0;
 	DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value1);  /* side effects */
 	DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value2);  /* side effects */
+
+	/* Error handling complete, remove side effect protections and
+	 * process pending finalizers.
+	 */
+#if defined(DUK_USE_ASSERTIONS)
+	DUK_ASSERT(thr->heap->error_not_allowed == 1);
+	thr->heap->error_not_allowed = 0;
+#endif
+	DUK_ASSERT(thr->heap->pf_prevent_count > 0);
+	thr->heap->pf_prevent_count--;
+	DUK_DD(DUK_DDPRINT("safe call error handled, pf_prevent_count updated to %ld", (long) thr->heap->pf_prevent_count));
+
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
+
+	DUK_REFZERO_CHECK_SLOW(thr);
 }
 
 DUK_LOCAL void duk__handle_safe_call_shared(duk_hthread *thr,
@@ -60228,6 +61981,8 @@
 #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG)
 	duk__interrupt_fixup(thr, entry_curr_thread);
 #endif
+
+	DUK_ASSERT_LJSTATE_UNSET(thr->heap);
 }
 
 /*
@@ -60415,7 +62170,8 @@
 		DUK_ASSERT(thr->callstack_top >= 1);
 		DUK_ASSERT((call_flags & DUK_CALL_FLAG_IS_RESUME) == 0);
 
-		act = thr->callstack + thr->callstack_top - 1;
+		act = thr->callstack_curr;
+		DUK_ASSERT(act != NULL);
 		if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
 			/* See: test-bug-tailcall-preventyield-assert.c. */
 			DUK_DDD(DUK_DDDPRINT("tail call prevented by current activation having DUK_ACT_FLAG_PREVENTYIELD"));
@@ -60462,16 +62218,17 @@
 				break;
 			}
 		}
-		duk_hthread_catchstack_unwind(thr, i_stk + 1);
+		duk_hthread_catchstack_unwind_norz(thr, i_stk + 1);
 
 		/* Unwind the topmost callstack entry before reusing it */
 		DUK_ASSERT(thr->callstack_top > 0);
-		duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
+		duk_hthread_callstack_unwind_norz(thr, thr->callstack_top - 1);
 
 		/* Then reuse the unwound activation; callstack was not shrunk so there is always space */
+		DUK_ASSERT(thr->callstack_top < thr->callstack_size);
+		act = thr->callstack + thr->callstack_top;
 		thr->callstack_top++;
-		DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
-		act = thr->callstack + thr->callstack_top - 1;
+		thr->callstack_curr = act;
 
 		/* Start filling in the activation */
 		act->func = func;  /* don't want an intermediate exposed state with func == NULL */
@@ -60488,7 +62245,7 @@
 		DUK_TVAL_SET_OBJECT(&act->tv_func, func);  /* borrowed, no refcount */
 #if defined(DUK_USE_REFERENCE_COUNTING)
 		DUK_HOBJECT_INCREF(thr, func);
-		act = thr->callstack + thr->callstack_top - 1;  /* side effects (currently none though) */
+		act = thr->callstack_curr;  /* side effects (currently none though) */
 #endif
 
 #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
@@ -60500,7 +62257,7 @@
 		 * is in use.
 		 */
 		duk__update_func_caller_prop(thr, func);
-		act = thr->callstack + thr->callstack_top - 1;
+		act = thr->callstack_curr;
 #endif
 
 		act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
@@ -60557,7 +62314,7 @@
 			DUK_DDD(DUK_DDDPRINT("update to current activation idx_retval"));
 			DUK_ASSERT(thr->callstack_top < thr->callstack_size);
 			DUK_ASSERT(thr->callstack_top >= 1);
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
 			DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(act)));
 			act->idx_retval = entry_valstack_bottom_index + idx_func;
@@ -60566,6 +62323,7 @@
 		DUK_ASSERT(thr->callstack_top < thr->callstack_size);
 		act = thr->callstack + thr->callstack_top;
 		thr->callstack_top++;
+		thr->callstack_curr = act;
 		DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
 
 		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(func));
@@ -60598,7 +62356,7 @@
 
 #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
 		duk__update_func_caller_prop(thr, func);
-		act = thr->callstack + thr->callstack_top - 1;
+		act = thr->callstack_curr;
 #endif
 	}
 
@@ -60627,6 +62385,7 @@
 		 */
 
 		duk__handle_oldenv_for_call(thr, func, act);
+		/* No need to re-lookup 'act' at present: no side effects. */
 
 		DUK_ASSERT(act->lex_env != NULL);
 		DUK_ASSERT(act->var_env != NULL);
@@ -60656,7 +62415,7 @@
 
 	/* [ ... arg1 ... argN envobj ] */
 
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
 	act->lex_env = env;
 	act->var_env = env;
 	DUK_HOBJECT_INCREF(thr, act->lex_env);
@@ -60692,6 +62451,7 @@
 	 *  the topmost activation.
 	 */
 
+	DUK_REFZERO_CHECK_FAST(thr);
 	return 1;
 }
 #line 1 "duk_js_compiler.c"
@@ -62695,6 +64455,8 @@
 	duk_pop(ctx);
 	return ret;
 #else
+	DUK_UNREF(comp_ctx);
+	DUK_UNREF(rc);
 	DUK_ASSERT((rc & DUK__CONST_MARKER) == 0);  /* caller removes const marker */
 	return 0;
 #endif
@@ -62946,15 +64708,31 @@
 				DUK_DDD(DUK_DDDPRINT("arith inline check: d1=%lf, d2=%lf, op=%ld",
 				                     (double) d1, (double) d2, (long) x->op));
 				switch (x->op) {
-				case DUK_OP_ADD: d3 = d1 + d2; break;
-				case DUK_OP_SUB: d3 = d1 - d2; break;
-				case DUK_OP_MUL: d3 = d1 * d2; break;
-				case DUK_OP_DIV: d3 = d1 / d2; break;
+				case DUK_OP_ADD: {
+					d3 = d1 + d2;
+					break;
+				}
+				case DUK_OP_SUB: {
+					d3 = d1 - d2;
+					break;
+				}
+				case DUK_OP_MUL: {
+					d3 = d1 * d2;
+					break;
+				}
+				case DUK_OP_DIV: {
+					d3 = d1 / d2;
+					break;
+				}
 				case DUK_OP_EXP: {
 					d3 = (duk_double_t) duk_js_arith_pow((double) d1, (double) d2);
 					break;
 				}
-				default: accept_fold = 0; break;
+				default: {
+					d3 = 0.0;  /* Won't be used, but silence MSVC /W4 warning. */
+					accept_fold = 0;
+					break;
+				}
 				}
 
 				if (accept_fold) {
@@ -66868,6 +68646,7 @@
 	duk_small_uint_t stmt_flags = 0;
 	duk_int_t label_id = -1;
 	duk_small_uint_t tok;
+	duk_bool_t test_func_decl;
 
 	DUK__RECURSION_INCREASE(comp_ctx, thr);
 
@@ -66933,17 +68712,15 @@
 		 *  for function statements are modelled after V8, see
 		 *  test-dev-func-decl-outside-top.js.
 		 */
-
+		test_func_decl = allow_source_elem;
 #if defined(DUK_USE_NONSTD_FUNC_STMT)
 		/* Lenient: allow function declarations outside top level in
 		 * non-strict mode but reject them in strict mode.
 		 */
-		if (allow_source_elem || !comp_ctx->curr_func.is_strict)
-#else  /* DUK_USE_NONSTD_FUNC_STMT */
+		test_func_decl = test_func_decl || !comp_ctx->curr_func.is_strict;
+#endif  /* DUK_USE_NONSTD_FUNC_STMT */
 		/* Strict: never allow function declarations outside top level. */
-		if (allow_source_elem)
-#endif  /* DUK_USE_NONSTD_FUNC_STMT */
-		{
+		if (test_func_decl) {
 			/* FunctionDeclaration: not strictly a statement but handled as such.
 			 *
 			 * O(depth^2) parse count for inner functions is handled by recording a
@@ -67655,6 +69432,9 @@
 		                     (duk_tval *) duk_get_tval(ctx, -2),
 		                     (duk_tval *) duk_get_tval(ctx, -1)));
 
+#if defined(DUK_USE_FASTINT)
+		DUK_ASSERT(DUK_TVAL_IS_NULL(duk_get_tval(ctx, -1)) || DUK_TVAL_IS_FASTINT(duk_get_tval(ctx, -1)));
+#endif
 		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);  /* [ ... name reg/null ] -> [ ... ] */
 	}
 
@@ -67720,8 +69500,7 @@
 				duk_push_null(ctx);
 
 				declvar_flags = DUK_PROPDESC_FLAG_WRITABLE |
-			                        DUK_PROPDESC_FLAG_ENUMERABLE |
-				                DUK_BC_DECLVAR_FLAG_UNDEF_VALUE;
+			                        DUK_PROPDESC_FLAG_ENUMERABLE;
 				if (configurable_bindings) {
 					declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
 				}
@@ -68398,9 +70177,9 @@
 	DUK_ASSERT(lex_pt != NULL);
 
 	flags = comp_stk->flags;
-	is_eval = (flags & DUK_JS_COMPILE_FLAG_EVAL ? 1 : 0);
-	is_strict = (flags & DUK_JS_COMPILE_FLAG_STRICT ? 1 : 0);
-	is_funcexpr = (flags & DUK_JS_COMPILE_FLAG_FUNCEXPR ? 1 : 0);
+	is_eval = (flags & DUK_COMPILE_EVAL ? 1 : 0);
+	is_strict = (flags & DUK_COMPILE_STRICT ? 1 : 0);
+	is_funcexpr = (flags & DUK_COMPILE_FUNCEXPR ? 1 : 0);
 
 	h_filename = duk_get_hstring(ctx, -1);  /* may be undefined */
 
@@ -68476,7 +70255,7 @@
 	 */
 
 	DUK_ASSERT(func->is_setget == 0);
-	func->is_strict = is_strict;
+	func->is_strict = (duk_uint8_t) is_strict;
 	DUK_ASSERT(func->is_notail == 0);
 
 	if (is_funcexpr) {
@@ -68491,8 +70270,9 @@
 		(void) duk__parse_func_like_raw(comp_ctx, 0 /*flags*/);
 	} else {
 		DUK_ASSERT(func->is_function == 0);
-		func->is_eval = is_eval;
-		func->is_global = !is_eval;
+		DUK_ASSERT(is_eval == 0 || is_eval == 1);
+		func->is_eval = (duk_uint8_t) is_eval;
+		func->is_global = (duk_uint8_t) !is_eval;
 		DUK_ASSERT(func->is_namebinding == 0);
 		DUK_ASSERT(func->is_constructable == 0);
 
@@ -68532,6 +70312,7 @@
 	DUK_LEXER_INITCTX(&comp_stk.comp_ctx_alloc.lex);
 	comp_stk.comp_ctx_alloc.lex.input = src_buffer;
 	comp_stk.comp_ctx_alloc.lex.input_length = src_length;
+	comp_stk.comp_ctx_alloc.lex.flags = flags;  /* Forward flags directly for now. */
 
 	/* [ ... filename ] */
 
@@ -69363,7 +71144,7 @@
 	DUK_ASSERT(DUK_TVAL_IS_STRING(tv_id));
 	name = DUK_TVAL_GET_STRING(tv_id);
 	DUK_ASSERT(name != NULL);
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
 	(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [ ... val this ] */
 
 	/* XXX: Fastint fast path would be useful here.  Also fastints
@@ -69382,11 +71163,13 @@
 
 	if (op & 0x02) {
 		duk_push_number(ctx, y);  /* -> [ ... x this y ] */
+		act = thr->callstack_curr;
 		duk_js_putvar_activation(thr, act, name, DUK_GET_TVAL_NEGIDX(ctx, -1), is_strict);
 		duk_pop_2(ctx);  /* -> [ ... x ] */
 	} else {
 		duk_pop_2(ctx);  /* -> [ ... ] */
 		duk_push_number(ctx, y);  /* -> [ ... y ] */
+		act = thr->callstack_curr;
 		duk_js_putvar_activation(thr, act, name, DUK_GET_TVAL_NEGIDX(ctx, -1), is_strict);
 	}
 
@@ -69516,17 +71299,19 @@
 
 	duk__set_catcher_regs(thr, cat_idx, tv_val_unstable, lj_type);
 
-	duk_hthread_catchstack_unwind(thr, cat_idx + 1);
-	duk_hthread_callstack_unwind(thr, thr->catchstack[cat_idx].callstack_index + 1);
+	duk_hthread_catchstack_unwind_norz(thr, cat_idx + 1);
+	duk_hthread_callstack_unwind_norz(thr, thr->catchstack[cat_idx].callstack_index + 1);
 
 	DUK_ASSERT(thr->callstack_top >= 1);
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
-	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
+	DUK_ASSERT(thr->callstack_curr != NULL);
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
 
 	duk__reconfig_valstack_ecma_catcher(thr, thr->callstack_top - 1, cat_idx);
 
 	DUK_ASSERT(thr->callstack_top >= 1);
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
+	DUK_ASSERT(act != NULL);
 	act->curr_pc = thr->catchstack[cat_idx].pc_base + 0;  /* +0 = catch */
 	act = NULL;
 
@@ -69541,8 +71326,7 @@
 	 */
 
 	if (DUK_CAT_HAS_CATCH_BINDING_ENABLED(&thr->catchstack[cat_idx])) {
-		duk_hobject *new_env;
-		duk_hobject *act_lex_env;
+		duk_hdecenv *new_env;
 
 		DUK_DDD(DUK_DDDPRINT("catcher has an automatic catch binding"));
 
@@ -69550,7 +71334,8 @@
 		 * points, so we re-lookup it multiple times.
 		 */
 		DUK_ASSERT(thr->callstack_top >= 1);
-		act = thr->callstack + thr->callstack_top - 1;
+		act = thr->callstack_curr;
+		DUK_ASSERT(act != NULL);
 
 		if (act->lex_env == NULL) {
 			DUK_ASSERT(act->var_env == NULL);
@@ -69558,22 +71343,26 @@
 
 			/* this may have side effects, so re-lookup act */
 			duk_js_init_activation_environment_records_delayed(thr, act);
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
+			DUK_ASSERT(act != NULL);
 		}
 		DUK_ASSERT(act->lex_env != NULL);
 		DUK_ASSERT(act->var_env != NULL);
 		DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
 		DUK_UNREF(act);  /* unreferenced without assertions */
 
-		act = thr->callstack + thr->callstack_top - 1;
-		act_lex_env = act->lex_env;
-		act = NULL;  /* invalidated */
-
-		new_env = duk_push_object_helper_proto(ctx,
-		                                       DUK_HOBJECT_FLAG_EXTENSIBLE |
-		                                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
-		                                       act_lex_env);
+		/* XXX: If an out-of-memory happens here, longjmp state asserts
+		 * will be triggered at present and a try-catch fails to catch.
+		 * That's not sandboxing fatal (C API protected calls are what
+		 * matters), and script catch code can immediately throw anyway
+		 * for almost any operation.
+		 */
+		new_env = duk_hdecenv_alloc(thr,
+		                            DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV));
 		DUK_ASSERT(new_env != NULL);
+		duk_push_hobject(ctx, (duk_hobject *) new_env);
+		DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL);
 		DUK_DDD(DUK_DDDPRINT("new_env allocated: %!iO", (duk_heaphdr *) new_env));
 
 		/* Note: currently the catch binding is handled without a register
@@ -69582,14 +71371,20 @@
 		 * record regbases etc.
 		 */
 
+		/* XXX: duk_xdef_prop() may cause an out-of-memory, see above. */
 		DUK_ASSERT(thr->catchstack[cat_idx].h_varname != NULL);
 		duk_push_hstring(ctx, thr->catchstack[cat_idx].h_varname);
 		duk_push_tval(ctx, thr->valstack + thr->catchstack[cat_idx].idx_base);
 		duk_xdef_prop(ctx, -3, DUK_PROPDESC_FLAGS_W);  /* writable, not configurable */
 
-		act = thr->callstack + thr->callstack_top - 1;
-		act->lex_env = new_env;
-		DUK_HOBJECT_INCREF(thr, new_env);  /* reachable through activation */
+		act = thr->callstack_curr;
+		DUK_ASSERT(act != NULL);
+		DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, act->lex_env);
+		act->lex_env = (duk_hobject *) new_env;
+		DUK_HOBJECT_INCREF(thr, (duk_hobject *) new_env);  /* reachable through activation */
+		/* Net refcount change to act->lex_env is 0: incref for new_env's
+		 * prototype, decref for act->lex_env overwrite.
+		 */
 
 		DUK_CAT_SET_LEXENV_ACTIVE(&thr->catchstack[cat_idx]);
 
@@ -69609,17 +71404,19 @@
 
 	duk__set_catcher_regs(thr, cat_idx, tv_val_unstable, lj_type);
 
-	duk_hthread_catchstack_unwind(thr, cat_idx + 1);  /* cat_idx catcher is kept, even for finally */
-	duk_hthread_callstack_unwind(thr, thr->catchstack[cat_idx].callstack_index + 1);
+	duk_hthread_catchstack_unwind_norz(thr, cat_idx + 1);  /* cat_idx catcher is kept, even for finally */
+	duk_hthread_callstack_unwind_norz(thr, thr->catchstack[cat_idx].callstack_index + 1);
 
 	DUK_ASSERT(thr->callstack_top >= 1);
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
-	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
+	DUK_ASSERT(thr->callstack_curr != NULL);
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
 
 	duk__reconfig_valstack_ecma_catcher(thr, thr->callstack_top - 1, cat_idx);
 
 	DUK_ASSERT(thr->callstack_top >= 1);
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
+	DUK_ASSERT(act != NULL);
 	act->curr_pc = thr->catchstack[cat_idx].pc_base + 1;  /* +1 = finally */
 	act = NULL;
 
@@ -69632,7 +71429,8 @@
 	DUK_ASSERT(thr != NULL);
 
 	DUK_ASSERT(thr->callstack_top >= 1);
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
+	DUK_ASSERT(act != NULL);
 
 	DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
 	DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(DUK_ACT_GET_FUNC(act)));
@@ -69641,13 +71439,14 @@
 	act->curr_pc = thr->catchstack[cat_idx].pc_base + (lj_type == DUK_LJ_TYPE_CONTINUE ? 1 : 0);
 	act = NULL;  /* invalidated */
 
-	duk_hthread_catchstack_unwind(thr, cat_idx + 1);  /* keep label catcher */
+	duk_hthread_catchstack_unwind_norz(thr, cat_idx + 1);  /* keep label catcher */
 	/* no need to unwind callstack */
 
 	/* valstack should not need changes */
 #if defined(DUK_USE_ASSERTIONS)
 	DUK_ASSERT(thr->callstack_top >= 1);
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
+	DUK_ASSERT(act != NULL);
 	DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) ==
 	           (duk_size_t) ((duk_hcompfunc *) DUK_ACT_GET_FUNC(act))->nregs);
 #endif
@@ -69669,7 +71468,7 @@
 	tv1 = resumer->valstack + resumer->callstack[act_idx].idx_retval;  /* return value from Duktape.Thread.resume() */
 	DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv_val_unstable);  /* side effects */
 
-	duk_hthread_callstack_unwind(resumer, act_idx + 1);  /* unwind to 'resume' caller */
+	duk_hthread_callstack_unwind_norz(resumer, act_idx + 1);  /* unwind to 'resume' caller */
 
 	/* no need to unwind catchstack */
 	duk__reconfig_valstack_ecma_return(resumer, act_idx);
@@ -69732,10 +71531,11 @@
 
 		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(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL &&
-		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)) &&
-		           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))->func == duk_bi_thread_resume);
-		DUK_ASSERT_DISABLE((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0);                              /* unsigned */
+		DUK_ASSERT(thr->callstack_curr != NULL);
+		DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL &&
+		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)) &&
+		           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(thr->callstack_curr))->func == duk_bi_thread_resume);
+		DUK_ASSERT_DISABLE((thr->callstack_curr - 1)->idx_retval >= 0);                                              /* unsigned */
 
 		tv = &thr->heap->lj.value2;  /* resumee */
 		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
@@ -69750,11 +71550,11 @@
 		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 ||
-		           (DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 1) != NULL &&
-		            DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 1)) &&
-		            ((duk_hnatfunc *) DUK_ACT_GET_FUNC(resumee->callstack + resumee->callstack_top - 1))->func == duk_bi_thread_yield));
+		           (DUK_ACT_GET_FUNC(resumee->callstack_curr) != NULL &&
+		            DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(resumee->callstack_curr)) &&
+		            ((duk_hnatfunc *) DUK_ACT_GET_FUNC(resumee->callstack_curr))->func == duk_bi_thread_yield));
 		DUK_ASSERT_DISABLE(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
-		           (resumee->callstack + resumee->callstack_top - 2)->idx_retval >= 0);                              /* idx_retval unsigned */
+		           (resumee->callstack_curr - 1)->idx_retval >= 0);                                                  /* idx_retval unsigned */
 		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_INACTIVE ||
 		           resumee->callstack_top == 0);                                                                     /* INACTIVE: no activation, single function value on valstack */
 
@@ -69769,7 +71569,9 @@
 			 *  which we simply ignore.
 			 */
 
+			DUK_ASSERT(resumee->resumer == NULL);
 			resumee->resumer = thr;
+			DUK_HTHREAD_INCREF(thr, thr);
 			resumee->state = DUK_HTHREAD_STATE_RUNNING;
 			thr->state = DUK_HTHREAD_STATE_RESUMED;
 			DUK_HEAP_SWITCH_THREAD(thr->heap, resumee);
@@ -69793,13 +71595,15 @@
 			tv2 = &thr->heap->lj.value1;
 			DUK_TVAL_SET_TVAL_UPDREF(thr, tv, tv2);  /* side effects */
 
-			duk_hthread_callstack_unwind(resumee, act_idx + 1);  /* unwind to 'yield' caller */
+			duk_hthread_callstack_unwind_norz(resumee, act_idx + 1);  /* unwind to 'yield' caller */
 
 			/* no need to unwind catchstack */
 
 			duk__reconfig_valstack_ecma_return(resumee, act_idx);
 
+			DUK_ASSERT(resumee->resumer == NULL);
 			resumee->resumer = thr;
+			DUK_HTHREAD_INCREF(thr, thr);
 			resumee->state = DUK_HTHREAD_STATE_RUNNING;
 			thr->state = DUK_HTHREAD_STATE_RESUMED;
 			DUK_HEAP_SWITCH_THREAD(thr->heap, resumee);
@@ -69835,7 +71639,9 @@
 				DUK_ERROR_INTERNAL(thr);
 			}
 
+			DUK_ASSERT(resumee->resumer == NULL);
 			resumee->resumer = thr;
+			DUK_HTHREAD_INCREF(thr, thr);
 			resumee->state = DUK_HTHREAD_STATE_RUNNING;
 			thr->state = DUK_HTHREAD_STATE_RESUMED;
 			DUK_HEAP_SWITCH_THREAD(thr->heap, resumee);
@@ -69868,28 +71674,31 @@
 		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(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL &&
-		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)) &&
-		           ((duk_hnatfunc *) 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_COMPFUNC(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 */
+		DUK_ASSERT(thr->callstack_curr != NULL);
+		DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL &&
+		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)) &&
+		           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(thr->callstack_curr))->func == duk_bi_thread_yield);
+		DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr - 1) != NULL &&
+		           DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr - 1)));                              /* an Ecmascript function */
+		DUK_ASSERT_DISABLE((thr->callstack_curr - 1)->idx_retval >= 0);                                              /* unsigned */
 
 		resumer = thr->resumer;
 
 		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(DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 1) != NULL &&
-		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(resumer->callstack + resumer->callstack_top - 1)) &&
-		           ((duk_hnatfunc *) 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_COMPFUNC(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 */
+		DUK_ASSERT(resumer->callstack_curr != NULL);
+		DUK_ASSERT(DUK_ACT_GET_FUNC(resumer->callstack_curr) != NULL &&
+		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(resumer->callstack_curr)) &&
+		           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(resumer->callstack_curr))->func == duk_bi_thread_resume);
+		DUK_ASSERT(DUK_ACT_GET_FUNC(resumer->callstack_curr - 1) != NULL &&
+		           DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(resumer->callstack_curr - 1)));                            /* an Ecmascript function */
+		DUK_ASSERT_DISABLE((resumer->callstack_curr - 1)->idx_retval >= 0);                                            /* unsigned */
 
 		if (thr->heap->lj.iserror) {
 			thr->state = DUK_HTHREAD_STATE_YIELDED;
 			thr->resumer = NULL;
+			DUK_HTHREAD_DECREF_NORZ(thr, resumer);
 			resumer->state = DUK_HTHREAD_STATE_RUNNING;
 			DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
 			thr = resumer;
@@ -69905,6 +71714,7 @@
 
 			thr->state = DUK_HTHREAD_STATE_YIELDED;
 			thr->resumer = NULL;
+			DUK_HTHREAD_DECREF_NORZ(thr, resumer);
 			resumer->state = DUK_HTHREAD_STATE_RUNNING;
 			DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
 #if 0
@@ -69985,9 +71795,9 @@
 			 * final catcher unwind everything
 			 */
 #if 0
-			duk_hthread_catchstack_unwind(thr, (cat - thr->catchstack) + 1);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
-			duk_hthread_callstack_unwind(thr, entry_callstack_index + 1);
-
+			duk_hthread_catchstack_unwind_norz(thr, (cat - thr->catchstack) + 1);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
+			duk_hthread_callstack_unwind_norz(thr, entry_callstack_index + 1);
+			DUK_REFZERO_CHECK_SLOW(thr);
 #endif
 			DUK_D(DUK_DPRINT("-> throw propagated up to entry level, rethrow and exit bytecode executor"));
 			retval = DUK__LONGJMP_RETHROW;
@@ -70004,11 +71814,12 @@
 
 		DUK_ASSERT(thr->resumer != NULL);
 		DUK_ASSERT(thr->resumer->callstack_top >= 2);  /* Ecmascript activation + Duktape.Thread.resume() activation */
-		DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1) != NULL &&
-		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1)) &&
-		           ((duk_hnatfunc *) 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_COMPFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 2)));  /* an Ecmascript function */
+		DUK_ASSERT(thr->resumer->callstack_curr != NULL);
+		DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr) != NULL &&
+		           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr)) &&
+		           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(thr->resumer->callstack_curr))->func == duk_bi_thread_resume);  /* Duktape.Thread.resume() */
+		DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr - 1) != NULL &&
+		           DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr - 1)));  /* an Ecmascript function */
 
 		resumer = thr->resumer;
 
@@ -70021,6 +71832,7 @@
 		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);
 
 		thr->resumer = NULL;
+		DUK_HTHREAD_DECREF_NORZ(thr, resumer);
 		resumer->state = DUK_HTHREAD_STATE_RUNNING;
 		DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
 		thr = resumer;
@@ -70049,6 +71861,8 @@
 	DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value1);  /* side effects */
 	DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value2);  /* side effects */
 
+	DUK_GC_TORTURE(thr->heap);
+
  just_return:
 	return retval;
 
@@ -70189,6 +72003,7 @@
 
 	cat = thr->catchstack + thr->catchstack_top - 1;  /* may be < thr->catchstack initially */
 	DUK_ASSERT(thr->callstack_top > 0);  /* ensures callstack_top - 1 >= 0 */
+	DUK_ASSERT(thr->callstack_curr != NULL);
 	orig_callstack_index = thr->callstack_top - 1;
 
 	while (cat >= thr->catchstack) {
@@ -70236,22 +72051,22 @@
 		 */
 
 		DUK_DDD(DUK_DDDPRINT("return to Ecmascript caller, idx_retval=%ld, lj_value1=%!T",
-		                     (long) (thr->callstack + thr->callstack_top - 2)->idx_retval,
+		                     (long) (thr->callstack_curr - 1)->idx_retval,
 		                     (duk_tval *) &thr->heap->lj.value1));
 
-		DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(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_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr - 1)));   /* must be ecmascript */
+
+		tv1 = thr->valstack + (thr->callstack_curr - 1)->idx_retval;
 		DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
 		tv2 = thr->valstack_top - 1;
 		DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2);  /* side effects */
 
 		DUK_DDD(DUK_DDDPRINT("return value at idx_retval=%ld is %!T",
-		                     (long) (thr->callstack + thr->callstack_top - 2)->idx_retval,
-		                     (duk_tval *) (thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval)));
-
-		duk_hthread_catchstack_unwind(thr, new_cat_top);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
-		duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
+		                     (long) (thr->callstack_curr - 1)->idx_retval,
+		                     (duk_tval *) (thr->valstack + (thr->callstack_curr - 1)->idx_retval)));
+
+		duk_hthread_catchstack_unwind_norz(thr, new_cat_top);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
+		duk_hthread_callstack_unwind_norz(thr, thr->callstack_top - 1);
 		duk__reconfig_valstack_ecma_return(thr, thr->callstack_top - 1);
 
 		DUK_DD(DUK_DDPRINT("-> return not intercepted, restart execution in caller"));
@@ -70263,12 +72078,13 @@
 
 	DUK_ASSERT(thr->resumer != NULL);
 	DUK_ASSERT(thr->resumer->callstack_top >= 2);  /* Ecmascript activation + Duktape.Thread.resume() activation */
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1) != NULL &&
-	           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1)) &&
-	           ((duk_hnatfunc *) 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_COMPFUNC(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->resumer->callstack_curr != NULL);
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr) != NULL &&
+	           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr)) &&
+	           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(thr->resumer->callstack_curr))->func == duk_bi_thread_resume);  /* Duktape.Thread.resume() */
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr - 1) != NULL &&
+	           DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr - 1)));  /* an Ecmascript function */
+	DUK_ASSERT_DISABLE((thr->resumer->callstack_curr - 1)->idx_retval >= 0);                  /* unsigned */
 	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
 	DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);
 
@@ -70282,6 +72098,7 @@
 	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);
 
 	thr->resumer = NULL;
+	DUK_HTHREAD_DECREF(thr, resumer);
 	resumer->state = DUK_HTHREAD_STATE_RUNNING;
 	DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
 #if 0
@@ -70340,7 +72157,8 @@
 	DUK_ASSERT(thr->heap->dbg_processing == 0);  /* don't re-enter e.g. during Eval */
 
 	ctx = (duk_context *) thr;
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
+	DUK_ASSERT(act != NULL);
 
 	/* It might seem that replacing 'thr->heap' with just 'heap' below
 	 * might be a good idea, but it increases code size slightly
@@ -70365,8 +72183,7 @@
 			    (line != thr->heap->dbg_step_startline)) {
 				DUK_D(DUK_DPRINT("STEP STATE TRIGGERED PAUSE at line %ld",
 				                 (long) line));
-
-				DUK_HEAP_SET_PAUSED(thr->heap);
+				duk_debug_set_paused(thr->heap);
 			}
 
 			/* Check for breakpoints only on line transition.
@@ -70392,8 +72209,7 @@
 				if (act->prev_line != bp->line && line == bp->line) {
 					DUK_D(DUK_DPRINT("BREAKPOINT TRIGGERED at %!O:%ld",
 					                 (duk_heaphdr *) bp->filename, (long) bp->line));
-
-					DUK_HEAP_SET_PAUSED(thr->heap);
+					duk_debug_set_paused(thr->heap);
 				}
 			}
 		} else {
@@ -70480,8 +72296,9 @@
 	 * above, so we must recheck attach status.
 	 */
 
-	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
-		act = thr->callstack + thr->callstack_top - 1;  /* relookup, may have changed */
+	if (duk_debug_is_attached(thr->heap)) {
+		act = thr->callstack_curr;  /* relookup, may have changed */
+		DUK_ASSERT(act != NULL);
 		if (act->flags & DUK_ACT_FLAG_BREAKPOINT_ACTIVE ||
 		    ((thr->heap->dbg_step_type == DUK_STEP_TYPE_INTO ||
 		      thr->heap->dbg_step_type == DUK_STEP_TYPE_OVER) &&
@@ -70504,7 +72321,7 @@
 }
 #endif  /* DUK_USE_DEBUGGER_SUPPORT */
 
-DUK_LOCAL duk_small_uint_t duk__executor_interrupt(duk_hthread *thr) {
+DUK_LOCAL DUK_NOINLINE DUK_COLD duk_small_uint_t duk__executor_interrupt(duk_hthread *thr) {
 	duk_int_t ctr;
 	duk_activation *act;
 	duk_hcompfunc *fun;
@@ -70554,7 +72371,8 @@
 	}
 	DUK_HEAP_SET_INTERRUPT_RUNNING(thr->heap);
 
-	act = thr->callstack + thr->callstack_top - 1;
+	act = thr->callstack_curr;
+	DUK_ASSERT(act != NULL);
 
 	fun = (duk_hcompfunc *) DUK_ACT_GET_FUNC(act);
 	DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC((duk_hobject *) fun));
@@ -70590,7 +72408,7 @@
 		 * detaching (to finish off the pending detach).
 		 */
 		duk__interrupt_handle_debugger(thr, &immediate, &retval);
-		act = thr->callstack + thr->callstack_top - 1;  /* relookup if changed */
+		act = thr->callstack_curr;  /* relookup if changed */
 		DUK_UNREF(act);  /* 'act' is no longer accessed, scanbuild fix */
 	}
 #endif  /* DUK_USE_DEBUGGER_SUPPORT */
@@ -70732,7 +72550,7 @@
 	    (thr->heap->dbg_step_thread != thr ||
 	     thr->heap->dbg_step_csindex != thr->callstack_top - 1)) {
 		DUK_D(DUK_DPRINT("STEP INTO ACTIVE, FORCE PAUSED"));
-		DUK_HEAP_SET_PAUSED(thr->heap);
+		duk_debug_set_paused(thr->heap);
 	}
 
 	/* Force interrupt right away if we're paused or in "checked mode".
@@ -70787,7 +72605,7 @@
 #if defined(DUK_USE_EXEC_FUN_LOCAL)
 #define DUK__FUN()          fun
 #else
-#define DUK__FUN()          ((duk_hcompfunc *) DUK_ACT_GET_FUNC((thr)->callstack + (thr)->callstack_top - 1))
+#define DUK__FUN()          ((duk_hcompfunc *) DUK_ACT_GET_FUNC((thr)->callstack_curr))
 #endif
 #define DUK__STRICT()       (DUK_HOBJECT_HAS_STRICT((duk_hobject *) DUK__FUN()))
 
@@ -70864,12 +72682,12 @@
 
 #define DUK__SYNC_CURR_PC()  do { \
 		duk_activation *act; \
-		act = thr->callstack + thr->callstack_top - 1; \
+		act = thr->callstack_curr; \
 		act->curr_pc = curr_pc; \
 	} while (0)
 #define DUK__SYNC_AND_NULL_CURR_PC()  do { \
 		duk_activation *act; \
-		act = thr->callstack + thr->callstack_top - 1; \
+		act = thr->callstack_curr; \
 		act->curr_pc = curr_pc; \
 		thr->ptr_curr_pc = NULL; \
 	} while (0)
@@ -70921,15 +72739,27 @@
 
 	lj_ret = duk__handle_longjmp(heap->curr_thread, entry_thread, entry_callstack_top);
 
+	/* Error handling complete, remove side effect protections.
+	 */
+#if defined(DUK_USE_ASSERTIONS)
+	DUK_ASSERT(heap->error_not_allowed == 1);
+	heap->error_not_allowed = 0;
+#endif
+	DUK_ASSERT(heap->pf_prevent_count > 0);
+	heap->pf_prevent_count--;
+	DUK_DD(DUK_DDPRINT("executor error handled, pf_prevent_count updated to %ld", (long) heap->pf_prevent_count));
+
 	if (lj_ret == DUK__LONGJMP_RESTART) {
 		/* Restart bytecode execution, possibly with a changed thread. */
-		;
-	} else {
-		/* Rethrow error to calling state. */
-		DUK_ASSERT(lj_ret == DUK__LONGJMP_RETHROW);
-
-		/* Longjmp handling has restored jmpbuf_ptr. */
-		DUK_ASSERT(heap->lj.jmpbuf_ptr == entry_jmpbuf_ptr);
+		DUK_REFZERO_CHECK_SLOW(heap->curr_thread);
+	} else {
+		/* If an error is propagated, don't run refzero checks here.
+		 * The next catcher will deal with that.  Pf_prevent_count
+		 * will be re-bumped by the longjmp.
+		 */
+
+		DUK_ASSERT(lj_ret == DUK__LONGJMP_RETHROW);  /* Rethrow error to calling state. */
+		DUK_ASSERT(heap->lj.jmpbuf_ptr == entry_jmpbuf_ptr);  /* Longjmp handling has restored jmpbuf_ptr. */
 
 		/* Thread may have changed, e.g. YIELD converted to THROW. */
 		duk_err_longjmp(heap->curr_thread);
@@ -70952,8 +72782,10 @@
 	DUK_ASSERT(exec_thr->heap->curr_thread != NULL);
 	DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR((duk_heaphdr *) exec_thr);
 	DUK_ASSERT(exec_thr->callstack_top >= 1);  /* at least one activation, ours */
-	DUK_ASSERT(DUK_ACT_GET_FUNC(exec_thr->callstack + exec_thr->callstack_top - 1) != NULL);
-	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(exec_thr->callstack + exec_thr->callstack_top - 1)));
+	DUK_ASSERT(DUK_ACT_GET_FUNC(exec_thr->callstack_curr) != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(exec_thr->callstack_curr)));
+
+	DUK_GC_TORTURE(exec_thr->heap);
 
 	entry_thread = exec_thr;
 	heap = entry_thread->heap;
@@ -71044,7 +72876,7 @@
 }
 
 /* Inner executor, performance critical. */
-DUK_LOCAL DUK_NOINLINE void duk__js_execute_bytecode_inner(duk_hthread *entry_thread, duk_size_t entry_callstack_top) {
+DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *entry_thread, duk_size_t entry_callstack_top) {
 	/* Current PC, accessed by other functions through thr->ptr_to_curr_pc.
 	 * Critical for performance.  It would be safest to make this volatile,
 	 * but that eliminates performance benefits; aliasing guarantees
@@ -71085,6 +72917,8 @@
 #endif
 #endif
 
+	DUK_GC_TORTURE(entry_thread->heap);
+
 	/*
 	 *  Restart execution by reloading thread state.
 	 *
@@ -71134,8 +72968,11 @@
 	thr = entry_thread->heap->curr_thread;
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(thr->callstack_top >= 1);
-	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
-	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
+	DUK_ASSERT(thr->callstack_curr != NULL);
+	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
+
+	DUK_GC_TORTURE(thr->heap);
 
 	thr->ptr_curr_pc = &curr_pc;
 
@@ -71149,7 +72986,8 @@
 		/* Assume interrupt init/counter are properly initialized here. */
 		/* Assume that thr->valstack_bottom has been set-up before getting here. */
 
-		act = thr->callstack + thr->callstack_top - 1;
+		act = thr->callstack_curr;
+		DUK_ASSERT(act != NULL);
 		fun = (duk_hcompfunc *) DUK_ACT_GET_FUNC(act);
 		DUK_ASSERT(fun != NULL);
 		DUK_ASSERT(thr->valstack_top - thr->valstack_bottom == fun->nregs);
@@ -71157,9 +72995,10 @@
 		DUK_ASSERT(consts != NULL);
 
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
-		if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap) && !thr->heap->dbg_processing) {
+		if (duk_debug_is_attached(thr->heap) && !thr->heap->dbg_processing) {
 			duk__executor_recheck_debugger(thr, act, fun);
-			act = thr->callstack + thr->callstack_top - 1;  /* relookup after side effects (no side effects currently however) */
+			act = thr->callstack_curr;  /* relookup after side effects (no side effects currently however) */
+			DUK_ASSERT(act != NULL);
 		}
 #endif  /* DUK_USE_DEBUGGER_SUPPORT */
 
@@ -71209,10 +73048,11 @@
 			duk_small_uint_t exec_int_ret;
 
 			/* Write curr_pc back for the debugger. */
-			DUK_ASSERT(thr->callstack_top > 0);
 			{
 				duk_activation *act;
-				act = thr->callstack + thr->callstack_top - 1;
+				DUK_ASSERT(thr->callstack_top > 0);
+				act = thr->callstack_curr;
+				DUK_ASSERT(act != NULL);
 				act->curr_pc = (duk_instr_t *) curr_pc;
 			}
 
@@ -71246,7 +73086,7 @@
 #if defined(DUK_USE_ASSERTIONS) || defined(DUK_USE_DEBUG)
 		{
 			duk_activation *act;
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			DUK_ASSERT(curr_pc >= DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, DUK__FUN()));
 			DUK_ASSERT(curr_pc < DUK_HCOMPFUNC_GET_CODE_END(thr->heap, DUK__FUN()));
 			DUK_UNREF(act);  /* if debugging disabled */
@@ -71538,7 +73378,7 @@
 			DUK_ASSERT(DUK_TVAL_IS_STRING(tv));
 			name = DUK_TVAL_GET_STRING(tv);
 			tv = NULL;  /* lookup has side effects */
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			if (duk_js_getvar_activation(thr, act, name, 0 /*throw*/)) {
 				/* -> [... val this] */
 				tv = DUK_GET_TVAL_NEGIDX(ctx, -2);
@@ -72266,14 +74106,12 @@
 			duk_hstring *name;
 			duk_small_uint_t prop_flags;
 			duk_bool_t is_func_decl;
-			duk_bool_t is_undef_value;
 
 			tv1 = DUK__REGCONSTP_B(ins);
 			DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
 			name = DUK_TVAL_GET_STRING(tv1);
 			DUK_ASSERT(name != NULL);
 
-			is_undef_value = ((a & DUK_BC_DECLVAR_FLAG_UNDEF_VALUE) != 0);
 			is_func_decl = ((a & DUK_BC_DECLVAR_FLAG_FUNC_DECL) != 0);
 
 			/* XXX: declvar takes an duk_tval pointer, which is awkward and
@@ -72285,19 +74123,25 @@
 			 */
 			prop_flags = a & DUK_PROPDESC_FLAGS_MASK;
 
-			if (is_undef_value) {
+			if (is_func_decl) {
+				duk_push_tval(ctx, DUK__REGCONSTP_C(ins));
+			} else {
 				DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top));  /* valstack policy */
 				thr->valstack_top++;
-			} else {
-				duk_push_tval(ctx, DUK__REGCONSTP_C(ins));
 			}
 			tv1 = DUK_GET_TVAL_NEGIDX(ctx, -1);
 
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			if (duk_js_declvar_activation(thr, act, name, tv1, prop_flags, is_func_decl)) {
-				/* already declared, must update binding value */
-				tv1 = DUK_GET_TVAL_NEGIDX(ctx, -1);
-				duk_js_putvar_activation(thr, act, name, tv1, DUK__STRICT());
+				if (is_func_decl) {
+					/* Already declared, update value. */
+					tv1 = DUK_GET_TVAL_NEGIDX(ctx, -1);
+					duk_js_putvar_activation(thr, act, name, tv1, DUK__STRICT());
+				} else {
+					/* Already declared but no initializer value
+					 * (e.g. 'var xyz;'), no-op.
+					 */
+				}
 			}
 
 			duk_pop(ctx);
@@ -72351,7 +74195,7 @@
 			DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
 			name = DUK_TVAL_GET_STRING(tv1);
 			DUK_ASSERT(name != NULL);
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [... val this] */
 
 			idx = (duk_uint_fast_t) DUK_DEC_A(ins);
@@ -72378,7 +74222,7 @@
 			DUK_ASSERT_DISABLE(bc >= 0); /* unsigned */
 			DUK_ASSERT((duk_uint_t) bc < (duk_uint_t) DUK_HCOMPFUNC_GET_FUNCS_COUNT(thr->heap, DUK__FUN()));
 
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			fun_act = (duk_hcompfunc *) DUK_ACT_GET_FUNC(act);
 			fun_temp = DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, fun_act)[bc];
 			DUK_ASSERT(fun_temp != NULL);
@@ -72390,6 +74234,7 @@
 			if (act->lex_env == NULL) {
 				DUK_ASSERT(act->var_env == NULL);
 				duk_js_init_activation_environment_records_delayed(thr, act);
+				act = thr->callstack_curr;
 			}
 			DUK_ASSERT(act->lex_env != NULL);
 			DUK_ASSERT(act->var_env != NULL);
@@ -72416,7 +74261,7 @@
 			DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
 			name = DUK_TVAL_GET_STRING(tv1);
 			DUK_ASSERT(name != NULL);
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [... val this] */
 			duk_pop(ctx);  /* 'this' binding is not needed here */
 			DUK__REPLACE_TOP_A_BREAK();
@@ -72437,7 +74282,7 @@
 			 */
 
 			tv1 = DUK__REGP_A(ins);  /* val */
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			duk_js_putvar_activation(thr, act, name, tv1, DUK__STRICT());
 			break;
 		}
@@ -72452,7 +74297,7 @@
 			DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
 			name = DUK_TVAL_GET_STRING(tv1);
 			DUK_ASSERT(name != NULL);
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
 			rc = duk_js_delvar_activation(thr, act, name);
 			DUK__REPLACE_BOOL_A_BREAK(rc);
 		}
@@ -72513,6 +74358,7 @@
 			thr->valstack_top++;
 			DUK__RETURN_SHARED();
 		}
+		/* This will be unused without refcounting. */
 		case DUK_OP_RETCONST: {
 			duk_tval *tv;
 
@@ -72529,7 +74375,10 @@
 			DUK__SYNC_AND_NULL_CURR_PC();
 			tv = DUK__CONSTP_BC(ins);
 			DUK_TVAL_SET_TVAL(thr->valstack_top, tv);
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			/* Without refcounting only RETCONSTN is used. */
 			DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv));  /* no INCREF for this constant */
+#endif
 			thr->valstack_top++;
 			DUK__RETURN_SHARED();
 		}
@@ -72653,99 +74502,6 @@
 			a = DUK_DEC_A(ins);
 			bc = DUK_DEC_BC(ins);
 
-			act = thr->callstack + thr->callstack_top - 1;
-			DUK_ASSERT(thr->callstack_top >= 1);
-
-			/* 'with' target must be created first, in case we run out of memory */
-			/* XXX: refactor out? */
-
-			if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
-				DUK_DDD(DUK_DDDPRINT("need to initialize a with binding object"));
-
-				if (act->lex_env == NULL) {
-					DUK_ASSERT(act->var_env == NULL);
-					DUK_DDD(DUK_DDDPRINT("delayed environment initialization"));
-
-					/* must relookup act in case of side effects */
-					duk_js_init_activation_environment_records_delayed(thr, act);
-					act = thr->callstack + thr->callstack_top - 1;
-					DUK_UNREF(act);  /* 'act' is no longer accessed, scanbuild fix */
-				}
-				DUK_ASSERT(act->lex_env != NULL);
-				DUK_ASSERT(act->var_env != NULL);
-
-				(void) duk_push_object_helper(ctx,
-				                              DUK_HOBJECT_FLAG_EXTENSIBLE |
-				                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV),
-				                              -1);  /* no prototype, updated below */
-
-				duk_push_tval(ctx, DUK__REGP(bc));
-				duk_to_object(ctx, -1);
-				duk_dup_top(ctx);
-
-				/* [ ... env target ] */
-				/* [ ... env target target ] */
-
-				duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
-				duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);  /* always provideThis=true */
-
-				/* [ ... env ] */
-
-				DUK_DDD(DUK_DDDPRINT("environment for with binding: %!iT",
-				                     (duk_tval *) duk_get_tval(ctx, -1)));
-			}
-
-			/* allocate catcher and populate it (should be atomic) */
-
-			duk_hthread_catchstack_grow(thr);
-			cat = thr->catchstack + thr->catchstack_top;
-			DUK_ASSERT(thr->catchstack_top + 1 <= thr->catchstack_size);
-			thr->catchstack_top++;
-
-			cat->flags = DUK_CAT_TYPE_TCF;
-			cat->h_varname = NULL;
-
-			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) {
-				cat->flags |= DUK_CAT_FLAG_CATCH_ENABLED;
-			}
-			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) {
-				cat->flags |= DUK_CAT_FLAG_FINALLY_ENABLED;
-			}
-			if (a & DUK_BC_TRYCATCH_FLAG_CATCH_BINDING) {
-				DUK_DDD(DUK_DDDPRINT("catch binding flag set to catcher"));
-				cat->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED;
-				tv1 = DUK__REGP(bc);
-				DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
-
-				/* borrowed reference; although 'tv1' comes from a register,
-				 * its value was loaded using LDCONST so the constant will
-				 * also exist and be reachable.
-				 */
-				cat->h_varname = DUK_TVAL_GET_STRING(tv1);
-			} else if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
-				/* env created above to stack top */
-				duk_hobject *new_env;
-
-				DUK_DDD(DUK_DDDPRINT("lexenv active flag set to catcher"));
-				cat->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE;
-
-				DUK_DDD(DUK_DDDPRINT("activating object env: %!iT",
-				                     (duk_tval *) duk_get_tval(ctx, -1)));
-				DUK_ASSERT(act->lex_env != NULL);
-				new_env = DUK_GET_HOBJECT_NEGIDX(ctx, -1);
-				DUK_ASSERT(new_env != NULL);
-
-				act = thr->callstack + thr->callstack_top - 1;  /* relookup (side effects) */
-				DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, new_env, act->lex_env);  /* side effects */
-
-				act = thr->callstack + thr->callstack_top - 1;  /* relookup (side effects) */
-				act->lex_env = new_env;
-				DUK_HOBJECT_INCREF(thr, new_env);
-				duk_pop(ctx);
-			} else {
-				;
-			}
-
 			/* Registers 'bc' and 'bc + 1' are written in longjmp handling
 			 * and if their previous values (which are temporaries) become
 			 * unreachable -and- have a finalizer, there'll be a function
@@ -72758,19 +74514,114 @@
 			 * error handling, so there's no side effect problem even if the
 			 * error value has a finalizer.
 			 */
+			duk_dup(ctx, bc);  /* Stabilize value. */
 			duk_to_undefined(ctx, bc);
 			duk_to_undefined(ctx, bc + 1);
 
-			cat = thr->catchstack + thr->catchstack_top - 1;  /* relookup (side effects) */
+			/* Ensure a catchstack entry is available.  One entry
+			 * is guaranteed even if side effects cause function
+			 * calls and the catchstack is shrunk because some
+			 * spare room is left behind by a shrink operation.
+			 */
+			duk_hthread_catchstack_grow(thr);
+
+			/* Allocate catcher and populate it.  Doesn't have to
+			 * be fully atomic, but the catcher must be in a
+			 * consistent state if side effects (such as finalizer
+			 * calls) occur.
+			 */
+
+			DUK_ASSERT(thr->catchstack_top + 1 <= thr->catchstack_size);
+			cat = thr->catchstack + thr->catchstack_top;
+			thr->catchstack_top++;
+
+			cat->flags = DUK_CAT_TYPE_TCF;
+			cat->h_varname = NULL;
 			cat->callstack_index = thr->callstack_top - 1;
 			cat->pc_base = (duk_instr_t *) curr_pc;  /* pre-incremented, points to first jump slot */
 			cat->idx_base = (duk_size_t) (thr->valstack_bottom - thr->valstack) + bc;
 
+			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) {
+				cat->flags |= DUK_CAT_FLAG_CATCH_ENABLED;
+			}
+			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) {
+				cat->flags |= DUK_CAT_FLAG_FINALLY_ENABLED;
+			}
+			if (a & DUK_BC_TRYCATCH_FLAG_CATCH_BINDING) {
+				DUK_DDD(DUK_DDDPRINT("catch binding flag set to catcher"));
+				cat->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED;
+				tv1 = DUK_GET_TVAL_NEGIDX(thr, -1);
+				DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
+
+				/* borrowed reference; although 'tv1' comes from a register,
+				 * its value was loaded using LDCONST so the constant will
+				 * also exist and be reachable.
+				 */
+				cat->h_varname = DUK_TVAL_GET_STRING(tv1);
+			} else if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
+				duk_hobjenv *env;
+				duk_hobject *target;
+
+				/* Delayed env initialization for activation (if needed). */
+				DUK_ASSERT(thr->callstack_top >= 1);
+				act = thr->callstack_curr;
+				DUK_ASSERT(act != NULL);
+				if (act->lex_env == NULL) {
+					DUK_DDD(DUK_DDDPRINT("delayed environment initialization"));
+					DUK_ASSERT(act->var_env == NULL);
+
+					duk_js_init_activation_environment_records_delayed(thr, act);
+					act = thr->callstack_curr;  /* relookup, side effects */
+					DUK_UNREF(act);  /* 'act' is no longer accessed, scanbuild fix */
+				}
+				DUK_ASSERT(act->lex_env != NULL);
+				DUK_ASSERT(act->var_env != NULL);
+
+				/* Coerce 'with' target. */
+				target = duk_to_hobject(ctx, -1);
+				DUK_ASSERT(target != NULL);
+
+				/* Create an object environment; it is not pushed
+				 * so avoid side effects very carefully until it is
+				 * referenced.
+				 */
+				env = duk_hobjenv_alloc(thr,
+				                        DUK_HOBJECT_FLAG_EXTENSIBLE |
+				                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV));
+				DUK_ASSERT(env != NULL);
+				DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) env) == NULL);
+				env->target = target;  /* always provideThis=true */
+				DUK_HOBJECT_INCREF(thr, target);
+				env->has_this = 1;
+				DUK_ASSERT_HOBJENV_VALID(env);
+				DUK_DDD(DUK_DDDPRINT("environment for with binding: %!iO", env));
+
+				act = thr->callstack_curr;  /* relookup (side effects) */
+				DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) env) == NULL);
+				DUK_ASSERT(act->lex_env != NULL);
+				DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) env, act->lex_env);
+				act->lex_env = (duk_hobject *) env;  /* Now reachable. */
+				DUK_HOBJECT_INCREF(thr, (duk_hobject *) env);
+				/* Net refcount change to act->lex_env is 0: incref for env's
+				 * prototype, decref for act->lex_env overwrite.
+				 */
+
+				/* Set catcher lex_env active (affects unwind)
+				 * only when the whole setup is complete.
+				 */
+				cat = thr->catchstack + thr->catchstack_top - 1;
+				cat->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE;
+			} else {
+				;
+			}
+
 			DUK_DDD(DUK_DDDPRINT("TRYCATCH catcher: flags=0x%08lx, callstack_index=%ld, pc_base=%ld, "
 			                     "idx_base=%ld, h_varname=%!O",
 			                     (unsigned long) cat->flags, (long) cat->callstack_index,
 			                     (long) cat->pc_base, (long) cat->idx_base, (duk_heaphdr *) cat->h_varname));
 
+			duk_pop(ctx);
+
 			curr_pc += 2;  /* skip jump slots */
 			break;
 		}
@@ -72824,7 +74675,8 @@
 			cat = thr->catchstack + thr->catchstack_top - 1;
 			DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));  /* cleared before entering catch part */
 
-			act = thr->callstack + thr->callstack_top - 1;
+			act = thr->callstack_curr;
+			DUK_ASSERT(act != NULL);
 
 			if (DUK_CAT_HAS_LEXENV_ACTIVE(cat)) {
 				duk_hobject *prev_env;
@@ -72839,6 +74691,7 @@
 				DUK_ASSERT(prev_env != NULL);
 				act->lex_env = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, prev_env);
 				DUK_CAT_CLEAR_LEXENV_ACTIVE(cat);
+				DUK_HOBJECT_INCREF(thr, act->lex_env);
 				DUK_HOBJECT_DECREF(thr, prev_env);  /* side effects */
 			}
 
@@ -72963,7 +74816,8 @@
 
 				duk_push_tval(ctx, thr->valstack + cat->idx_base);
 
-				duk_err_setup_heap_ljstate(thr, (duk_small_int_t) cont_type);
+				duk_err_setup_ljstate1(thr, (duk_small_int_t) cont_type, thr->valstack + cat->idx_base);
+				/* No debugger Throw notify check on purpose (rethrow). */
 
 				DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
 				duk_err_longjmp(thr);
@@ -73000,7 +74854,10 @@
 			                     (duk_tval *) duk_get_tval(ctx, -1)));
 #endif
 
-			duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
+			duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, DUK_GET_TVAL_NEGIDX(ctx, -1));
+#if defined(DUK_USE_DEBUGGER_SUPPORT)
+			duk_err_check_debugger_integration(thr);
+#endif
 
 			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
 			duk_err_longjmp(thr);
@@ -73537,7 +75394,7 @@
 			 * from precompiled bytecode.
 			 */
 #if defined(DUK_USE_DEBUGGER_SUPPORT)
-			if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
+			if (duk_debug_is_attached(thr->heap)) {
 				DUK_D(DUK_DPRINT("DEBUGGER statement encountered, halt execution"));
 				DUK__SYNC_AND_NULL_CURR_PC();
 				duk_debug_halt_execution(thr, 1 /*use_prev_pc*/);
@@ -73630,22 +75487,18 @@
 		case DUK_OP_UNUSED252:
 		case DUK_OP_UNUSED253:
 		case DUK_OP_UNUSED254:
-		case DUK_OP_UNUSED255: {
-			/* Force all case clauses to map to an actual handler
-			 * so that the compiler can emit a jump without a bounds
-			 * check: the switch argument is a duk_uint8_t so that
-			 * the compiler may be able to figure it out.  This is
-			 * a small detail and obviously compiler dependent.
-			 */
-			volatile duk_small_int_t dummy_volatile;
-			dummy_volatile = 0;
-			DUK_UNREF(dummy_volatile);
-			DUK_D(DUK_DPRINT("invalid opcode: %ld - %!I", (long) op, ins));
-			DUK__INTERNAL_ERROR("invalid opcode");
-			break;
-		}
+		case DUK_OP_UNUSED255:
+		/* Force all case clauses to map to an actual handler
+		 * so that the compiler can emit a jump without a bounds
+		 * check: the switch argument is a duk_uint8_t so that
+		 * the compiler may be able to figure it out.  This is
+		 * a small detail and obviously compiler dependent.
+		 */
+		/* default: clause omitted on purpose */
+#else
+		default:
 #endif  /* DUK_USE_EXEC_PREFER_SIZE */
-		default: {
+		{
 			/* Default case catches invalid/unsupported opcodes. */
 			DUK_D(DUK_DPRINT("invalid opcode: %ld - %!I", (long) op, ins));
 			DUK__INTERNAL_ERROR("invalid opcode");
@@ -73962,7 +75815,7 @@
 	case DUK_TAG_STRING: {
 		/* For Symbols ToNumber() is always a TypeError. */
 		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
-		if (DUK_HSTRING_HAS_SYMBOL(h)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
 			DUK_ERROR_TYPE(thr, DUK_STR_CANNOT_NUMBER_COERCE_SYMBOL);
 		}
 		duk_push_hstring(ctx, h);
@@ -74694,7 +76547,7 @@
 		DUK_ASSERT(h1 != NULL);
 		DUK_ASSERT(h2 != NULL);
 
-		if (!DUK_HSTRING_HAS_SYMBOL(h1) && !DUK_HSTRING_HAS_SYMBOL(h2)) {
+		if (DUK_LIKELY(!DUK_HSTRING_HAS_SYMBOL(h1) && !DUK_HSTRING_HAS_SYMBOL(h2))) {
 			rc = duk_js_string_compare(h1, h2);
 			duk_pop_2(ctx);
 			if (rc < 0) {
@@ -74824,7 +76677,7 @@
 		/* func support for [[HasInstance]] checked in the beginning of the loop */
 	} while (--sanity > 0);
 
-	if (sanity == 0) {
+	if (DUK_UNLIKELY(sanity == 0)) {
 		DUK_ERROR_RANGE(thr, DUK_STR_BOUND_CHAIN_LIMIT);
 	}
 
@@ -74910,7 +76763,7 @@
 		val = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, val);
 	} while (--sanity > 0);
 
-	if (sanity == 0) {
+	if (DUK_UNLIKELY(sanity == 0)) {
 		DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
 	}
 	DUK_UNREACHABLE();
@@ -75011,7 +76864,7 @@
 		/* All internal keys are identified as Symbols. */
 		str = DUK_TVAL_GET_STRING(tv_x);
 		DUK_ASSERT(str != NULL);
-		if (DUK_HSTRING_HAS_SYMBOL(str)) {
+		if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(str))) {
 			stridx = DUK_STRIDX_LC_SYMBOL;
 		} else {
 			stridx = DUK_STRIDX_LC_STRING;
@@ -75061,32 +76914,30 @@
  *
  *  Array index: E5 Section 15.4
  *  Array length: E5 Section 15.4.5.1 steps 3.c - 3.d (array length write)
- *
- *  duk_js_to_arrayindex_string_helper() computes the array index from
- *  string contents alone.  Depending on options it's only called during
- *  string intern (and value stored to duk_hstring) or it's called also
- *  at runtime.
- */
-
-DUK_INTERNAL duk_small_int_t duk_js_to_arrayindex_raw_string(const duk_uint8_t *str, duk_uint32_t blen, duk_uarridx_t *out_idx) {
+ */
+
+/* Compure array index from string context, or return a "not array index"
+ * indicator.
+ */
+DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_string(const duk_uint8_t *str, duk_uint32_t blen) {
 	duk_uarridx_t res;
 
-	if (blen == 0 || blen > 10) {
-		goto parse_fail;
-	}
-	if (str[0] == DUK_ASC_0 && blen > 1) {
-		goto parse_fail;
-	}
-
-	/* Accept 32-bit decimal integers, no leading zeroes, signs, etc.
-	 * Leading zeroes are not accepted (zero index "0" is an exception
-	 * handled above).
+	/* Only strings with byte length 1-10 can be 32-bit array indices.
+	 * Leading zeroes (except '0' alone), plus/minus signs are not allowed.
+	 * We could do a lot of prechecks here, but since most strings won't
+	 * start with any digits, it's simpler to just parse the number and
+	 * fail quickly.
 	 */
 
 	res = 0;
-	while (blen-- > 0) {
-		duk_uint8_t c = *str++;
-		if (c >= DUK_ASC_0 && c <= DUK_ASC_9) {
+	if (blen == 0) {
+		goto parse_fail;
+	}
+	do {
+		duk_uarridx_t dig;
+		dig = (duk_uarridx_t) (*str++) - DUK_ASC_0;
+
+		if (dig <= 9U) {
 			/* Careful overflow handling.  When multiplying by 10:
 			 * - 0x19999998 x 10 = 0xfffffff0: no overflow, and adding
 			 *   0...9 is safe.
@@ -75100,44 +76951,75 @@
 					goto parse_fail;
 				}
 				DUK_ASSERT(res == 0x19999999UL);
-				c -= DUK_ASC_0;
-				if (c >= 6) {
+				if (dig >= 6U) {
 					goto parse_fail;
 				}
-				res = 0xfffffffaUL + c;
-				DUK_ASSERT(res >= 0xfffffffaUL && res <= 0xffffffffUL);
-			} else {
-				res = res * 10U + (duk_uint32_t) (c - DUK_ASC_0);
-			}
-		} else {
+				res = 0xfffffffaUL + dig;
+				DUK_ASSERT(res >= 0xfffffffaUL);
+				DUK_ASSERT_DISABLE(res <= 0xffffffffUL);  /* range */
+			} else {
+				res = res * 10U + dig;
+				if (DUK_UNLIKELY(res == 0)) {
+					/* If 'res' is 0, previous 'res' must
+					 * have been 0 and we scanned in a zero.
+					 * This is only allowed if blen == 1,
+					 * i.e. the exact string '0'.
+					 */
+					if (blen == (duk_uint32_t) 1) {
+						return 0;
+					}
+					goto parse_fail;
+				}
+			}
+		} else {
+			/* Because 'dig' is unsigned, catches both values
+			 * above '9' and below '0'.
+			 */
 			goto parse_fail;
 		}
-	}
-
-	*out_idx = res;
-	return 1;
+	} while (--blen > 0);
+
+	return res;
 
  parse_fail:
-	*out_idx = DUK_HSTRING_NO_ARRAY_INDEX;
-	return 0;
-}
-
-/* Called by duk_hstring.h macros */
-DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_string_helper(duk_hstring *h) {
+	return DUK_HSTRING_NO_ARRAY_INDEX;
+}
+
+#if !defined(DUK_USE_HSTRING_ARRIDX)
+/* Get array index for a string which is known to be an array index.  This helper
+ * is needed when duk_hstring doesn't concretely store the array index, but strings
+ * are flagged as array indices at intern time.
+ */
+DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_hstring_fast_known(duk_hstring *h) {
+	const duk_uint8_t *p;
 	duk_uarridx_t res;
-	duk_small_int_t rc;
-
+	duk_uint8_t t;
+
+	DUK_ASSERT(h != NULL);
+	DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(h));
+
+	p = DUK_HSTRING_GET_DATA(h);
+	res = 0;
+	for (;;) {
+		t = *p++;
+		if (DUK_UNLIKELY(t == 0)) {
+			/* Scanning to NUL is always safe for interned strings. */
+			break;
+		}
+		DUK_ASSERT(t >= DUK_ASC_0 && t <= DUK_ASC_9);
+		res = res * 10U + (t - DUK_ASC_0);
+	}
+	return res;
+}
+
+DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_hstring_fast(duk_hstring *h) {
+	DUK_ASSERT(h != NULL);
 	if (!DUK_HSTRING_HAS_ARRIDX(h)) {
 		return DUK_HSTRING_NO_ARRAY_INDEX;
 	}
-
-	rc = duk_js_to_arrayindex_raw_string(DUK_HSTRING_GET_DATA(h),
-	                                     DUK_HSTRING_GET_BYTELEN(h),
-	                                     &res);
-	DUK_UNREF(rc);
-	DUK_ASSERT(rc != 0);
-	return res;
-}
+	return duk_js_to_arrayindex_hstring_fast_known(h);
+}
+#endif  /* DUK_USE_HSTRING_ARRIDX */
 #line 1 "duk_js_var.c"
 /*
  *  Identifier access and function closure handling.
@@ -75178,11 +77060,11 @@
  */
 
 typedef struct {
+	duk_hobject *env;
 	duk_hobject *holder;      /* for object-bound identifiers */
 	duk_tval *value;          /* for register-bound and declarative env identifiers */
 	duk_int_t attrs;          /* property attributes for identifier (relevant if value != NULL) */
-	duk_tval *this_binding;
-	duk_hobject *env;
+	duk_bool_t has_this;      /* for object-bound identifiers: provide 'this' binding */
 } duk__id_lookup_result;
 
 /*
@@ -75335,7 +77217,7 @@
 	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&fun_clos->obj));
 	DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(&fun_clos->obj));
 	DUK_ASSERT(!DUK_HOBJECT_HAS_NATFUNC(&fun_clos->obj));
-	DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(&fun_clos->obj));
+	DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(&fun_clos->obj));
 	/* DUK_HOBJECT_FLAG_ARRAY_PART: don't care */
 	/* DUK_HOBJECT_FLAG_NEWENV: handled below */
 	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&fun_clos->obj));
@@ -75370,7 +77252,7 @@
 #if defined(DUK_USE_FUNC_NAME_PROPERTY)
 		if (DUK_HOBJECT_HAS_NAMEBINDING(&fun_clos->obj)) {
 			duk_hobject *proto;
-			duk_hobject *new_env;
+			duk_hdecenv *new_env;
 
 			/*
 			 *  Named function expression, name needs to be bound
@@ -75388,11 +77270,18 @@
 			}
 
 			/* -> [ ... closure template env ] */
-			new_env = duk_push_object_helper_proto(ctx,
-			                                       DUK_HOBJECT_FLAG_EXTENSIBLE |
-			                                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
-			                                       proto);
+			new_env = duk_hdecenv_alloc(thr,
+			                            DUK_HOBJECT_FLAG_EXTENSIBLE |
+			                            DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV));
 			DUK_ASSERT(new_env != NULL);
+			duk_push_hobject(ctx, (duk_hobject *) new_env);
+
+			DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL);
+			DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, proto);
+			DUK_HOBJECT_INCREF_ALLOWNULL(thr, proto);
+
+			DUK_ASSERT(new_env->thread == NULL);  /* Closed. */
+			DUK_ASSERT(new_env->varmap == NULL);
 
 			/* It's important that duk_xdef_prop() is a 'raw define' so that any
 			 * properties in an ancestor are never an issue (they should never be
@@ -75411,10 +77300,10 @@
 
 			/* [ ... closure template env ] */
 
-			DUK_HCOMPFUNC_SET_LEXENV(thr->heap, fun_clos, new_env);
-			DUK_HCOMPFUNC_SET_VARENV(thr->heap, fun_clos, new_env);
-			DUK_HOBJECT_INCREF(thr, new_env);
-			DUK_HOBJECT_INCREF(thr, new_env);
+			DUK_HCOMPFUNC_SET_LEXENV(thr->heap, fun_clos, (duk_hobject *) new_env);
+			DUK_HCOMPFUNC_SET_VARENV(thr->heap, fun_clos, (duk_hobject *) new_env);
+			DUK_HOBJECT_INCREF(thr, (duk_hobject *) new_env);
+			DUK_HOBJECT_INCREF(thr, (duk_hobject *) new_env);
 			duk_pop(ctx);
 
 			/* [ ... closure template ] */
@@ -75636,10 +77525,11 @@
                                                       duk_hobject *func,
                                                       duk_size_t idx_bottom) {
 	duk_context *ctx = (duk_context *) thr;
-	duk_hobject *env;
+	duk_hdecenv *env;
 	duk_hobject *parent;
 	duk_hcompfunc *f;
 
+	DUK_ASSERT(ctx != NULL);
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(func != NULL);
 
@@ -75649,25 +77539,44 @@
 		parent = thr->builtins[DUK_BIDX_GLOBAL_ENV];
 	}
 
-	(void) duk_push_object_helper(ctx,
-	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
-	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
-	                              -1);  /* no prototype, updated below */
-	env = duk_known_hobject(ctx, -1);
-	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, env, parent);  /* parent env is the prototype */
+	env = duk_hdecenv_alloc(thr,
+	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV));
+	DUK_ASSERT(env != NULL);
+	duk_push_hobject(ctx, (duk_hobject *) env);
+
+	DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) env) == NULL);
+	DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) env, parent);
+	DUK_HOBJECT_INCREF_ALLOWNULL(thr, parent);  /* parent env is the prototype */
 
 	/* open scope information, for compiled functions only */
 
+	DUK_ASSERT(env->thread == NULL);
+	DUK_ASSERT(env->varmap == NULL);
+	DUK_ASSERT(env->regbase == 0);
 	if (DUK_HOBJECT_IS_COMPFUNC(func)) {
-		duk_push_hthread(ctx, thr);
-		duk_xdef_prop_stridx_short_wec(ctx, -2, DUK_STRIDX_INT_THREAD);
-		duk_push_hobject(ctx, func);
-		duk_xdef_prop_stridx_short_wec(ctx, -2, DUK_STRIDX_INT_CALLEE);
-		duk_push_size_t(ctx, idx_bottom);
-		duk_xdef_prop_stridx_short_wec(ctx, -2, DUK_STRIDX_INT_REGBASE);
-	}
-
-	return env;
+		duk_hobject *varmap;
+		duk_tval *tv;
+
+		tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
+		if (tv != NULL && DUK_TVAL_IS_OBJECT(tv)) {
+			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+			varmap = DUK_TVAL_GET_OBJECT(tv);
+			DUK_ASSERT(varmap != NULL);
+			env->varmap = varmap;
+			DUK_HOBJECT_INCREF(thr, varmap);
+			env->thread = thr;
+			DUK_HTHREAD_INCREF(thr, thr);
+			env->regbase = idx_bottom;
+		} else {
+			/* If function has no _Varmap, leave the environment closed. */
+			DUK_ASSERT(env->thread == NULL);
+			DUK_ASSERT(env->varmap == NULL);
+			DUK_ASSERT(env->regbase == 0);
+		}
+	}
+
+	return (duk_hobject *) env;
 }
 
 DUK_INTERNAL
@@ -75676,7 +77585,10 @@
 	duk_context *ctx = (duk_context *) thr;
 	duk_hobject *func;
 	duk_hobject *env;
-
+	duk_size_t act_off;
+
+	DUK_ASSERT(act != NULL);
+	act_off = (duk_size_t) ((duk_uint8_t *) act - (duk_uint8_t *) thr->callstack);
 	func = DUK_ACT_GET_FUNC(act);
 	DUK_ASSERT(func != NULL);
 	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(func));  /* bound functions are never in act 'func' */
@@ -75691,6 +77603,7 @@
 
 	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
 	DUK_ASSERT(env != NULL);
+	act = (duk_activation *) (void *) ((duk_uint8_t *) thr->callstack + act_off);
 
 	DUK_DDD(DUK_DDDPRINT("created delayed fresh env: %!ipO", (duk_heaphdr *) env));
 #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2)
@@ -75715,156 +77628,103 @@
  *  Closing environment records.
  *
  *  The environment record MUST be closed with the thread where its activation
- *  is.  In other words (if 'env' is open):
- *
- *    - 'thr' must match _env.thread
- *    - 'func' must match _env.callee
- *    - 'regbase' must match _env.regbase
- *
- *  These are not looked up from the env to minimize code size.
- *
- *  XXX: should access the own properties directly instead of using the API
- */
-
-DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env, duk_hobject *func, duk_size_t regbase) {
+ *  is; i.e. if 'env' is open, 'thr' must match env->thread, and the regbase
+ *  and varmap must still be valid.  On entry, 'env' must be reachable.
+ */
+
+DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env) {
 	duk_context *ctx = (duk_context *) thr;
 	duk_uint_fast32_t i;
+	duk_hobject *varmap;
+	duk_hstring *key;
+	duk_tval *tv;
+	duk_uint_t regnum;
 
 	DUK_ASSERT(thr != NULL);
 	DUK_ASSERT(env != 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, "
-		                     "or already closed: %!iO",
-		                     (duk_heaphdr *) env));
-		return;
-	}
-
-	DUK_DDD(DUK_DDDPRINT("closing environment record: %!iO, func: %!iO, regbase: %ld",
-	                     (duk_heaphdr *) env, (duk_heaphdr *) func, (long) regbase));
-
-	duk_push_hobject(ctx, env);
-
-	/* assertions: env must be closed in the same thread as where it runs */
-#if defined(DUK_USE_ASSERTIONS)
-	{
-		/* [... env] */
-
-		if (duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_CALLEE)) {
-			DUK_ASSERT(duk_is_object(ctx, -1));
-			DUK_ASSERT(duk_get_hobject(ctx, -1) == (duk_hobject *) func);
-		}
-		duk_pop(ctx);
-
-		if (duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_THREAD)) {
-			DUK_ASSERT(duk_is_object(ctx, -1));
-			DUK_ASSERT(duk_get_hobject(ctx, -1) == (duk_hobject *) thr);
-		}
-		duk_pop(ctx);
-
-		if (duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_REGBASE)) {
-			DUK_ASSERT(duk_is_number(ctx, -1));
-			DUK_ASSERT(duk_get_number(ctx, -1) == (double) regbase);
-		}
-		duk_pop(ctx);
-
-		/* [... env] */
-	}
-#endif
-
-	if (func != NULL && DUK_HOBJECT_IS_COMPFUNC(func)) {
-		duk_hobject *varmap;
-		duk_hstring *key;
-		duk_tval *tv;
-		duk_uint_t regnum;
-
-		/* XXX: additional conditions when to close variables? we don't want to do it
-		 * unless the environment may have "escaped" (referenced in a function closure).
-		 * With delayed environments, the existence is probably good enough of a check.
-		 */
-
-		/* XXX: any way to detect faster whether something needs to be closed?
-		 * We now look up _Callee and then skip the rest.
-		 */
-
-		/* Note: we rely on the _Varmap having a bunch of nice properties, like:
-		 *  - being compacted and unmodified during this process
-		 *  - not containing an array part
-		 *  - having correct value types
-		 */
-
-		/* [... env] */
-
-		if (!duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_CALLEE)) {
-			DUK_DDD(DUK_DDDPRINT("env has no callee property, nothing to close; re-delete the control properties just in case"));
-			duk_pop(ctx);
-			goto skip_varmap;
-		}
-
-		/* [... env callee] */
-
-		if (!duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_VARMAP)) {
-			DUK_DDD(DUK_DDDPRINT("callee has no varmap property, nothing to close; delete the control properties"));
-			duk_pop_2(ctx);
-			goto skip_varmap;
-		}
-		varmap = duk_require_hobject(ctx, -1);
-		DUK_ASSERT(varmap != NULL);
-
-		DUK_DDD(DUK_DDDPRINT("varmap: %!O", (duk_heaphdr *) varmap));
-
-		/* [... env callee varmap] */
-
-		DUK_DDD(DUK_DDDPRINT("copying bound register values, %ld bound regs", (long) DUK_HOBJECT_GET_ENEXT(varmap)));
-
-		for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(varmap); i++) {
-			key = DUK_HOBJECT_E_GET_KEY(thr->heap, varmap, i);
-			DUK_ASSERT(key != NULL);   /* assume keys are compacted */
-
-			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, varmap, i));  /* assume plain values */
-
-			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, varmap, i);
-			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));  /* assume value is a number */
-			regnum = (duk_uint_t) DUK_TVAL_GET_NUMBER(tv);
-			DUK_ASSERT_DISABLE(regnum >= 0);  /* unsigned */
-			DUK_ASSERT(regnum < ((duk_hcompfunc *) func)->nregs);  /* regnum is sane */
-			DUK_ASSERT(thr->valstack + regbase + regnum >= thr->valstack);
-			DUK_ASSERT(thr->valstack + regbase + regnum < thr->valstack_top);
-
-			/* XXX: slightly awkward */
-			duk_push_hstring(ctx, key);
-			duk_push_tval(ctx, thr->valstack + regbase + regnum);
-			DUK_DDD(DUK_DDDPRINT("closing identifier '%s' -> reg %ld, value %!T",
-			                     (const char *) duk_require_string(ctx, -2),
-			                     (long) regnum,
-			                     (duk_tval *) duk_get_tval(ctx, -1)));
-
-			/* [... env callee varmap key val] */
-
-			/* if property already exists, overwrites silently */
-			duk_xdef_prop(ctx, -5, DUK_PROPDESC_FLAGS_WE);  /* writable but not deletable */
-		}
-
-		duk_pop_2(ctx);
-
-		/* [... env] */
-	}
-
- skip_varmap:
-
-	/* [... env] */
-
-	duk_del_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_CALLEE);
-	duk_del_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_THREAD);
-	duk_del_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_REGBASE);
-
-	duk_pop(ctx);
-
-	DUK_HOBJECT_SET_ENVRECCLOSED(env);
-
-	DUK_DDD(DUK_DDDPRINT("environment record after being closed: %!O",
-	                     (duk_heaphdr *) env));
+
+	if (DUK_UNLIKELY(!DUK_HOBJECT_IS_DECENV(env))) {
+		DUK_DDD(DUK_DDDPRINT("env not a declarative record: %!iO", (duk_heaphdr *) env));
+		return;
+	}
+
+	varmap = ((duk_hdecenv *) env)->varmap;
+	if (varmap == NULL) {
+		DUK_DDD(DUK_DDDPRINT("env already closed: %!iO", (duk_heaphdr *) env));
+
+		return;
+	}
+	DUK_ASSERT(((duk_hdecenv *) env)->thread != NULL);
+	DUK_ASSERT_HDECENV_VALID((duk_hdecenv *) env);
+
+	DUK_DDD(DUK_DDDPRINT("closing env: %!iO", (duk_heaphdr *) env));
+	DUK_DDD(DUK_DDDPRINT("varmap: %!O", (duk_heaphdr *) varmap));
+
+	/* Env must be closed in the same thread as where it runs. */
+	DUK_ASSERT(((duk_hdecenv *) env)->thread == thr);
+
+	/* XXX: additional conditions when to close variables? we don't want to do it
+	 * unless the environment may have "escaped" (referenced in a function closure).
+	 * With delayed environments, the existence is probably good enough of a check.
+	 */
+
+	/* Note: we rely on the _Varmap having a bunch of nice properties, like:
+	 *  - being compacted and unmodified during this process
+	 *  - not containing an array part
+	 *  - having correct value types
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("copying bound register values, %ld bound regs", (long) DUK_HOBJECT_GET_ENEXT(varmap)));
+
+	/* Copy over current variable values from value stack to the
+	 * environment record.  The scope object is empty but may
+	 * inherit from another scope which has conflicting names.
+	 */
+
+	/* XXX: Do this using a once allocated entry area, no side effects.
+	 * Hash part would need special treatment however (maybe copy, and
+	 * then realloc with hash part if large enough).
+	 */
+	for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(varmap); i++) {
+		duk_size_t regbase;
+
+		key = DUK_HOBJECT_E_GET_KEY(thr->heap, varmap, i);
+		DUK_ASSERT(key != NULL);   /* assume keys are compact in _Varmap */
+		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, varmap, i));  /* assume plain values */
+
+		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, varmap, i);
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (duk_double_t) DUK_UINT32_MAX);  /* limits */
+#if defined(DUK_USE_FASTINT)
+		DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv));
+		regnum = (duk_uint_t) DUK_TVAL_GET_FASTINT_U32(tv);
+#else
+		regnum = (duk_uint_t) DUK_TVAL_GET_NUMBER(tv);
+#endif
+
+		regbase = ((duk_hdecenv *) env)->regbase;
+		DUK_ASSERT(thr->valstack + regbase + regnum >= thr->valstack);
+		DUK_ASSERT(thr->valstack + regbase + regnum < thr->valstack_top);
+
+		/* If property already exists, overwrites silently.
+		 * Property is writable, but not deletable (not configurable
+		 * in terms of property attributes).
+		 */
+		duk_push_tval(ctx, thr->valstack + regbase + regnum);
+		DUK_DDD(DUK_DDDPRINT("closing identifier %!O -> reg %ld, value %!T",
+		                     (duk_heaphdr *) key,
+		                     (long) regnum,
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+		duk_hobject_define_property_internal(thr, env, key, DUK_PROPDESC_FLAGS_WE);
+	}
+
+	/* NULL atomically to avoid inconsistent state + side effects. */
+	DUK_HOBJECT_DECREF_NORZ(thr, ((duk_hdecenv *) env)->thread);
+	DUK_HOBJECT_DECREF_NORZ(thr, ((duk_hdecenv *) env)->varmap);
+	((duk_hdecenv *) env)->thread = NULL;
+	((duk_hdecenv *) env)->varmap = NULL;
+
+	DUK_DDD(DUK_DDDPRINT("env after closing: %!O", (duk_heaphdr *) env));
 }
 
 /*
@@ -75894,12 +77754,8 @@
 DUK_LOCAL
 duk_bool_t duk__getid_open_decl_env_regs(duk_hthread *thr,
                                          duk_hstring *name,
-                                         duk_hobject *env,
+                                         duk_hdecenv *env,
                                          duk__id_lookup_result *out) {
-	duk_hthread *env_thr;
-	duk_hobject *env_func;
-	duk_size_t env_regbase;
-	duk_hobject *varmap;
 	duk_tval *tv;
 	duk_size_t reg_rel;
 	duk_size_t idx;
@@ -75909,69 +77765,39 @@
 	DUK_ASSERT(env != NULL);
 	DUK_ASSERT(out != NULL);
 
-	DUK_ASSERT(DUK_HOBJECT_IS_DECENV(env));
-
-	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_CALLEE(thr));
-	if (!tv) {
-		/* env is closed, should be missing _Callee, _Thread, _Regbase */
-		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL);
-		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL);
-		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL);
-		return 0;
-	}
-
-	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
-	DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
-	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_TVAL_GET_OBJECT(tv)));
-	env_func = DUK_TVAL_GET_OBJECT(tv);
-	DUK_ASSERT(env_func != NULL);
-
-	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env_func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
-	if (!tv) {
-		return 0;
-	}
-	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
-	varmap = DUK_TVAL_GET_OBJECT(tv);
-	DUK_ASSERT(varmap != NULL);
-
-	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, varmap, name);
-	if (!tv) {
-		return 0;
-	}
+	DUK_ASSERT(DUK_HOBJECT_IS_DECENV((duk_hobject *) env));
+	DUK_ASSERT_HDECENV_VALID(env);
+
+	if (env->thread == NULL) {
+		/* already closed */
+		return 0;
+	}
+	DUK_ASSERT(env->varmap != NULL);
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env->varmap, name);
+	if (DUK_UNLIKELY(tv == NULL)) {
+		return 0;
+	}
+
 	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+	DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (duk_double_t) DUK_UINT32_MAX);  /* limits */
+#if defined(DUK_USE_FASTINT)
+	DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv));
+	reg_rel = (duk_size_t) DUK_TVAL_GET_FASTINT_U32(tv);
+#else
 	reg_rel = (duk_size_t) DUK_TVAL_GET_NUMBER(tv);
+#endif
 	DUK_ASSERT_DISABLE(reg_rel >= 0);  /* unsigned */
-	DUK_ASSERT(reg_rel < ((duk_hcompfunc *) env_func)->nregs);
-
-	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_THREAD(thr));
-	DUK_ASSERT(tv != NULL);
-	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
-	DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
-	DUK_ASSERT(DUK_HOBJECT_IS_THREAD(DUK_TVAL_GET_OBJECT(tv)));
-	env_thr = (duk_hthread *) DUK_TVAL_GET_OBJECT(tv);
-	DUK_ASSERT(env_thr != NULL);
-
-	/* Note: env_thr != thr is quite possible and normal, so careful
-	 * with what thread is used for valstack lookup.
-	 */
-
-	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_REGBASE(thr));
-	DUK_ASSERT(tv != NULL);
-	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
-	env_regbase = (duk_size_t) DUK_TVAL_GET_NUMBER(tv);
-
-	idx = env_regbase + reg_rel;
-	tv = env_thr->valstack + idx;
-	DUK_ASSERT(tv >= env_thr->valstack && tv < env_thr->valstack_end);  /* XXX: more accurate? */
+
+	idx = env->regbase + reg_rel;
+	tv = env->thread->valstack + idx;
+	DUK_ASSERT(tv >= env->thread->valstack && tv < env->thread->valstack_end);  /* XXX: more accurate? */
 
 	out->value = tv;
 	out->attrs = DUK_PROPDESC_FLAGS_W;  /* registers are mutable, non-deletable */
-	out->this_binding = NULL;  /* implicit this value always undefined for
-	                            * declarative environment records.
-	                            */
-	out->env = env;
+	out->env = (duk_hobject *) env;
 	out->holder = NULL;
-
+	out->has_this = 0;
 	return 1;
 }
 
@@ -76000,6 +77826,7 @@
 		return 0;
 	}
 
+	/* XXX: move varmap to duk_hcompfunc struct field. */
 	tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
 	if (!tv) {
 		return 0;
@@ -76023,12 +77850,9 @@
 
 	out->value = tv;
 	out->attrs = DUK_PROPDESC_FLAGS_W;  /* registers are mutable, non-deletable */
-	out->this_binding = NULL;  /* implicit this value always undefined for
-	                            * declarative environment records.
-	                            */
 	out->env = NULL;
 	out->holder = NULL;
-
+	out->has_this = 0;
 	return 1;
 }
 
@@ -76040,7 +77864,6 @@
                                          duk_bool_t parents,
                                          duk__id_lookup_result *out) {
 	duk_tval *tv;
-	duk_tval *tv_target;
 	duk_tval tv_name;
 	duk_uint_t sanity;
 
@@ -76081,10 +77904,10 @@
 
 		if (duk__getid_activation_regs(thr, name, act, out)) {
 			DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
-			                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+			                     "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O "
 			                     "(found from register bindings when env=NULL)",
 			                     (duk_heaphdr *) name, (duk_tval *) out->value,
-			                     (long) out->attrs, (duk_tval *) out->this_binding,
+			                     (long) out->attrs, (long) out->has_this,
 			                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
 			return 1;
 		}
@@ -76161,37 +77984,30 @@
 			 *  register-bound variables.
 			 */
 
-			if (DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
-				/* already closed */
-				goto skip_regs;
-			}
-
-			if (duk__getid_open_decl_env_regs(thr, name, env, out)) {
+			DUK_ASSERT_HDECENV_VALID((duk_hdecenv *) env);
+			if (duk__getid_open_decl_env_regs(thr, name, (duk_hdecenv *) env, out)) {
 				DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
-				                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+				                     "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O "
 				                     "(declarative environment record, scope open, found in regs)",
 				                     (duk_heaphdr *) name, (duk_tval *) out->value,
-				                     (long) out->attrs, (duk_tval *) out->this_binding,
+				                     (long) out->attrs, (long) out->has_this,
 				                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
 				return 1;
 			}
-		 skip_regs:
 
 			tv = duk_hobject_find_existing_entry_tval_ptr_and_attrs(thr->heap, env, name, &attrs);
 			if (tv) {
 				out->value = tv;
 				out->attrs = attrs;
-				out->this_binding = NULL;  /* implicit this value always undefined for
-				                            * declarative environment records.
-				                            */
 				out->env = env;
 				out->holder = env;
+				out->has_this = 0;
 
 				DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
-				                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+				                     "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O "
 				                     "(declarative environment record, found in properties)",
 				                     (duk_heaphdr *) name, (duk_tval *) out->value,
-				                     (long) out->attrs, (duk_tval *) out->this_binding,
+				                     (long) out->attrs, (long) out->has_this,
 				                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
 				return 1;
 			}
@@ -76214,11 +78030,9 @@
 			duk_bool_t found;
 
 			DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV);
-
-			tv_target = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_TARGET(thr));
-			DUK_ASSERT(tv_target != NULL);
-			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_target));
-			target = DUK_TVAL_GET_OBJECT(tv_target);
+			DUK_ASSERT_HOBJENV_VALID((duk_hobjenv *) env);
+
+			target = ((duk_hobjenv *) env)->target;
 			DUK_ASSERT(target != NULL);
 
 			/* Target may be a Proxy or property may be an accessor, so we must
@@ -76229,10 +78043,13 @@
 			 */
 
 			if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(target)) {
+				duk_tval tv_target_tmp;
+
 				DUK_ASSERT(name != NULL);
 				DUK_TVAL_SET_STRING(&tv_name, name);
-
-				found = duk_hobject_hasprop(thr, tv_target, &tv_name);
+				DUK_TVAL_SET_OBJECT(&tv_target_tmp, target);
+
+				found = duk_hobject_hasprop(thr, &tv_target_tmp, &tv_name);
 			} else {
 				/* XXX: duk_hobject_hasprop() would be correct for
 				 * non-Proxy objects too, but it is about ~20-25%
@@ -76245,16 +78062,15 @@
 			if (found) {
 				out->value = NULL;  /* can't get value, may be accessor */
 				out->attrs = 0;     /* irrelevant when out->value == NULL */
-				tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_THIS(thr));
-				out->this_binding = tv;  /* may be NULL */
 				out->env = env;
 				out->holder = target;
+				out->has_this = ((duk_hobjenv *) env)->has_this;
 
 				DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
-				                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+				                     "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O "
 				                     "(object environment record)",
 				                     (duk_heaphdr *) name, (duk_tval *) out->value,
-				                     (long) out->attrs, (duk_tval *) out->this_binding,
+				                     (long) out->attrs, (long) out->has_this,
 				                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
 				return 1;
 			}
@@ -76266,11 +78082,11 @@
 			goto fail_not_found;
 		}
 
-                if (sanity-- == 0) {
+                if (DUK_UNLIKELY(sanity-- == 0)) {
                         DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
                 }
 		env = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, env);
-	};
+	}
 
 	/*
 	 *  Not found (even in global object)
@@ -76377,29 +78193,27 @@
 	parents = 1;     /* follow parent chain */
 	if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) {
 		if (ref.value) {
-			DUK_ASSERT(ref.this_binding == NULL);  /* always for register bindings */
 			duk_push_tval(ctx, ref.value);
 			duk_push_undefined(ctx);
 		} else {
 			DUK_ASSERT(ref.holder != NULL);
 
-			/* Note: getprop may invoke any getter and invalidate any
-			 * duk_tval pointers, so this must be done first.
-			 */
-
-			if (ref.this_binding) {
-				duk_push_tval(ctx, ref.this_binding);
-			} else {
-				duk_push_undefined(ctx);
-			}
+			/* ref.holder is safe across the getprop call (even
+			 * with side effects) because 'env' is reachable and
+			 * ref.holder is a direct heap pointer.
+			 */
 
 			DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder);
 			DUK_TVAL_SET_STRING(&tv_tmp_key, name);
-			(void) duk_hobject_getprop(thr, &tv_tmp_obj, &tv_tmp_key);  /* [this value] */
-
-			/* ref.value, ref.this.binding invalidated here by getprop call */
-
-			duk_insert(ctx, -2);  /* [this value] -> [value this] */
+			(void) duk_hobject_getprop(thr, &tv_tmp_obj, &tv_tmp_key);  /* [value] */
+
+			if (ref.has_this) {
+				duk_push_hobject(ctx, ref.holder);
+			} else {
+				duk_push_undefined(ctx);
+			}
+
+			/* [value this] */
 		}
 
 		return 1;
@@ -76502,13 +78316,11 @@
 			 */
 			duk_tval *tv_val;
 
-			DUK_ASSERT(ref.this_binding == NULL);  /* always for register bindings */
-
 			tv_val = ref.value;
 			DUK_ASSERT(tv_val != NULL);
 			DUK_TVAL_SET_TVAL_UPDREF(thr, tv_val, val);  /* side effects */
 
-			/* ref.value and ref.this_binding invalidated here */
+			/* ref.value invalidated here */
 		} else {
 			DUK_ASSERT(ref.holder != NULL);
 
@@ -76516,7 +78328,7 @@
 			DUK_TVAL_SET_STRING(&tv_tmp_key, name);
 			(void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, strict);
 
-			/* ref.value and ref.this_binding invalidated here */
+			/* ref.value invalidated here */
 		}
 
 		return;
@@ -76889,14 +78701,11 @@
 	 */
 
 	if (DUK_HOBJECT_IS_DECENV(env)) {
+		DUK_ASSERT_HDECENV_VALID((duk_hdecenv *) env);
 		holder = env;
 	} else {
-		DUK_ASSERT(DUK_HOBJECT_IS_OBJENV(env));
-
-		tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env, DUK_HTHREAD_STRING_INT_TARGET(thr));
-		DUK_ASSERT(tv != NULL);
-		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
-		holder = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT_HOBJENV_VALID((duk_hobjenv *) env);
+		holder = ((duk_hobjenv *) env)->target;
 		DUK_ASSERT(holder != NULL);
 	}
 
@@ -76938,6 +78747,10 @@
                                      duk_bool_t is_func_decl) {
 	duk_hobject *env;
 	duk_tval tv_val_copy;
+	duk_size_t act_off;
+
+	DUK_ASSERT(act != NULL);
+	act_off = (duk_size_t) ((duk_uint8_t *) act - (duk_uint8_t *) thr->callstack);
 
 	/*
 	 *  Make a value copy of the input val.  This ensures that
@@ -76954,6 +78767,7 @@
 	if (!act->var_env) {
 		DUK_ASSERT(act->lex_env == NULL);
 		duk_js_init_activation_environment_records_delayed(thr, act);
+		act = (duk_activation *) (void *) ((duk_uint8_t *) thr->callstack + act_off);
 	}
 	DUK_ASSERT(act->lex_env != NULL);
 	DUK_ASSERT(act->var_env != NULL);
@@ -77711,6 +79525,68 @@
 	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_ESCAPE);
 }
 
+/* Parse legacy octal escape of the form \N{1,3}, e.g. \0, \5, \0377.  Maximum
+ * allowed value is \0377 (U+00FF), longest match is used.  Used for both string
+ * RegExp octal escape parsing.  Window[0] must be the slash '\' and the first
+ * digit must already be validated to be in [0-9] by the caller.
+ */
+DUK_LOCAL duk_codepoint_t duk__lexer_parse_legacy_octal(duk_lexer_ctx *lex_ctx, duk_small_int_t *out_adv, duk_bool_t reject_annex_b) {
+	duk_codepoint_t cp;
+	duk_small_uint_t lookup_idx;
+	duk_small_int_t adv;
+	duk_codepoint_t tmp;
+
+	DUK_ASSERT(out_adv != NULL);
+	DUK_ASSERT(DUK__LOOKUP(lex_ctx, 0) == DUK_ASC_BACKSLASH);
+	DUK_ASSERT(DUK__LOOKUP(lex_ctx, 1) >= DUK_ASC_0 && DUK__LOOKUP(lex_ctx, 1) <= DUK_ASC_9);
+
+	cp = 0;
+	for (lookup_idx = 1; lookup_idx <= 3; lookup_idx++) {
+		DUK_DDD(DUK_DDDPRINT("lookup_idx=%ld, cp=%ld", (long) lookup_idx, (long) cp));
+		tmp = DUK__LOOKUP(lex_ctx, lookup_idx);
+		if (tmp < DUK_ASC_0 || tmp > DUK_ASC_7) {
+			/* No more valid digits. */
+			break;
+		}
+		tmp = (cp << 3) + (tmp - DUK_ASC_0);
+		if (tmp > 0xff) {
+			/* Three digit octal escapes above \377 (= 0xff)
+			 * are not allowed.
+			 */
+			break;
+		}
+		cp = tmp;
+	}
+	DUK_DDD(DUK_DDDPRINT("final lookup_idx=%ld, cp=%ld", (long) lookup_idx, (long) cp));
+
+	adv = lookup_idx;
+	if (lookup_idx == 1) {
+		DUK_DDD(DUK_DDDPRINT("\\8 or \\9 -> treat as literal, accept in strict mode too"));
+		DUK_ASSERT(tmp == DUK_ASC_8 || tmp == DUK_ASC_9);
+		cp = tmp;
+		adv++;  /* correction to above, eat offending character */
+	} else if (lookup_idx == 2 && cp == 0) {
+		/* Note: 'foo\0bar' is OK in strict mode, but 'foo\00bar' is not.
+		 * It won't be interpreted as 'foo\u{0}0bar' but as a SyntaxError.
+		 */
+		DUK_DDD(DUK_DDDPRINT("\\0 -> accept in strict mode too"));
+	} else {
+		/* This clause also handles non-shortest zero, e.g. \00. */
+		if (reject_annex_b) {
+			DUK_DDD(DUK_DDDPRINT("non-zero octal literal %ld -> reject in strict-mode", (long) cp));
+			cp = -1;
+		} else {
+			DUK_DDD(DUK_DDDPRINT("non-zero octal literal %ld -> accepted", (long) cp));
+			DUK_ASSERT(cp >= 0 && cp <= 0xff);
+		}
+	}
+
+	*out_adv = adv;
+
+	DUK_ASSERT((cp >= 0 && cp <= 0xff) || (cp == -1 && reject_annex_b));
+	return cp;
+}
+
 /* XXX: move strict mode to lex_ctx? */
 DUK_LOCAL void duk__lexer_parse_string_literal(duk_lexer_ctx *lex_ctx, duk_token *out_token, duk_small_int_t quote, duk_bool_t strict_mode) {
 	duk_small_int_t adv;
@@ -77796,46 +79672,9 @@
 					 *  Parse octal (up to 3 digits) from the lookup window.
 					 */
 
-					duk_codepoint_t tmp;
-					duk_small_uint_t lookup_idx;
-
-					emitcp = 0;
-					for (lookup_idx = 1; lookup_idx <= 3; lookup_idx++) {
-						DUK_DDD(DUK_DDDPRINT("lookup_idx=%ld, emitcp=%ld", (long) lookup_idx, (long) emitcp));
-						tmp = DUK__LOOKUP(lex_ctx, lookup_idx);
-						if (tmp < DUK_ASC_0 || tmp > DUK_ASC_7) {
-							/* No more valid digits. */
-							break;
-						}
-						tmp = (emitcp << 3) + (tmp - DUK_ASC_0);
-						if (tmp > 0xff) {
-							/* Three digit octal escapes above \377 (= 0xff)
-							 * are not allowed.
-							 */
-							break;
-						}
-						emitcp = tmp;
-					}
-					DUK_DDD(DUK_DDDPRINT("final lookup_idx=%ld, emitcp=%ld", (long) lookup_idx, (long) emitcp));
-
-					adv = lookup_idx;
-					if (lookup_idx == 1) {
-						/* \8 or \9 -> treat as literal, accept also
-						 * in strict mode.
-						 */
-						DUK_DDD(DUK_DDDPRINT("\\8 or \\9 -> treat as literal, accept in strict mode too"));
-						emitcp = x;
-						adv++;  /* correction to above, eat offending character */
-					} else if (lookup_idx == 2 && emitcp == 0) {
-						/* Zero escape, also allowed in non-strict mode. */
-						DUK_DDD(DUK_DDDPRINT("\\0 -> accept in strict mode too"));
-					} else {
-						/* Valid octal, only accept in non-strict mode. */
-						DUK_DDD(DUK_DDDPRINT("octal literal %ld -> accept only in non-strict-mode", (long) emitcp));
-						DUK_ASSERT(emitcp >= 0 && emitcp <= 0xff);
-						if (strict_mode) {
-							goto fail_escape;
-						}
+					emitcp = duk__lexer_parse_legacy_octal(lex_ctx, &adv, strict_mode /*reject_annex_b*/);
+					if (emitcp < 0) {
+						goto fail_escape;
 					}
 				} else if (x < 0) {
 					goto fail_unterminated;
@@ -77886,6 +79725,19 @@
 	return;
 }
 
+/* Skip to end-of-line (or end-of-file), used for single line comments. */
+DUK_LOCAL void duk__lexer_skip_to_endofline(duk_lexer_ctx *lex_ctx) {
+	for (;;) {
+		duk_codepoint_t x;
+
+		x = DUK__L0();
+		if (x < 0 || duk_unicode_is_line_terminator(x)) {
+			break;
+		}
+		DUK__ADVANCECHARS(lex_ctx, 1);
+	}
+}
+
 /*
  *  Parse Ecmascript source InputElementDiv or InputElementRegExp
  *  (E5 Section 7), skipping whitespace, comments, and line terminators.
@@ -78024,6 +79876,17 @@
 		DUK__ADVANCECHARS(lex_ctx, 1);
 		got_lineterm = 1;
 		goto restart_lineupdate;
+#if defined(DUK_USE_SHEBANG_COMMENTS)
+	case DUK_ASC_HASH:  /* '#' */
+		if (DUK__L1() == DUK_ASC_EXCLAMATION && lex_ctx->window[0].offset == 0 &&
+		    (lex_ctx->flags & DUK_COMPILE_SHEBANG)) {
+			/* "Shebang" comment ('#! ...') on first line. */
+			/* DUK__ADVANCECHARS(lex_ctx, 2) would be correct here, but not necessary */
+			duk__lexer_skip_to_endofline(lex_ctx);
+			goto restart;  /* line terminator will be handled on next round */
+		}
+		goto fail_token;
+#endif  /* DUK_USE_SHEBANG_COMMENTS */
 	case DUK_ASC_SLASH:  /* '/' */
 		if (DUK__L1() == DUK_ASC_SLASH) {
 			/*
@@ -78031,14 +79894,8 @@
 			 *  code point).
 			 */
 
-			/* DUK__ADVANCECHARS(lex_ctx, 2) would be correct here, but it unnecessary */
-			for (;;) {
-				x = DUK__L0();
-				if (x < 0 || duk_unicode_is_line_terminator(x)) {
-					break;
-				}
-				DUK__ADVANCECHARS(lex_ctx, 1);
-			}
+			/* DUK__ADVANCECHARS(lex_ctx, 2) would be correct here, but not necessary */
+			duk__lexer_skip_to_endofline(lex_ctx);
 			goto restart;  /* line terminator will be handled on next round */
 		} else if (DUK__L1() == DUK_ASC_STAR) {
 			/*
@@ -78223,6 +80080,18 @@
 		advtok = DUK__ADVTOK(1, DUK_TOK_COMMA);
 		break;
 	case DUK_ASC_LANGLE:  /* '<' */
+#if defined(DUK_USE_HTML_COMMENTS)
+		if (DUK__L1() == DUK_ASC_EXCLAMATION && DUK__L2() == DUK_ASC_MINUS && DUK__L3() == DUK_ASC_MINUS) {
+			/*
+			 *  ES6: B.1.3, handle "<!--" SingleLineHTMLOpenComment
+			 */
+
+			/* DUK__ADVANCECHARS(lex_ctx, 4) would be correct here, but not necessary */
+			duk__lexer_skip_to_endofline(lex_ctx);
+			goto restart;  /* line terminator will be handled on next round */
+		}
+		else
+#endif  /* DUK_USE_HTML_COMMENTS */
 		if (DUK__L1() == DUK_ASC_LANGLE && DUK__L2() == DUK_ASC_EQUALS) {
 			advtok = DUK__ADVTOK(3, DUK_TOK_ALSHIFT_EQ);
 		} else if (DUK__L1() == DUK_ASC_EQUALS) {
@@ -78276,6 +80145,25 @@
 		}
 		break;
 	case DUK_ASC_MINUS:  /* '-' */
+#if defined(DUK_USE_HTML_COMMENTS)
+		if (got_lineterm && DUK__L1() == DUK_ASC_MINUS && DUK__L2() == DUK_ASC_RANGLE) {
+			/*
+			 *  ES6: B.1.3, handle "-->" SingleLineHTMLCloseComment
+			 *  Only allowed:
+			 *  - on new line
+			 *  - preceded only by whitespace
+			 *  - preceded by end of multiline comment and optional whitespace
+			 *
+			 * Since whitespace generates no tokens, and multiline comments
+			 * are treated as a line ending, consulting `got_lineterm` is
+			 * sufficient to test for these three options.
+			 */
+
+			/* DUK__ADVANCECHARS(lex_ctx, 3) would be correct here, but not necessary */
+			duk__lexer_skip_to_endofline(lex_ctx);
+			goto restart;  /* line terminator will be handled on next round */
+		} else
+#endif  /* DUK_USE_HTML_COMMENTS */
 		if (DUK__L1() == DUK_ASC_MINUS) {
 			advtok = DUK__ADVTOK(2, DUK_TOK_DECREMENT);
 		} else if (DUK__L1() == DUK_ASC_EQUALS) {
@@ -78987,6 +80875,8 @@
 			} else if (DUK__L2() == DUK_ASC_COLON) {
 				/* (?: */
 				advtok = DUK__ADVTOK(3, DUK_RETOK_ATOM_START_NONCAPTURE_GROUP);
+			} else {
+				goto fail_group;
 			}
 		} else {
 			/* ( */
@@ -79054,6 +80944,10 @@
 	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_ESCAPE);
 	return;
 
+ fail_group:
+	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_GROUP);
+	return;
+
 #if !defined(DUK_USE_ES6_REGEXP_SYNTAX)
  fail_invalid_char:
 	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_CHARACTER);
@@ -79227,12 +81121,24 @@
 				                            sizeof(duk_unicode_re_ranges_not_wordchar) / sizeof(duk_uint16_t));
 				ch = -1;
 			} else if (DUK__ISDIGIT(x)) {
-				/* DecimalEscape, only \0 is allowed, no leading zeroes are allowed */
+				/* DecimalEscape, only \0 is allowed, no leading
+				 * zeroes are allowed.
+				 *
+				 * ES2015 Annex B also allows (maximal match) legacy
+				 * octal escapes up to \377 and \8 and \9 are
+				 * accepted as literal '8' and '9', also in strict mode.
+				 */
+
+#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
+				ch = duk__lexer_parse_legacy_octal(lex_ctx, &adv, 0 /*reject_annex_b*/);
+				DUK_ASSERT(ch >= 0);  /* no rejections */
+#else
 				if (x == DUK_ASC_0 && !DUK__ISDIGIT(DUK__L2())) {
 					ch = 0x0000;
 				} else {
 					goto fail_escape;
 				}
+#endif
 #if defined(DUK_USE_ES6_REGEXP_SYNTAX)
 			} else if (x >= 0) {
 				/* IdentityEscape: ES2015 Annex B allows almost all
@@ -82477,34 +84383,34 @@
 		switch (c) {
 		case (duk_uint8_t) 'g': {
 			if (flags & DUK_RE_FLAG_GLOBAL) {
-				goto error;
+				goto flags_error;
 			}
 			flags |= DUK_RE_FLAG_GLOBAL;
 			break;
 		}
 		case (duk_uint8_t) 'i': {
 			if (flags & DUK_RE_FLAG_IGNORE_CASE) {
-				goto error;
+				goto flags_error;
 			}
 			flags |= DUK_RE_FLAG_IGNORE_CASE;
 			break;
 		}
 		case (duk_uint8_t) 'm': {
 			if (flags & DUK_RE_FLAG_MULTILINE) {
-				goto error;
+				goto flags_error;
 			}
 			flags |= DUK_RE_FLAG_MULTILINE;
 			break;
 		}
 		default: {
-			goto error;
+			goto flags_error;
 		}
 		}
 	}
 
 	return flags;
 
- error:
+ flags_error:
 	DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_REGEXP_FLAGS);
 	return 0;  /* never here */
 }
@@ -83575,6 +85481,7 @@
 		char_offset = (duk_uint32_t) 0;
 	}
 
+	DUK_ASSERT(char_offset <= DUK_HSTRING_GET_CHARLEN(h_input));
 	sp = re_ctx.input + duk_heap_strcache_offset_char2byte(thr, h_input, char_offset);
 
 	/*
@@ -90754,8 +92661,7 @@
 }
 
 /* Decode a one-bit flag, and if set, decode a value of 'bits', otherwise return
- * default value.  Return value is signed so that negative marker value can be
- * used by caller as a "not present" value.
+ * default value.
  */
 DUK_INTERNAL duk_uint32_t duk_bd_decode_flagged(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_uint32_t def_value) {
 	if (duk_bd_decode_flag(ctx)) {
@@ -90765,6 +92671,11 @@
 	}
 }
 
+/* Signed variant, allows negative marker value. */
+DUK_INTERNAL duk_int32_t duk_bd_decode_flagged_signed(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_int32_t def_value) {
+	return (duk_int32_t) duk_bd_decode_flagged(ctx, bits, (duk_uint32_t) def_value);
+}
+
 /* Shared varint encoding.  Match dukutil.py BitEncode.varuint(). */
 DUK_INTERNAL duk_uint32_t duk_bd_decode_varuint(duk_bitdecoder_ctx *ctx) {
 	duk_small_uint_t t;
@@ -90966,7 +92877,7 @@
 	curr_off = (duk_size_t) (bw_ctx->p - bw_ctx->p_base);
 	add_sz = (curr_off >> DUK_BW_SPARE_SHIFT) + DUK_BW_SPARE_ADD;
 	new_sz = curr_off + sz + add_sz;
-	if (new_sz < curr_off) {
+	if (DUK_UNLIKELY(new_sz < curr_off)) {
 		/* overflow */
 		DUK_ERROR_RANGE(thr, DUK_STR_BUFFER_TOO_LONG);
 		return NULL;  /* not reachable */
--- a/extern/duktape/duktape.h	Tue Nov 06 11:08:29 2018 +0100
+++ b/extern/duktape/duktape.h	Tue Sep 26 12:33:56 2017 +0200
@@ -1,13 +1,13 @@
 /*
- *  Duktape public API for Duktape 2.0.1.
+ *  Duktape public API for Duktape 2.1.0.
  *
- *  See the API reference for documentation on call semantics.
- *  The exposed API is inside the DUK_API_PUBLIC_H_INCLUDED
- *  include guard.  Other parts of the header are Duktape
- *  internal and related to platform/compiler/feature detection.
+ *  See the API reference for documentation on call semantics.  The exposed,
+ *  supported API is between the "BEGIN PUBLIC API" and "END PUBLIC API"
+ *  comments.  Other parts of the header are Duktape internal and related to
+ *  e.g. platform/compiler/feature detection.
  *
- *  Git commit 3f5e91704aff97c754f6ef5d44aedc5e529d1b16 (v2.0.1).
- *  Git branch v2.0-maintenance.
+ *  Git commit 1f1f51a4f9595ffe8def0e9ba45b20f14679393a (v2.1.0).
+ *  Git branch master.
  *
  *  See Duktape AUTHORS.rst and LICENSE.txt for copyright and
  *  licensing information.
@@ -87,6 +87,8 @@
  *  * Brett Vickers (https://github.com/beevik)
  *  * Dominik Okwieka (https://github.com/okitec)
  *  * Remko Tron\u00e7on (https://el-tramo.be)
+ *  * Romero Malaquias (rbsm@ic.ufal.br)
+ *  * Michael Drake <michael.drake@codethink.co.uk>
  *  
  *  Other contributions
  *  ===================
@@ -135,18 +137,46 @@
 
 #define DUK_SINGLE_FILE
 
+/*
+ *  BEGIN PUBLIC API
+ */
+
+/*
+ *  Version and Git commit identification
+ */
+
+/* Duktape version, (major * 10000) + (minor * 100) + patch.  Allows C code
+ * to #if (DUK_VERSION >= NNN) against Duktape API version.  The same value
+ * is also available to Ecmascript code in Duktape.version.  Unofficial
+ * development snapshots have 99 for patch level (e.g. 0.10.99 would be a
+ * development version after 0.10.0 but before the next official release).
+ */
+#define DUK_VERSION                       20100L
+
+/* Git commit, describe, and branch for Duktape build.  Useful for
+ * non-official snapshot builds so that application code can easily log
+ * which Duktape snapshot was used.  Not available in the Ecmascript
+ * environment.
+ */
+#define DUK_GIT_COMMIT                    "1f1f51a4f9595ffe8def0e9ba45b20f14679393a"
+#define DUK_GIT_DESCRIBE                  "v2.1.0"
+#define DUK_GIT_BRANCH                    "master"
+
 /* External duk_config.h provides platform/compiler/OS dependent
  * typedefs and macros, and DUK_USE_xxx config options so that
  * the rest of Duktape doesn't need to do any feature detection.
+ * DUK_VERSION is defined before including so that configuration
+ * snippets can react to it.
  */
 #include "duk_config.h"
 
 /*
- *  BEGIN PUBLIC API
+ *  Avoid C++ name mangling
  */
 
-#if !defined(DUK_API_PUBLIC_H_INCLUDED)
-#define DUK_API_PUBLIC_H_INCLUDED
+#if defined(__cplusplus)
+extern "C" {
+#endif
 
 /*
  *  Some defines forwarded from feature detection
@@ -239,23 +269,6 @@
  *  Constants
  */
 
-/* Duktape version, (major * 10000) + (minor * 100) + patch.  Allows C code
- * to #if (DUK_VERSION >= NNN) against Duktape API version.  The same value
- * is also available to Ecmascript code in Duktape.version.  Unofficial
- * development snapshots have 99 for patch level (e.g. 0.10.99 would be a
- * development version after 0.10.0 but before the next official release).
- */
-#define DUK_VERSION                       20001L
-
-/* Git commit, describe, and branch for Duktape build.  Useful for
- * non-official snapshot builds so that application code can easily log
- * which Duktape snapshot was used.  Not available in the Ecmascript
- * environment.
- */
-#define DUK_GIT_COMMIT                    "3f5e91704aff97c754f6ef5d44aedc5e529d1b16"
-#define DUK_GIT_DESCRIBE                  "v2.0.1"
-#define DUK_GIT_BRANCH                    "v2.0-maintenance"
-
 /* Duktape debug protocol version used by this build. */
 #define DUK_DEBUG_PROTOCOL_VERSION        2
 
@@ -326,11 +339,13 @@
 #define DUK_COMPILE_EVAL                  (1 << 3)    /* compile eval code (instead of global code) */
 #define DUK_COMPILE_FUNCTION              (1 << 4)    /* compile function code (instead of global code) */
 #define DUK_COMPILE_STRICT                (1 << 5)    /* use strict (outer) context for global, eval, or function code */
-#define DUK_COMPILE_SAFE                  (1 << 6)    /* (internal) catch compilation errors */
-#define DUK_COMPILE_NORESULT              (1 << 7)    /* (internal) omit eval result */
-#define DUK_COMPILE_NOSOURCE              (1 << 8)    /* (internal) no source string on stack */
-#define DUK_COMPILE_STRLEN                (1 << 9)    /* (internal) take strlen() of src_buffer (avoids double evaluation in macro) */
-#define DUK_COMPILE_NOFILENAME            (1 << 10)    /* (internal) no filename on stack */
+#define DUK_COMPILE_SHEBANG               (1 << 6)    /* allow shebang ('#! ...') comment on first line of source */
+#define DUK_COMPILE_SAFE                  (1 << 7)    /* (internal) catch compilation errors */
+#define DUK_COMPILE_NORESULT              (1 << 8)    /* (internal) omit eval result */
+#define DUK_COMPILE_NOSOURCE              (1 << 9)    /* (internal) no source string on stack */
+#define DUK_COMPILE_STRLEN                (1 << 10)   /* (internal) take strlen() of src_buffer (avoids double evaluation in macro) */
+#define DUK_COMPILE_NOFILENAME            (1 << 11)   /* (internal) no filename on stack */
+#define DUK_COMPILE_FUNCEXPR              (1 << 12)   /* (internal) source is a function expression (used for Function constructor) */
 
 /* Flags for duk_def_prop() and its variants */
 #define DUK_DEFPROP_WRITABLE              (1 << 0)    /* set writable (effective if DUK_DEFPROP_HAVE_WRITABLE set) */
@@ -437,9 +452,9 @@
 DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_fatal_raw(duk_context *ctx, const char *err_msg));
 #define duk_fatal(ctx,err_msg) \
 	(duk_fatal_raw((ctx), (err_msg)), (duk_ret_t) 0)
+DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...));
 
 #if defined(DUK_API_VARIADIC_MACROS)
-DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...));
 #define duk_error(ctx,err_code,...)  \
 	(duk_error_raw((ctx), (duk_errcode_t) (err_code), (const char *) (DUK_FILE_MACRO), (duk_int_t) (DUK_LINE_MACRO), __VA_ARGS__), (duk_ret_t) 0)
 #define duk_generic_error(ctx,...)  \
@@ -508,6 +523,7 @@
 #endif  /* DUK_API_VARIADIC_MACROS */
 
 DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_error_va_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap));
+
 #define duk_error_va(ctx,err_code,fmt,ap)  \
 	(duk_error_va_raw((ctx), (duk_errcode_t) (err_code), (const char *) (DUK_FILE_MACRO), (duk_int_t) (DUK_LINE_MACRO), (fmt), (ap)), (duk_ret_t) 0)
 #define duk_generic_error_va(ctx,fmt,ap)  \
@@ -655,19 +671,18 @@
 #define duk_push_external_buffer(ctx) \
 	((void) duk_push_buffer_raw((ctx), 0, DUK_BUF_FLAG_DYNAMIC | DUK_BUF_FLAG_EXTERNAL))
 
-#define DUK_BUFOBJ_CREATE_ARRBUF       (1 << 4)  /* internal flag: create backing ArrayBuffer; keep in one byte */
 #define DUK_BUFOBJ_ARRAYBUFFER         0
-#define DUK_BUFOBJ_NODEJS_BUFFER       (1 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_DATAVIEW            (2 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_INT8ARRAY           (3 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_UINT8ARRAY          (4 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_UINT8CLAMPEDARRAY   (5 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_INT16ARRAY          (6 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_UINT16ARRAY         (7 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_INT32ARRAY          (8 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_UINT32ARRAY         (9 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_FLOAT32ARRAY        (10 | DUK_BUFOBJ_CREATE_ARRBUF)
-#define DUK_BUFOBJ_FLOAT64ARRAY        (11 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_NODEJS_BUFFER       1
+#define DUK_BUFOBJ_DATAVIEW            2
+#define DUK_BUFOBJ_INT8ARRAY           3
+#define DUK_BUFOBJ_UINT8ARRAY          4
+#define DUK_BUFOBJ_UINT8CLAMPEDARRAY   5
+#define DUK_BUFOBJ_INT16ARRAY          6
+#define DUK_BUFOBJ_UINT16ARRAY         7
+#define DUK_BUFOBJ_INT32ARRAY          8
+#define DUK_BUFOBJ_UINT32ARRAY         9
+#define DUK_BUFOBJ_FLOAT32ARRAY        10
+#define DUK_BUFOBJ_FLOAT64ARRAY        11
 
 DUK_EXTERNAL_DECL void duk_push_buffer_object(duk_context *ctx, duk_idx_t idx_buffer, duk_size_t byte_offset, duk_size_t byte_length, duk_uint_t flags);
 
@@ -781,8 +796,43 @@
 DUK_EXTERNAL_DECL duk_c_function duk_get_c_function(duk_context *ctx, duk_idx_t idx);
 DUK_EXTERNAL_DECL duk_context *duk_get_context(duk_context *ctx, duk_idx_t idx);
 DUK_EXTERNAL_DECL void *duk_get_heapptr(duk_context *ctx, duk_idx_t idx);
-DUK_EXTERNAL_DECL duk_size_t duk_get_length(duk_context *ctx, duk_idx_t idx);
-DUK_EXTERNAL_DECL void duk_set_length(duk_context *ctx, duk_idx_t idx, duk_size_t len);
+
+/*
+ *  Get-with-explicit default operations: like get operations but with an
+ *  explicit default value.
+ */
+
+DUK_EXTERNAL_DECL duk_bool_t duk_get_boolean_default(duk_context *ctx, duk_idx_t idx, duk_bool_t def_value);
+DUK_EXTERNAL_DECL duk_double_t duk_get_number_default(duk_context *ctx, duk_idx_t idx, duk_double_t def_value);
+DUK_EXTERNAL_DECL duk_int_t duk_get_int_default(duk_context *ctx, duk_idx_t idx, duk_int_t def_value);
+DUK_EXTERNAL_DECL duk_uint_t duk_get_uint_default(duk_context *ctx, duk_idx_t idx, duk_uint_t def_value);
+DUK_EXTERNAL_DECL const char *duk_get_string_default(duk_context *ctx, duk_idx_t idx, const char *def_value);
+DUK_EXTERNAL_DECL const char *duk_get_lstring_default(duk_context *ctx, duk_idx_t idx, duk_size_t *out_len, const char *def_ptr, duk_size_t def_len);
+DUK_EXTERNAL_DECL void *duk_get_buffer_default(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_len);
+DUK_EXTERNAL_DECL void *duk_get_buffer_data_default(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_len);
+DUK_EXTERNAL_DECL void *duk_get_pointer_default(duk_context *ctx, duk_idx_t idx, void *def_value);
+DUK_EXTERNAL_DECL duk_c_function duk_get_c_function_default(duk_context *ctx, duk_idx_t idx, duk_c_function def_value);
+DUK_EXTERNAL_DECL duk_context *duk_get_context_default(duk_context *ctx, duk_idx_t idx, duk_context *def_value);
+DUK_EXTERNAL_DECL void *duk_get_heapptr_default(duk_context *ctx, duk_idx_t idx, void *def_value);
+
+/*
+ *  Opt operations: like require operations but with an explicit default value
+ *  when value is undefined or index is invalid, null and non-matching types
+ *  cause a TypeError.
+ */
+
+DUK_EXTERNAL_DECL duk_bool_t duk_opt_boolean(duk_context *ctx, duk_idx_t idx, duk_bool_t def_value);
+DUK_EXTERNAL_DECL duk_double_t duk_opt_number(duk_context *ctx, duk_idx_t idx, duk_double_t def_value);
+DUK_EXTERNAL_DECL duk_int_t duk_opt_int(duk_context *ctx, duk_idx_t idx, duk_int_t def_value);
+DUK_EXTERNAL_DECL duk_uint_t duk_opt_uint(duk_context *ctx, duk_idx_t idx, duk_uint_t def_value);
+DUK_EXTERNAL_DECL const char *duk_opt_string(duk_context *ctx, duk_idx_t idx, const char *def_ptr);
+DUK_EXTERNAL_DECL const char *duk_opt_lstring(duk_context *ctx, duk_idx_t idx, duk_size_t *out_len, const char *def_ptr, duk_size_t def_len);
+DUK_EXTERNAL_DECL void *duk_opt_buffer(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size);
+DUK_EXTERNAL_DECL void *duk_opt_buffer_data(duk_context *ctx, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size);
+DUK_EXTERNAL_DECL void *duk_opt_pointer(duk_context *ctx, duk_idx_t idx, void *def_value);
+DUK_EXTERNAL_DECL duk_c_function duk_opt_c_function(duk_context *ctx, duk_idx_t idx, duk_c_function def_value);
+DUK_EXTERNAL_DECL duk_context *duk_opt_context(duk_context *ctx, duk_idx_t idx, duk_context *def_value);
+DUK_EXTERNAL_DECL void *duk_opt_heapptr(duk_context *ctx, duk_idx_t idx, void *def_value);
 
 /*
  *  Require operations: no coercion, throw error if index or type
@@ -861,6 +911,17 @@
 	duk_safe_to_lstring((ctx), (idx), NULL)
 
 /*
+ *  Value length
+ */
+
+DUK_EXTERNAL_DECL duk_size_t duk_get_length(duk_context *ctx, duk_idx_t idx);
+DUK_EXTERNAL_DECL void duk_set_length(duk_context *ctx, duk_idx_t idx, duk_size_t len);
+#if 0
+/* duk_require_length()? */
+/* duk_opt_length()? */
+#endif
+
+/*
  *  Misc conversion
  */
 
@@ -1211,434 +1272,17 @@
 DUK_EXTERNAL_DECL const void * const duk_rom_compressed_pointers[];
 #endif
 
-#endif  /* DUK_API_PUBLIC_H_INCLUDED */
+/*
+ *  C++ name mangling
+ */
+
+#if defined(__cplusplus)
+/* end 'extern "C"' wrapper */
+}
+#endif
 
 /*
  *  END PUBLIC API
  */
 
-/*
- *  Union to access IEEE double memory representation, indexes for double
- *  memory representation, and some macros for double manipulation.
- *
- *  Also used by packed duk_tval.  Use a union for bit manipulation to
- *  minimize aliasing issues in practice.  The C99 standard does not
- *  guarantee that this should work, but it's a very widely supported
- *  practice for low level manipulation.
- *
- *  IEEE double format summary:
- *
- *    seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
- *       A        B        C        D        E        F        G        H
- *
- *    s       sign bit
- *    eee...  exponent field
- *    fff...  fraction
- *
- *  See http://en.wikipedia.org/wiki/Double_precision_floating-point_format.
- *
- *  NaNs are represented as exponent 0x7ff and mantissa != 0.  The NaN is a
- *  signaling NaN when the highest bit of the mantissa is zero, and a quiet
- *  NaN when the highest bit is set.
- *
- *  At least three memory layouts are relevant here:
- *
- *    A B C D E F G H    Big endian (e.g. 68k)           DUK_USE_DOUBLE_BE
- *    H G F E D C B A    Little endian (e.g. x86)        DUK_USE_DOUBLE_LE
- *    D C B A H G F E    Mixed/cross endian (e.g. ARM)   DUK_USE_DOUBLE_ME
- *
- *  ARM is a special case: ARM double values are in mixed/cross endian
- *  format while ARM duk_uint64_t values are in standard little endian
- *  format (H G F E D C B A).  When a double is read as a duk_uint64_t
- *  from memory, the register will contain the (logical) value
- *  E F G H A B C D.  This requires some special handling below.
- *
- *  Indexes of various types (8-bit, 16-bit, 32-bit) in memory relative to
- *  the logical (big endian) order:
- *
- *  byte order      duk_uint8_t    duk_uint16_t     duk_uint32_t
- *    BE             01234567         0123               01
- *    LE             76543210         3210               10
- *    ME (ARM)       32107654         1032               01
- *
- *  Some processors may alter NaN values in a floating point load+store.
- *  For instance, on X86 a FLD + FSTP may convert a signaling NaN to a
- *  quiet one.  This is catastrophic when NaN space is used in packed
- *  duk_tval values.  See: misc/clang_aliasing.c.
- */
-
-#if !defined(DUK_DBLUNION_H_INCLUDED)
-#define DUK_DBLUNION_H_INCLUDED
-
-/*
- *  Union for accessing double parts, also serves as packed duk_tval
- */
-
-union duk_double_union {
-	double d;
-	float f[2];
-#if defined(DUK_USE_64BIT_OPS)
-	duk_uint64_t ull[1];
-#endif
-	duk_uint32_t ui[2];
-	duk_uint16_t us[4];
-	duk_uint8_t uc[8];
-#if defined(DUK_USE_PACKED_TVAL)
-	void *vp[2];  /* used by packed duk_tval, assumes sizeof(void *) == 4 */
-#endif
-};
-
-typedef union duk_double_union duk_double_union;
-
-/*
- *  Indexes of various types with respect to big endian (logical) layout
- */
-
-#if defined(DUK_USE_DOUBLE_LE)
-#if defined(DUK_USE_64BIT_OPS)
-#define DUK_DBL_IDX_ULL0   0
-#endif
-#define DUK_DBL_IDX_UI0    1
-#define DUK_DBL_IDX_UI1    0
-#define DUK_DBL_IDX_US0    3
-#define DUK_DBL_IDX_US1    2
-#define DUK_DBL_IDX_US2    1
-#define DUK_DBL_IDX_US3    0
-#define DUK_DBL_IDX_UC0    7
-#define DUK_DBL_IDX_UC1    6
-#define DUK_DBL_IDX_UC2    5
-#define DUK_DBL_IDX_UC3    4
-#define DUK_DBL_IDX_UC4    3
-#define DUK_DBL_IDX_UC5    2
-#define DUK_DBL_IDX_UC6    1
-#define DUK_DBL_IDX_UC7    0
-#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
-#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
-#elif defined(DUK_USE_DOUBLE_BE)
-#if defined(DUK_USE_64BIT_OPS)
-#define DUK_DBL_IDX_ULL0   0
-#endif
-#define DUK_DBL_IDX_UI0    0
-#define DUK_DBL_IDX_UI1    1
-#define DUK_DBL_IDX_US0    0
-#define DUK_DBL_IDX_US1    1
-#define DUK_DBL_IDX_US2    2
-#define DUK_DBL_IDX_US3    3
-#define DUK_DBL_IDX_UC0    0
-#define DUK_DBL_IDX_UC1    1
-#define DUK_DBL_IDX_UC2    2
-#define DUK_DBL_IDX_UC3    3
-#define DUK_DBL_IDX_UC4    4
-#define DUK_DBL_IDX_UC5    5
-#define DUK_DBL_IDX_UC6    6
-#define DUK_DBL_IDX_UC7    7
-#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
-#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
-#elif defined(DUK_USE_DOUBLE_ME)
-#if defined(DUK_USE_64BIT_OPS)
-#define DUK_DBL_IDX_ULL0   0  /* not directly applicable, byte order differs from a double */
-#endif
-#define DUK_DBL_IDX_UI0    0
-#define DUK_DBL_IDX_UI1    1
-#define DUK_DBL_IDX_US0    1
-#define DUK_DBL_IDX_US1    0
-#define DUK_DBL_IDX_US2    3
-#define DUK_DBL_IDX_US3    2
-#define DUK_DBL_IDX_UC0    3
-#define DUK_DBL_IDX_UC1    2
-#define DUK_DBL_IDX_UC2    1
-#define DUK_DBL_IDX_UC3    0
-#define DUK_DBL_IDX_UC4    7
-#define DUK_DBL_IDX_UC5    6
-#define DUK_DBL_IDX_UC6    5
-#define DUK_DBL_IDX_UC7    4
-#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
-#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
-#else
-#error internal error
-#endif
-
-/*
- *  Helper macros for reading/writing memory representation parts, used
- *  by duk_numconv.c and duk_tval.h.
- */
-
-#define DUK_DBLUNION_SET_DOUBLE(u,v)  do {  \
-		(u)->d = (v); \
-	} while (0)
-
-#define DUK_DBLUNION_SET_HIGH32(u,v)  do {  \
-		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \
-	} while (0)
-
-#if defined(DUK_USE_64BIT_OPS)
-#if defined(DUK_USE_DOUBLE_ME)
-#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
-		(u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \
-	} while (0)
-#else
-#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
-		(u)->ull[DUK_DBL_IDX_ULL0] = ((duk_uint64_t) (v)) << 32; \
-	} while (0)
-#endif
-#else  /* DUK_USE_64BIT_OPS */
-#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
-		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \
-		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0; \
-	} while (0)
-#endif  /* DUK_USE_64BIT_OPS */
-
-#define DUK_DBLUNION_SET_LOW32(u,v)  do {  \
-		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \
-	} while (0)
-
-#define DUK_DBLUNION_GET_DOUBLE(u)  ((u)->d)
-#define DUK_DBLUNION_GET_HIGH32(u)  ((u)->ui[DUK_DBL_IDX_UI0])
-#define DUK_DBLUNION_GET_LOW32(u)   ((u)->ui[DUK_DBL_IDX_UI1])
-
-#if defined(DUK_USE_64BIT_OPS)
-#if defined(DUK_USE_DOUBLE_ME)
-#define DUK_DBLUNION_SET_UINT64(u,v)  do { \
-		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) ((v) >> 32); \
-		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \
-	} while (0)
-#define DUK_DBLUNION_GET_UINT64(u) \
-	((((duk_uint64_t) (u)->ui[DUK_DBL_IDX_UI0]) << 32) | \
-	 ((duk_uint64_t) (u)->ui[DUK_DBL_IDX_UI1]))
-#else
-#define DUK_DBLUNION_SET_UINT64(u,v)  do { \
-		(u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \
-	} while (0)
-#define DUK_DBLUNION_GET_UINT64(u)  ((u)->ull[DUK_DBL_IDX_ULL0])
-#endif
-#define DUK_DBLUNION_SET_INT64(u,v) DUK_DBLUNION_SET_UINT64((u), (duk_uint64_t) (v))
-#define DUK_DBLUNION_GET_INT64(u)   ((duk_int64_t) DUK_DBLUNION_GET_UINT64((u)))
-#endif  /* DUK_USE_64BIT_OPS */
-
-/*
- *  Double NaN manipulation macros related to NaN normalization needed when
- *  using the packed duk_tval representation.  NaN normalization is necessary
- *  to keep double values compatible with the duk_tval format.
- *
- *  When packed duk_tval is used, the NaN space is used to store pointers
- *  and other tagged values in addition to NaNs.  Actual NaNs are normalized
- *  to a specific quiet NaN.  The macros below are used by the implementation
- *  to check and normalize NaN values when they might be created.  The macros
- *  are essentially NOPs when the non-packed duk_tval representation is used.
- *
- *  A FULL check is exact and checks all bits.  A NOTFULL check is used by
- *  the packed duk_tval and works correctly for all NaNs except those that
- *  begin with 0x7ff0.  Since the 'normalized NaN' values used with packed
- *  duk_tval begin with 0x7ff8, the partial check is reliable when packed
- *  duk_tval is used.  The 0x7ff8 prefix means the normalized NaN will be a
- *  quiet NaN regardless of its remaining lower bits.
- *
- *  The ME variant below is specifically for ARM byte order, which has the
- *  feature that while doubles have a mixed byte order (32107654), unsigned
- *  long long values has a little endian byte order (76543210).  When writing
- *  a logical double value through a ULL pointer, the 32-bit words need to be
- *  swapped; hence the #if defined()s below for ULL writes with DUK_USE_DOUBLE_ME.
- *  This is not full ARM support but suffices for some environments.
- */
-
-#if defined(DUK_USE_64BIT_OPS)
-#if defined(DUK_USE_DOUBLE_ME)
-/* Macros for 64-bit ops + mixed endian doubles. */
-#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
-		(u)->ull[DUK_DBL_IDX_ULL0] = 0x000000007ff80000ULL; \
-	} while (0)
-#define DUK__DBLUNION_IS_NAN_FULL(u) \
-	((((u)->ull[DUK_DBL_IDX_ULL0] & 0x000000007ff00000ULL) == 0x000000007ff00000ULL) && \
-	 ((((u)->ull[DUK_DBL_IDX_ULL0]) & 0xffffffff000fffffULL) != 0))
-#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x000000007ff80000ULL)
-#define DUK__DBLUNION_IS_ANYINF(u) \
-	(((u)->ull[DUK_DBL_IDX_ULL0] & 0xffffffff7fffffffULL) == 0x000000007ff00000ULL)
-#define DUK__DBLUNION_IS_POSINF(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x000000007ff00000ULL)
-#define DUK__DBLUNION_IS_NEGINF(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x00000000fff00000ULL)
-#define DUK__DBLUNION_IS_ANYZERO(u) \
-	(((u)->ull[DUK_DBL_IDX_ULL0] & 0xffffffff7fffffffULL) == 0x0000000000000000ULL)
-#define DUK__DBLUNION_IS_POSZERO(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x0000000000000000ULL)
-#define DUK__DBLUNION_IS_NEGZERO(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x0000000080000000ULL)
-#else
-/* Macros for 64-bit ops + big/little endian doubles. */
-#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
-		(u)->ull[DUK_DBL_IDX_ULL0] = 0x7ff8000000000000ULL; \
-	} while (0)
-#define DUK__DBLUNION_IS_NAN_FULL(u) \
-	((((u)->ull[DUK_DBL_IDX_ULL0] & 0x7ff0000000000000ULL) == 0x7ff0000000000000UL) && \
-	 ((((u)->ull[DUK_DBL_IDX_ULL0]) & 0x000fffffffffffffULL) != 0))
-#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x7ff8000000000000ULL)
-#define DUK__DBLUNION_IS_ANYINF(u) \
-	(((u)->ull[DUK_DBL_IDX_ULL0] & 0x7fffffffffffffffULL) == 0x7ff0000000000000ULL)
-#define DUK__DBLUNION_IS_POSINF(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x7ff0000000000000ULL)
-#define DUK__DBLUNION_IS_NEGINF(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0xfff0000000000000ULL)
-#define DUK__DBLUNION_IS_ANYZERO(u) \
-	(((u)->ull[DUK_DBL_IDX_ULL0] & 0x7fffffffffffffffULL) == 0x0000000000000000ULL)
-#define DUK__DBLUNION_IS_POSZERO(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x0000000000000000ULL)
-#define DUK__DBLUNION_IS_NEGZERO(u) \
-	((u)->ull[DUK_DBL_IDX_ULL0] == 0x8000000000000000ULL)
-#endif
-#else  /* DUK_USE_64BIT_OPS */
-/* Macros for no 64-bit ops, any endianness. */
-#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
-		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) 0x7ff80000UL; \
-		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0x00000000UL; \
-	} while (0)
-#define DUK__DBLUNION_IS_NAN_FULL(u) \
-	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7ff00000UL) == 0x7ff00000UL) && \
-	 (((u)->ui[DUK_DBL_IDX_UI0] & 0x000fffffUL) != 0 || \
-          (u)->ui[DUK_DBL_IDX_UI1] != 0))
-#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
-	(((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff80000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#define DUK__DBLUNION_IS_ANYINF(u) \
-	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7fffffffUL) == 0x7ff00000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#define DUK__DBLUNION_IS_POSINF(u) \
-	(((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff00000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#define DUK__DBLUNION_IS_NEGINF(u) \
-	(((u)->ui[DUK_DBL_IDX_UI0] == 0xfff00000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#define DUK__DBLUNION_IS_ANYZERO(u) \
-	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7fffffffUL) == 0x00000000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#define DUK__DBLUNION_IS_POSZERO(u) \
-	(((u)->ui[DUK_DBL_IDX_UI0] == 0x00000000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#define DUK__DBLUNION_IS_NEGZERO(u) \
-	(((u)->ui[DUK_DBL_IDX_UI0] == 0x80000000UL) && \
-	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
-#endif  /* DUK_USE_64BIT_OPS */
-
-#define DUK__DBLUNION_SET_NAN_NOTFULL(u)  do { \
-		(u)->us[DUK_DBL_IDX_US0] = 0x7ff8UL; \
-	} while (0)
-
-#define DUK__DBLUNION_IS_NAN_NOTFULL(u) \
-	/* E == 0x7ff, topmost four bits of F != 0 => assume NaN */ \
-	((((u)->us[DUK_DBL_IDX_US0] & 0x7ff0UL) == 0x7ff0UL) && \
-	 (((u)->us[DUK_DBL_IDX_US0] & 0x000fUL) != 0x0000UL))
-
-#define DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL(u) \
-	/* E == 0x7ff, F == 8 => normalized NaN */ \
-	((u)->us[DUK_DBL_IDX_US0] == 0x7ff8UL)
-
-#define DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL(u)  do { \
-		if (DUK__DBLUNION_IS_NAN_FULL((u))) { \
-			DUK__DBLUNION_SET_NAN_FULL((u)); \
-		} \
-	} while (0)
-
-#define DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL(u)  do { \
-		if (DUK__DBLUNION_IS_NAN_NOTFULL((u))) { \
-			DUK__DBLUNION_SET_NAN_NOTFULL((u)); \
-		} \
-	} while (0)
-
-/* Concrete macros for NaN handling used by the implementation internals.
- * Chosen so that they match the duk_tval representation: with a packed
- * duk_tval, ensure NaNs are properly normalized; with a non-packed duk_tval
- * these are essentially NOPs.
- */
-
-#if defined(DUK_USE_PACKED_TVAL)
-#if defined(DUK_USE_FULL_TVAL)
-#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL((u))
-#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_FULL((u))
-#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NORMALIZED_NAN_FULL((u))
-#define DUK_DBLUNION_SET_NAN(d)              DUK__DBLUNION_SET_NAN_FULL((d))
-#else
-#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL((u))
-#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_NOTFULL((u))
-#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL((u))
-#define DUK_DBLUNION_SET_NAN(d)              DUK__DBLUNION_SET_NAN_NOTFULL((d))
-#endif
-#define DUK_DBLUNION_IS_NORMALIZED(u) \
-	(!DUK_DBLUNION_IS_NAN((u)) ||  /* either not a NaN */ \
-	 DUK_DBLUNION_IS_NORMALIZED_NAN((u)))  /* or is a normalized NaN */
-#else  /* DUK_USE_PACKED_TVAL */
-#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  /* nop: no need to normalize */
-#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_FULL((u))  /* (DUK_ISNAN((u)->d)) */
-#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NAN_FULL((u))  /* (DUK_ISNAN((u)->d)) */
-#define DUK_DBLUNION_IS_NORMALIZED(u)        1  /* all doubles are considered normalized */
-#define DUK_DBLUNION_SET_NAN(u)  do { \
-		/* in non-packed representation we don't care about which NaN is used */ \
-		(u)->d = DUK_DOUBLE_NAN; \
-	} while (0)
-#endif  /* DUK_USE_PACKED_TVAL */
-
-#define DUK_DBLUNION_IS_ANYINF(u) DUK__DBLUNION_IS_ANYINF((u))
-#define DUK_DBLUNION_IS_POSINF(u) DUK__DBLUNION_IS_POSINF((u))
-#define DUK_DBLUNION_IS_NEGINF(u) DUK__DBLUNION_IS_NEGINF((u))
-
-#define DUK_DBLUNION_IS_ANYZERO(u) DUK__DBLUNION_IS_ANYZERO((u))
-#define DUK_DBLUNION_IS_POSZERO(u) DUK__DBLUNION_IS_POSZERO((u))
-#define DUK_DBLUNION_IS_NEGZERO(u) DUK__DBLUNION_IS_NEGZERO((u))
-
-/* XXX: native 64-bit byteswaps when available */
-
-/* 64-bit byteswap, same operation independent of target endianness. */
-#define DUK_DBLUNION_BSWAP64(u) do { \
-		duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \
-		duk__bswaptmp1 = (u)->ui[0]; \
-		duk__bswaptmp2 = (u)->ui[1]; \
-		duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \
-		duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \
-		(u)->ui[0] = duk__bswaptmp2; \
-		(u)->ui[1] = duk__bswaptmp1; \
-	} while (0)
-
-/* Byteswap an IEEE double in the duk_double_union from host to network
- * order.  For a big endian target this is a no-op.
- */
-#if defined(DUK_USE_DOUBLE_LE)
-#define DUK_DBLUNION_DOUBLE_HTON(u) do { \
-		duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \
-		duk__bswaptmp1 = (u)->ui[0]; \
-		duk__bswaptmp2 = (u)->ui[1]; \
-		duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \
-		duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \
-		(u)->ui[0] = duk__bswaptmp2; \
-		(u)->ui[1] = duk__bswaptmp1; \
-	} while (0)
-#elif defined(DUK_USE_DOUBLE_ME)
-#define DUK_DBLUNION_DOUBLE_HTON(u) do { \
-		duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \
-		duk__bswaptmp1 = (u)->ui[0]; \
-		duk__bswaptmp2 = (u)->ui[1]; \
-		duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \
-		duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \
-		(u)->ui[0] = duk__bswaptmp1; \
-		(u)->ui[1] = duk__bswaptmp2; \
-	} while (0)
-#elif defined(DUK_USE_DOUBLE_BE)
-#define DUK_DBLUNION_DOUBLE_HTON(u) do { } while (0)
-#else
-#error internal error, double endianness insane
-#endif
-
-/* Reverse operation is the same. */
-#define DUK_DBLUNION_DOUBLE_NTOH(u) DUK_DBLUNION_DOUBLE_HTON((u))
-
-/* Some sign bit helpers. */
-#if defined(DUK_USE_64BIT_OPS)
-#define DUK_DBLUNION_HAS_SIGNBIT(u) (((u)->ull[DUK_DBL_IDX_ULL0] & 0x8000000000000000ULL) != 0)
-#define DUK_DBLUNION_GET_SIGNBIT(u) (((u)->ull[DUK_DBL_IDX_ULL0] >> 63U))
-#else
-#define DUK_DBLUNION_HAS_SIGNBIT(u) (((u)->ui[DUK_DBL_IDX_UI0] & 0x80000000UL) != 0)
-#define DUK_DBLUNION_GET_SIGNBIT(u) (((u)->ui[DUK_DBL_IDX_UI0] >> 31U))
-#endif
-
-#endif  /* DUK_DBLUNION_H_INCLUDED */
-
 #endif  /* DUKTAPE_H_INCLUDED */
--- a/irccd/main.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/irccd/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -320,6 +320,12 @@
     instance->commands().add(std::make_unique<command::ServerPartCommand>());
     instance->commands().add(std::make_unique<command::ServerReconnectCommand>());
     instance->commands().add(std::make_unique<command::ServerTopicCommand>());
+    instance->commands().add(std::make_unique<command::RuleAddCommand>());
+    instance->commands().add(std::make_unique<command::RuleEditCommand>());
+    instance->commands().add(std::make_unique<command::RuleInfoCommand>());
+    instance->commands().add(std::make_unique<command::RuleListCommand>());
+    instance->commands().add(std::make_unique<command::RuleMoveCommand>());
+    instance->commands().add(std::make_unique<command::RuleRemoveCommand>());
 
     // Load Javascript API and plugin loader.
 #if defined(WITH_JS)
--- a/irccdctl/cli.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/irccdctl/cli.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -16,7 +16,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <cassert>
 #include <iostream>
+#include <sstream>
 
 #include <json.hpp>
 
@@ -27,6 +29,8 @@
 #include "options.hpp"
 #include "util.hpp"
 
+using namespace std::string_literals;
+
 namespace irccd {
 
 /*
@@ -840,6 +844,363 @@
 }
 
 /*
+ * RuleAddCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleAddCli::RuleAddCli()
+    : Cli("rule-add",
+          "add a new rule",
+          "rule-add [options] accept|drop",
+          "Add a new rule to irccd.\n\n"
+          "If no index is specified, the rule is added to the end.\n\n"
+          "Available options:\n"
+          "  -c, --add-channel\t\tmatch a channel\n"
+          "  -e, --add-event\t\tmatch an event\n"
+          "  -i, --index\t\t\trule position\n"
+          "  -p, --add-plugin\t\tmatch a plugin\n"
+          "  -s, --add-server\t\tmatch a server\n\n"
+          "Example:\n"
+          "\tirccdctl rule-add -p hangman drop\n"
+          "\tirccdctl rule-add -s localhost -c #games -p hangman accept")
+{
+}
+
+void RuleAddCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    static const option::Options options{
+        { "-c",             true },
+        { "--add-channel",  true },
+        { "-e",             true },
+        { "--add-event",    true },
+        { "-i",             true },
+        { "--index",        true },
+        { "-p",             true },
+        { "--add-plugin",   true },
+        { "-s",             true },
+        { "--add-server",   true }
+    };
+
+    auto copy = args;
+    auto result = option::read(copy, options);
+
+    if (copy.size() < 1)
+        throw std::invalid_argument("rule-add requires at least 1 argument");
+
+    auto json = nlohmann::json::object({
+        { "command",    "rule-add"              },
+        { "channels",   nlohmann::json::array() },
+        { "events",     nlohmann::json::array() },
+        { "plugins",    nlohmann::json::array() },
+        { "servers",    nlohmann::json::array() }
+    });
+
+    // All sets.
+    for (const auto& pair : result) {
+        if (pair.first == "-c" || pair.first == "--add-channel")
+            json["channels"].push_back(pair.second);
+        if (pair.first == "-e" || pair.first == "--add-event")
+            json["events"].push_back(pair.second);
+        if (pair.first == "-p" || pair.first == "--add-plugin")
+            json["plugins"].push_back(pair.second);
+        if (pair.first == "-s" || pair.first == "--add-server")
+            json["servers"].push_back(pair.second);
+    }
+
+    // Index.
+    if (result.count("-i") > 0)
+        json["index"] = util::toNumber<unsigned>(result.find("-i")->second);
+    if (result.count("--index") > 0)
+        json["index"] = util::toNumber<unsigned>(result.find("--index")->second);
+
+    // And action.
+    if (copy[0] != "accept" && copy[0] != "drop")
+        throw std::runtime_error("invalid action '"s + copy[0] + "'");
+
+    json["action"] = copy[0];
+
+    check(request(irccdctl, json));
+}
+
+/*
+ * RuleEditCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleEditCli::RuleEditCli()
+    : Cli("rule-edit",
+          "edit an existing rule",
+          "rule-edit [options] index",
+          "Edit an existing rule in irccd.\n\n"
+          "All options can be specified multiple times.\n\n"
+          "Available options:\n"
+          "  -a, --action\t\t\tset action\n"
+          "  -c, --add-channel\t\tmatch a channel\n"
+          "  -C, --remove-channel\t\tremove a channel\n"
+          "  -e, --add-event\t\tmatch an event\n"
+          "  -E, --remove-event\t\tremove an event\n"
+          "  -p, --add-plugin\t\tmatch a plugin\n"
+          "  -P, --add-plugin\t\tremove a plugin\n"
+          "  -s, --add-server\t\tmatch a server\n"
+          "  -S, --remove-server\t\tremove a server\n\n"
+          "Example:\n"
+          "\tirccdctl rule-edit -p hangman 0\n"
+          "\tirccdctl rule-edit -S localhost -c #games -p hangman 1")
+{
+}
+
+void RuleEditCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    static const option::Options options{
+        { "-a",                 true },
+        { "--action",           true },
+        { "-c",                 true },
+        { "--add-channel",      true },
+        { "-C",                 true },
+        { "--remove-channel",   true },
+        { "-e",                 true },
+        { "--add-event",        true },
+        { "-E",                 true },
+        { "--remove-event",     true },
+        { "-p",                 true },
+        { "--add-plugin",       true },
+        { "-P",                 true },
+        { "--remove-plugin",    true },
+        { "-s",                 true },
+        { "--add-server",       true },
+        { "-S",                 true },
+        { "--remove-server",    true },
+    };
+
+    auto copy = args;
+    auto result = option::read(copy, options);
+
+    if (copy.size() < 1)
+        throw std::invalid_argument("rule-edit requires at least 1 argument");
+
+    auto json = nlohmann::json::object({
+        { "command",    "rule-edit"             },
+        { "channels",   nlohmann::json::array() },
+        { "events",     nlohmann::json::array() },
+        { "plugins",    nlohmann::json::array() },
+        { "servers",    nlohmann::json::array() }
+    });
+
+    for (const auto& pair : result) {
+        // Action.
+        if (pair.first == "-a" || pair.first == "--action")
+            json["action"] = pair.second;
+
+        // Additions.
+        if (pair.first == "-c" || pair.first == "--add-channel")
+            json["add-channels"].push_back(pair.second);
+        if (pair.first == "-e" || pair.first == "--add-event")
+            json["add-events"].push_back(pair.second);
+        if (pair.first == "-p" || pair.first == "--add-plugin")
+            json["add-plugins"].push_back(pair.second);
+        if (pair.first == "-s" || pair.first == "--add-server")
+            json["add-servers"].push_back(pair.second);
+
+        // Removals.
+        if (pair.first == "-C" || pair.first == "--remove-channel")
+            json["remove-channels"].push_back(pair.second);
+        if (pair.first == "-E" || pair.first == "--remove-event")
+            json["remove-events"].push_back(pair.second);
+        if (pair.first == "-P" || pair.first == "--remove-plugin")
+            json["remove-plugins"].push_back(pair.second);
+        if (pair.first == "-S" || pair.first == "--remove-server")
+            json["remove-servers"].push_back(pair.second);
+    }
+
+    // Index.
+    json["index"] = util::toNumber<unsigned>(copy[0]);
+
+    check(request(irccdctl, json));
+}
+
+/*
+ * RuleListCli.
+ * ------------------------------------------------------------------
+ */
+
+namespace {
+
+void showRule(const nlohmann::json& json, int index)
+{
+    assert(json.is_object());
+
+    auto unjoin = [] (auto array) {
+        std::ostringstream oss;
+
+        for (auto it = array.begin(); it != array.end(); ++it) {
+            if (!it->is_string())
+                continue;
+
+            oss << it->template get<std::string>() << " ";
+        }
+
+        return oss.str();
+    };
+    auto unstr = [] (auto action) {
+        if (action.is_string() && action == "accept")
+            return "accept";
+        else
+            return "drop";
+    };
+
+    std::cout << "rule:        " << index << std::endl;
+    std::cout << "servers:     " << unjoin(json["servers"]) << std::endl;
+    std::cout << "channels:    " << unjoin(json["channels"]) << std::endl;
+    std::cout << "plugins:     " << unjoin(json["plugins"]) << std::endl;
+    std::cout << "events:      " << unjoin(json["events"]) << std::endl;
+    std::cout << "action:      " << unstr(json["action"]) << std::endl;
+    std::cout << std::endl;
+}
+
+} // !namespace
+
+RuleListCli::RuleListCli()
+    : Cli("rule-list",
+          "list all rules",
+          "rule-list",
+          "List all rules.\n\n"
+          "Example:\n"
+          "\tirccdctl rule-list")
+{
+}
+
+void RuleListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &)
+{
+    auto response = request(irccdctl);
+    auto pos = 0;
+
+    check(response);
+
+    for (const auto &obj : response["list"]) {
+        if (!obj.is_object())
+            continue;
+
+        showRule(obj, pos++);
+    }
+}
+
+/*
+ * RuleInfoCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleInfoCli::RuleInfoCli()
+    : Cli("rule-info",
+          "show a rule",
+          "rule-info index",
+          "Show a rule.\n\n"
+          "Example:\n"
+          "\tirccdctl rule-info 0\n"
+          "\tirccdctl rule-info 1")
+{
+}
+
+void RuleInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("rule-info requires 1 argument");
+
+    int index = 0;
+
+    try {
+        index = std::stoi(args[0]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number '" + args[0] + "'");
+    }
+
+    auto result = request(irccdctl, {
+        { "command",    "rule-info" },
+        { "index",      index       }
+    });
+
+    check(result);
+    showRule(result, 0);
+}
+
+/*
+ * RuleRemoveCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleRemoveCli::RuleRemoveCli()
+    : Cli("rule-remove",
+          "remove a rule",
+          "rule-remove index",
+          "Remove an existing rule.\n\n"
+          "Example:\n"
+          "\tirccdctl rule-remove 0\n"
+          "\tirccdctl rule-remove 1")
+{
+}
+
+void RuleRemoveCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("rule-remove requires 1 argument");
+
+    int index = 0;
+
+    try {
+        index = std::stoi(args[0]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number '" + args[0] + "'");
+    }
+
+    auto result = request(irccdctl, {
+        { "command",    "rule-remove"   },
+        { "index",      index           }
+    });
+
+    check(result);
+}
+
+/*
+ * RuleMoveCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleMoveCli::RuleMoveCli()
+    : Cli("rule-move",
+          "move a rule to a new position",
+          "rule-move source destination",
+          "Move a rule from the given source at the specified destination index.\n\n"
+          "The rule will replace the existing one at the given destination moving\ndown every "
+          "other rules. If destination is greater or equal the number of rules,\nthe rule "
+          "is moved to the end.\n\n"
+          "Example:\n"
+          "\tirccdctl rule-move 0 5\n"
+          "\tirccdctl rule-move 4 3")
+{
+}
+
+void RuleMoveCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    if (args.size() < 2)
+        throw std::invalid_argument("rule-move requires 2 arguments");
+
+    int from = 0;
+    int to = 0;
+
+    try {
+        from = std::stoi(args[0]);
+        to = std::stoi(args[1]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number");
+    }
+
+    check(request(irccdctl, {
+        { "command",    "rule-move" },
+        { "from",       from        },
+        { "to",         to          }
+    }));
+}
+
+/*
  * WatchCli.
  * ------------------------------------------------------------------
  */
--- a/irccdctl/cli.hpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/irccdctl/cli.hpp	Tue Sep 26 12:33:56 2017 +0200
@@ -586,6 +586,132 @@
 };
 
 /*
+ * RuleAddCli.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-add.
+ */
+class RuleAddCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleAddCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
+ * RuleEditCli.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-edit.
+ */
+class RuleEditCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleEditCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
+ * RuleListCli.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-list.
+ */
+class RuleListCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleListCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
+ * RuleInfoCli
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-info.
+ */
+class RuleInfoCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleInfoCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
+ * RuleRemoveCli
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-remove.
+ */
+class RuleRemoveCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleRemoveCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
+ * RuleMoveCli
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-move.
+ */
+class RuleMoveCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleMoveCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
  * WatchCli.
  * ------------------------------------------------------------------
  */
--- a/irccdctl/main.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/irccdctl/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -522,6 +522,12 @@
     commands.push_back(std::make_unique<cli::ServerPartCli>());
     commands.push_back(std::make_unique<cli::ServerReconnectCli>());
     commands.push_back(std::make_unique<cli::ServerTopicCli>());
+    commands.push_back(std::make_unique<cli::RuleAddCli>());
+    commands.push_back(std::make_unique<cli::RuleEditCli>());
+    commands.push_back(std::make_unique<cli::RuleListCli>());
+    commands.push_back(std::make_unique<cli::RuleInfoCli>());
+    commands.push_back(std::make_unique<cli::RuleMoveCli>());
+    commands.push_back(std::make_unique<cli::RuleRemoveCli>());
     commands.push_back(std::make_unique<cli::WatchCli>());
 }
 
--- a/libcommon/irccd/util.hpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libcommon/irccd/util.hpp	Tue Sep 26 12:33:56 2017 +0200
@@ -399,7 +399,17 @@
  */
 inline std::int64_t requireInt(const nlohmann::json &json, const std::string &key)
 {
-    return require(json, key, nlohmann::json::value_t::number_integer);
+    auto it = json.find(key);
+
+    if (it == json.end())
+        throw std::runtime_error(fmt::format("missing '{}' property", key));
+    if (it->is_number_integer())
+        return it->get<int>();
+    if (it->is_number_unsigned() && it->get<unsigned>() <= INT_MAX)
+        return static_cast<int>(it->get<unsigned>());
+
+    throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})",
+        key, it->type_name(), nlohmann::json(0).type_name()));
 }
 
 /**
@@ -412,7 +422,17 @@
  */
 inline std::uint64_t requireUint(const nlohmann::json &json, const std::string &key)
 {
-    return require(json, key, nlohmann::json::value_t::number_unsigned);
+    auto it = json.find(key);
+
+    if (it == json.end())
+        throw std::runtime_error(fmt::format("missing '{}' property", key));
+    if (it->is_number_unsigned())
+        return it->get<unsigned>();
+    if (it->is_number_integer() && it->get<int>() >= 0)
+        return static_cast<unsigned>(it->get<int>());
+
+    throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})",
+        key, it->type_name(), nlohmann::json(0U).type_name()));
 }
 
 /**
--- a/libirccd/irccd/command.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/command.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -22,6 +22,8 @@
 #include "transport.hpp"
 #include "util.hpp"
 
+using namespace std::string_literals;
+
 namespace irccd {
 
 namespace command {
@@ -70,6 +72,70 @@
     });
 }
 
+nlohmann::json toJson(const Rule &rule)
+{
+    auto join = [] (const auto &set) {
+        auto array = nlohmann::json::array();
+
+        for (const auto &entry : set)
+            array.push_back(entry);
+
+        return array;
+    };
+    auto str = [] (auto action) {
+        switch (action) {
+        case RuleAction::Accept:
+            return "accept";
+        default:
+            return "drop";
+        }
+    };
+
+    return {
+        { "servers",    join(rule.servers())    },
+        { "channels",   join(rule.channels())   },
+        { "plugins",    join(rule.plugins())    },
+        { "events",     join(rule.events())     },
+        { "action",     str(rule.action())      }
+    };
+}
+
+Rule fromJson(const nlohmann::json &json)
+{
+    auto toset = [] (auto object, auto name) -> RuleSet {
+        RuleSet result;
+
+        for (const auto &s : object[name])
+            if (s.is_string())
+                result.insert(s.template get<std::string>());
+
+        return result;
+    };
+    auto toaction = [] (auto object, auto name) -> RuleAction {
+        auto v = object[name];
+
+        if (!v.is_string())
+            throw std::runtime_error("no action given");
+
+        auto s = v.template get<std::string>();
+        if (s == "accept")
+            return RuleAction::Accept;
+        if (s == "drop")
+            return RuleAction::Drop;
+
+        throw std::runtime_error("unknown action '"s + s + "' given");
+    };
+
+    return {
+        toset(json, "servers"),
+        toset(json, "channels"),
+        toset(json, "origins"),
+        toset(json, "plugins"),
+        toset(json, "events"),
+        toaction(json, "action")
+    };
+}
+
 } // !namespace
 
 PluginConfigCommand::PluginConfigCommand()
@@ -421,6 +487,172 @@
     client.success("server-topic");
 }
 
+RuleEditCommand::RuleEditCommand()
+    : Command("rule-edit")
+{
+}
+
+void RuleEditCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    static const auto updateset = [] (auto &set, auto args, const auto &key) {
+        for (const auto &v : args["remove-"s + key]) {
+            if (v.is_string())
+                set.erase(v.template get<std::string>());
+        }
+        for (const auto &v : args["add-"s + key]) {
+            if (v.is_string())
+                set.insert(v.template get<std::string>());
+        }
+    };
+
+    // Create a copy to avoid incomplete edition in case of errors.
+    auto index = util::json::requireUint(args, "index");
+    auto rule = irccd.rules().require(index);
+
+    updateset(rule.channels(), args, "channels");
+    updateset(rule.events(), args, "events");
+    updateset(rule.plugins(), args, "plugins");
+    updateset(rule.servers(), args, "servers");
+
+    auto action = args.find("action");
+
+    if (action != args.end()) {
+        if (!action->is_string()) {
+            client.error("rule-edit", "action must be \"accept\" or \"drop\"");
+            return;
+        }
+
+        if (action->get<std::string>() == "accept")
+            rule.setAction(RuleAction::Accept);
+        else if (action->get<std::string>() == "drop")
+            rule.setAction(RuleAction::Drop);
+        else {
+            client.error("rule-edit", "invalid action '"s + action->get<std::string>() + "'");
+            return;
+        }
+    }
+
+    // All done, sync the rule.
+    irccd.rules().require(index) = rule;
+    client.success("rule-edit");
+}
+
+RuleListCommand::RuleListCommand()
+    : Command("rule-list")
+{
+}
+
+void RuleListCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &)
+{
+    auto array = nlohmann::json::array();
+
+    for (const auto& rule : irccd.rules().list())
+        array.push_back(toJson(rule));
+
+    client.success("rule-list", {{ "list", std::move(array) }});
+}
+
+RuleInfoCommand::RuleInfoCommand()
+    : Command("rule-info")
+{
+}
+
+void RuleInfoCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    client.success("rule-info", toJson(irccd.rules().require(util::json::requireUint(args, "index"))));
+}
+
+RuleRemoveCommand::RuleRemoveCommand()
+    : Command("rule-remove")
+{
+}
+
+void RuleRemoveCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    unsigned position = util::json::requireUint(args, "index");
+
+    if (irccd.rules().length() == 0)
+        client.error("rule-remove", "rule list is empty");
+    if (position >= irccd.rules().length())
+        client.error("rule-remove", "index is out of range");
+    else {
+        irccd.rules().remove(position);
+        client.success("rule-remove");
+    }
+}
+
+RuleMoveCommand::RuleMoveCommand()
+    : Command("rule-move")
+{
+}
+
+void RuleMoveCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    auto from = util::json::requireUint(args, "from");
+    auto to = util::json::requireUint(args, "to");
+
+    /*
+     * Examples of moves
+     * --------------------------------------------------------------
+     *
+     * Before: [0] [1] [2]
+     *
+     * from = 0
+     * to   = 2
+     *
+     * After:  [1] [2] [0]
+     *
+     * --------------------------------------------------------------
+     *
+     * Before: [0] [1] [2]
+     *
+     * from = 2
+     * to   = 0
+     *
+     * After:  [2] [0] [1]
+     *
+     * --------------------------------------------------------------
+     *
+     * Before: [0] [1] [2]
+     *
+     * from = 0
+     * to   = 123
+     *
+     * After:  [1] [2] [0]
+     */
+
+    // Ignore dump input.
+    if (from == to)
+        client.success("rule-move");
+    else if (from >= irccd.rules().length())
+        client.error("rule-move", "rule source index is out of range");
+    else {
+        auto save = irccd.rules().list()[from];
+
+        irccd.rules().remove(from);
+        irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to);
+        client.success("rule-move");
+    }
+}
+
+RuleAddCommand::RuleAddCommand()
+    : Command("rule-add")
+{
+}
+
+void RuleAddCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    auto index = util::json::getUint(args, "index", irccd.rules().length());
+    auto rule = fromJson(args);
+
+    if (index > irccd.rules().length())
+        client.error("rule-add", "index is out of range");
+    else {
+        irccd.rules().insert(rule, index);
+        client.success("rule-add");
+    }
+}
+
 } // !command
 
 } // !irccd
--- a/libirccd/irccd/command.hpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/command.hpp	Tue Sep 26 12:33:56 2017 +0200
@@ -458,6 +458,99 @@
     void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
 };
 
+class IRCCD_EXPORT RuleEditCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleEditCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
+/**
+ * \brief Implementation of rule-list transport command.
+ */
+class IRCCD_EXPORT RuleListCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleListCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
+/**
+ * \brief Implementation of rule-info transport command.
+ */
+class IRCCD_EXPORT RuleInfoCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleInfoCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
+/**
+ * \brief Implementation of rule-remove transport command.
+ */
+class IRCCD_EXPORT RuleRemoveCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleRemoveCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
+/**
+ * \brief Implementation of rule-move transport command.
+ */
+class IRCCD_EXPORT RuleMoveCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleMoveCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
+/**
+ * \brief Implementation of rule-add transport command.
+ */
+class IRCCD_EXPORT RuleAddCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleAddCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
 } // !command
 
 } // !irccd
--- a/libirccd/irccd/rule.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/rule.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -93,6 +93,13 @@
     return m_action;
 }
 
+void Rule::setAction(RuleAction action) noexcept
+{
+    assert(action == RuleAction::Accept || action == RuleAction::Drop);
+
+    m_action = action;
+}
+
 const RuleSet &Rule::servers() const noexcept
 {
     return m_servers;
--- a/libirccd/irccd/rule.hpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/rule.hpp	Tue Sep 26 12:33:56 2017 +0200
@@ -109,6 +109,13 @@
     IRCCD_EXPORT RuleAction action() const noexcept;
 
     /**
+     * Set the action.
+     *
+     * \pre action must be valid
+     */
+    IRCCD_EXPORT void setAction(RuleAction action) noexcept;
+
+    /**
      * Get the servers.
      *
      * \return the servers
@@ -116,12 +123,33 @@
     IRCCD_EXPORT const RuleSet &servers() const noexcept;
 
     /**
+     * Overloaded function.
+     *
+     * \return the servers
+     */
+    inline RuleSet &servers() noexcept
+    {
+        return m_servers;
+    }
+
+    /**
      * Get the channels.
      *
      * \return the channels
      */
     IRCCD_EXPORT const RuleSet &channels() const noexcept;
 
+
+    /**
+     * Overloaded function.
+     *
+     * \return the channels
+     */
+    inline RuleSet &channels() noexcept
+    {
+        return m_channels;
+    }
+
     /**
      * Get the origins.
      *
@@ -136,12 +164,34 @@
      */
     IRCCD_EXPORT const RuleSet &plugins() const noexcept;
 
+
+    /**
+     * Overloaded function.
+     *
+     * \return the plugins
+     */
+    inline RuleSet &plugins() noexcept
+    {
+        return m_plugins;
+    }
+
     /**
      * Get the events.
      *
      * \return the events
      */
     IRCCD_EXPORT const RuleSet &events() const noexcept;
+
+
+    /**
+     * Overloaded function.
+     *
+     * \return the events
+     */
+    inline RuleSet& events() noexcept
+    {
+        return m_events;
+    }
 };
 
 } // !irccd
--- a/libirccd/irccd/server.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/server.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -577,7 +577,7 @@
 void Server::setCtcpVersion(std::string ctcpversion)
 {
     m_ctcpversion = std::move(ctcpversion);
-    irc_set_ctcp_version(*m_session, ctcpversion.c_str());
+    irc_set_ctcp_version(*m_session, m_ctcpversion.c_str());
 }
 
 void Server::next(std::unique_ptr<State> state) noexcept
--- a/libirccd/irccd/service.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/service.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -296,6 +296,22 @@
     m_rules.erase(m_rules.begin() + position);
 }
 
+const Rule &RuleService::require(unsigned position) const
+{
+    if (position >= m_rules.size())
+        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
+
+    return m_rules[position];
+}
+
+Rule &RuleService::require(unsigned position)
+{
+    if (position >= m_rules.size())
+        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
+
+    return m_rules[position];
+}
+
 bool RuleService::solve(const std::string &server,
                         const std::string &channel,
                         const std::string &origin,
--- a/libirccd/irccd/service.hpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/libirccd/irccd/service.hpp	Tue Sep 26 12:33:56 2017 +0200
@@ -347,6 +347,22 @@
     IRCCD_EXPORT void remove(unsigned position);
 
     /**
+     * Get a rule at the specified index or throw an exception if not found.
+     *
+     * \param position the position
+     * \return the rule
+     * \throw std::out_of_range if position is invalid
+     */
+    IRCCD_EXPORT const Rule &require(unsigned position) const;
+
+    /**
+     * Overloaded function.
+     *
+     * \copydoc require
+     */
+    IRCCD_EXPORT Rule& require(unsigned position);
+
+    /**
      * Resolve the action to execute with the specified list of rules.
      *
      * \param server the server name
--- a/plugins/CMakeLists.txt	Tue Nov 06 11:08:29 2018 +0100
+++ b/plugins/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -38,10 +38,3 @@
         DOCS ${plugins_SOURCE_DIR}/${plugin}/${plugin}.md
     )
 endforeach ()
-
-# Debug plugins.
-irccd_define_plugin(
-    NAME debugnative
-    TYPE NATIVE
-    SOURCES ${plugins_SOURCE_DIR}/debugnative/main.cpp
-)
--- a/plugins/debugnative/main.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-/*
- * main.cpp -- native plugin for debugging
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <iostream>
-
-#include <irccd.hpp>
-#include <logger.hpp>
-#include <plugin-dynlib.hpp>
-#include <server.hpp>
-#include <util.hpp>
-
-using namespace irccd;
-
-extern "C" {
-
-DYNLIB_EXPORT void irccd_onChannelMode(Irccd &, const ChannelModeEvent &event)
-{
-    log::info() << "plugin debugnative: onChannelMode event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   mode: " << event.mode << std::endl;
-    log::info() << "plugin debugnative:   argument: " << event.argument << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onChannelNotice(Irccd &, const ChannelNoticeEvent &event)
-{
-    log::info() << "plugin debugnative: onChannelNotice event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onCommand(Irccd &, const MessageEvent &event)
-{
-    log::info() << "plugin debugnative: onCommand event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onConnect(Irccd &, const ConnectEvent &event)
-{
-    log::info() << "plugin debugnative: onConnect event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onInvite(Irccd &, const InviteEvent &event)
-{
-    log::info() << "plugin debugnative: onInvite event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   nickname: " << event.nickname << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onJoin(Irccd &, const JoinEvent &event)
-{
-    log::info() << "plugin debugnative: onJoin event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onKick(Irccd &, const KickEvent &event)
-{
-    log::info() << "plugin debugnative: onKick event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   target: " << event.target << std::endl;
-    log::info() << "plugin debugnative:   reason: " << event.reason << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onLoad(Irccd &, DynlibPlugin &)
-{
-    log::info() << "plugin debugnative: onLoad event\n";
-}
-
-DYNLIB_EXPORT void irccd_onMessage(Irccd &, const MessageEvent &event)
-{
-
-    log::info() << "plugin debugnative: onMessage event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onMe(Irccd &, const MeEvent &event)
-{
-    log::info() << "plugin debugnative: onMe event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onMode(Irccd &, const ModeEvent &event)
-{
-    log::info() << "plugin debugnative: onMode event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   mode: " << event.mode << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onNames(Irccd &, const NamesEvent &event)
-{
-    log::info() << "plugin debugnative: onNames event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   names: "
-                << util::join(event.names.begin(), event.names.end(), ", ")
-                << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onNick(Irccd &, const NickEvent &event)
-{
-    log::info() << "plugin debugnative: onNick event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   nickname: " << event.nickname << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onNotice(Irccd &, const NoticeEvent &event)
-{
-    log::info() << "plugin debugnative: onNotice event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onPart(Irccd &, const PartEvent &event)
-{
-    log::info() << "plugin debugnative: onPart event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   reason: " << event.reason << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onQuery(Irccd &, const QueryEvent &event)
-{
-    log::info() << "plugin debugnative: onQuery event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onQueryCommand(Irccd &, const QueryEvent &event)
-{
-    log::info() << "plugin debugnative: onQueryCommand event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   message: " << event.message << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onReload(Irccd &, DynlibPlugin &)
-{
-    log::info() << "plugin debugnative: onReload event\n";
-}
-
-DYNLIB_EXPORT void irccd_onTopic(Irccd &, const TopicEvent &event)
-{
-    log::info() << "plugin debugnative: onTopic event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   origin: " << event.origin << std::endl;
-    log::info() << "plugin debugnative:   channel: " << event.channel << std::endl;
-    log::info() << "plugin debugnative:   topic: " << event.topic << std::endl;
-}
-
-DYNLIB_EXPORT void irccd_onUnload(Irccd &, DynlibPlugin &)
-{
-    log::info() << "plugin debugnative: onUnload event\n";
-}
-
-DYNLIB_EXPORT void irccd_onWhois(Irccd &, const WhoisEvent &event)
-{
-    log::info() << "plugin debugnative: onWhois event\n";
-    log::info() << "plugin debugnative:   server: " << event.server->name() << std::endl;
-    log::info() << "plugin debugnative:   nick: " << event.whois.nick << std::endl;
-    log::info() << "plugin debugnative:   user: " << event.whois.user << std::endl;
-    log::info() << "plugin debugnative:   host: " << event.whois.host << std::endl;
-    log::info() << "plugin debugnative:   channels: "
-                << util::join(event.whois.channels.begin(),
-                              event.whois.channels.end(), ", ")
-                << std::endl;
-}
-
-} // !C
--- a/plugins/hangman/hangman.js	Tue Nov 06 11:08:29 2018 +0100
+++ b/plugins/hangman/hangman.js	Tue Sep 26 12:33:56 2017 +0200
@@ -40,7 +40,7 @@
     "asked":        "#{nickname}, '#{letter}' was already asked.",
     "dead":         "#{nickname}, fail the word was: #{word}.",
     "found":        "#{nickname}, nice! the word is now #{word}",
-    "running":      "#{nickname}, the game is already running.",
+    "running":      "#{nickname}, the game is already running and the word is: #{word}",
     "start":        "#{nickname}, the game is started, the word to find is: #{word}",
     "win":          "#{nickname}, congratulations, the word is #{word}.",
     "wrong-word":   "#{nickname}, this is not the word.",
@@ -351,9 +351,10 @@
     if (game) {
         var list = message.split(" \t");
 
-        if (list.length === 0 || String(list[0]).length === 0)
+        if (list.length === 0 || String(list[0]).length === 0) {
+            kw.word = game.formatWord();
             server.message(channel, Util.format(Plugin.format["running"], kw));
-        else {
+        } else {
             var word = String(list[0]);
 
             if (Hangman.isWord(word))
--- a/plugins/hangman/hangman.md	Tue Nov 06 11:08:29 2018 +0100
+++ b/plugins/hangman/hangman.md	Tue Sep 26 12:33:56 2017 +0200
@@ -17,13 +17,21 @@
 
 ## Usage
 
-The **hangman** plugin starts when a user execute its special command with no arguments.
+The **hangman** plugin starts when a user execute its special command with no
+arguments.
 
 ````nohighlight
 markand: !hangman
 irccd: markand, the game is started, the word to find is: _ _ _ _ _ _ _ _ _ _ _
 ````
 
+If a game is already running, the same command shows the current word.
+
+````nohighlight
+markand: !hangman
+irccd: markand, the game is already running and the word is: s _ _,
+````
+
 ### Proposal
 
 There are two ways for proposing a response to the game. You can either just ask for a letter or for a whole word.
@@ -92,6 +100,7 @@
 | **dead**                | word                                               | the word to find                |
 | **found**               | word                                               | the hidden word                 |
 | **start**               | word                                               | the hidden word                 |
+| **running**             | word                                               | the hidden word                 |
 | **win**                 | word                                               | the word to find                |
 | **wrong-word**          | word                                               | the invalid word proposal       |
 | **wrong-letter**        | letter                                             | the letter proposal             |
--- a/tests/CMakeLists.txt	Tue Nov 06 11:08:29 2018 +0100
+++ b/tests/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -26,6 +26,12 @@
     add_subdirectory(cmd-plugin-load)
     add_subdirectory(cmd-plugin-reload)
     add_subdirectory(cmd-plugin-unload)
+    add_subdirectory(cmd-rule-add)
+    add_subdirectory(cmd-rule-edit)
+    add_subdirectory(cmd-rule-info)
+    add_subdirectory(cmd-rule-list)
+    add_subdirectory(cmd-rule-move)
+    add_subdirectory(cmd-rule-remove)
     add_subdirectory(cmd-server-cmode)
     add_subdirectory(cmd-server-cnotice)
     add_subdirectory(cmd-server-connect)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-add/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-add
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-add/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,188 @@
+/*
+ * main.cpp -- test rule-add remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleAddCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleAddCommandTest()
+        : CommandTester(std::make_unique<RuleAddCommand>())
+    {
+        m_irccd.commands().add(std::make_unique<RuleListCommand>());
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleAddCommandTest, basic)
+{
+    m_irccdctl.client().request({
+        { "command",    "rule-add"          },
+        { "servers",    { "s1", "s2" }      },
+        { "channels",   { "c1", "c2" }      },
+        { "plugins",    { "p1", "p2" }      },
+        { "events",     { "onMessage" }     },
+        { "action",     "accept"            },
+        { "index",      0                   }
+    });
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        auto servers = m_result["list"][0]["servers"];
+        auto channels = m_result["list"][0]["channels"];
+        auto plugins = m_result["list"][0]["plugins"];
+        auto events = m_result["list"][0]["events"];
+
+        ASSERT_TRUE(contains(servers, "s1"));
+        ASSERT_TRUE(contains(servers, "s2"));
+        ASSERT_TRUE(contains(channels, "c1"));
+        ASSERT_TRUE(contains(channels, "c2"));
+        ASSERT_TRUE(contains(plugins, "p1"));
+        ASSERT_TRUE(contains(plugins, "p2"));
+        ASSERT_TRUE(contains(events, "onMessage"));
+        ASSERT_EQ("accept", m_result["list"][0]["action"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleAddCommandTest, append)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-add"          },
+            { "servers",    { "s1" }            },
+            { "channels",   { "c1" }            },
+            { "plugins",    { "p1" }            },
+            { "events",     { "onMessage" }     },
+            { "action",     "accept"            },
+            { "index",      0                   }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command",    "rule-add"          },
+            { "servers",    { "s2" }            },
+            { "channels",   { "c2" }            },
+            { "plugins",    { "p2" }            },
+            { "events",     { "onMessage" }     },
+            { "action",     "drop"              },
+            { "index",      1                   }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_EQ(2U, m_result["list"].size());
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][0]["action"]);
+        }
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("drop", m_result["list"][1]["action"]);
+        }
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-edit/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-edit
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-edit/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,537 @@
+/*
+ * main.cpp -- test rule-info remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleEditCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleEditCommandTest()
+        : CommandTester(std::make_unique<RuleEditCommand>())
+    {
+        m_irccd.commands().add(std::make_unique<RuleInfoCommand>());
+        m_irccd.rules().add(Rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            RuleAction::Drop
+        ));
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleEditCommandTest, addServer)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "add-servers",    { "new-s3" }    },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["servers"], "new-s3"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, addChannel)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "add-channels",   { "new-c3" }    },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["channels"], "new-c3"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, addPlugin)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "add-plugins",    { "new-p3" }    },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "new-p3"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, addEvent)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "add-events",     { "onQuery" }   },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_TRUE(contains(m_result["events"], "onQuery"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, addEventAndServer)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "add-servers",    { "new-s3" }    },
+            { "add-events",     { "onQuery" }   },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["servers"], "new-s3"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_TRUE(contains(m_result["events"], "onQuery"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, changeAction)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "action",         "accept"        },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "accept");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, removeServer)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "remove-servers", { "s2" }        },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_FALSE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, removeChannel)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "remove-channels", { "c2" }       },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_FALSE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, removePlugin)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "remove-plugins", { "p2" }        },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_FALSE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, removeEvent)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "remove-events",  { "onCommand" } },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_TRUE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_FALSE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleEditCommandTest, removeEventAndServer)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",        "rule-edit"     },
+            { "remove-servers", { "s2" }        },
+            { "remove-events",  { "onCommand" } },
+            { "index",          0               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command", "rule-info" },
+            { "index", 0 }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_TRUE(contains(m_result["servers"], "s1"));
+        ASSERT_FALSE(contains(m_result["servers"], "s2"));
+        ASSERT_TRUE(contains(m_result["channels"], "c1"));
+        ASSERT_TRUE(contains(m_result["channels"], "c2"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
+        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
+        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
+        ASSERT_FALSE(contains(m_result["events"], "onCommand"));
+        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-info/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-info
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-info/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,126 @@
+/*
+ * main.cpp -- test rule-info remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleInfoCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleInfoCommandTest()
+        : CommandTester(std::make_unique<RuleInfoCommand>())
+    {
+        m_irccd.rules().add(Rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            RuleAction::Drop
+        ));
+        m_irccd.rules().add(Rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            RuleAction::Accept
+        ));
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleInfoCommandTest, basic)
+{
+    m_irccdctl.client().request({
+        { "command",    "rule-info" },
+        { "index",      0           }
+    });
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+
+        auto servers = m_result["servers"];
+        auto channels = m_result["channels"];
+        auto plugins = m_result["plugins"];
+        auto events = m_result["events"];
+
+        ASSERT_TRUE(contains(servers, "s1"));
+        ASSERT_TRUE(contains(servers, "s2"));
+        ASSERT_TRUE(contains(channels, "c1"));
+        ASSERT_TRUE(contains(channels, "c2"));
+        ASSERT_TRUE(contains(plugins, "p1"));
+        ASSERT_TRUE(contains(plugins, "p2"));
+        ASSERT_TRUE(contains(events, "onMessage"));
+        ASSERT_TRUE(contains(events, "onCommand"));
+        ASSERT_EQ("drop", m_result["action"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleInfoCommandTest, outOfBounds)
+{
+    m_irccdctl.client().request({
+        { "command",    "rule-info" },
+        { "index",      123         }
+    });
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_FALSE(m_result["status"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-list/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-list
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-list/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,140 @@
+/*
+ * main.cpp -- test rule-list remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleListCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleListCommandTest()
+        : CommandTester(std::make_unique<RuleListCommand>())
+    {
+        m_irccd.rules().add(Rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            RuleAction::Drop
+        ));
+        m_irccd.rules().add(Rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            RuleAction::Accept
+        ));
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleListCommandTest, basic)
+{
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["list"].is_array());
+        ASSERT_EQ(2U, m_result["list"].size());
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_TRUE(contains(events, "onCommand"));
+            ASSERT_EQ("drop", m_result["list"][0]["action"]);
+        }
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][1]["action"]);
+        }
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleListCommandTest, empty)
+{
+    m_irccd.rules().remove(0);
+    m_irccd.rules().remove(0);
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["list"].is_array());
+        ASSERT_EQ(0U, m_result["list"].size());
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-move/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-move
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-move/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,391 @@
+/*
+ * main.cpp -- test rule-move remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleMoveCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleMoveCommandTest()
+        : CommandTester(std::make_unique<RuleMoveCommand>())
+    {
+        m_irccd.commands().add(std::make_unique<RuleListCommand>());
+        m_irccd.rules().add(Rule(
+            { "s0" },
+            { "c0" },
+            { "o0" },
+            { "p0" },
+            { "onMessage" },
+            RuleAction::Drop
+        ));
+        m_irccd.rules().add(Rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            RuleAction::Accept
+        ));
+        m_irccd.rules().add(Rule(
+            { "s2", },
+            { "c2", },
+            { "o2", },
+            { "p2", },
+            { "onMessage", },
+            RuleAction::Accept
+        ));
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleMoveCommandTest, backward)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-move" },
+            { "from",       2           },
+            { "to",         0           }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        // Rule 2.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][0]["action"].get<std::string>());
+        }
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s0"));
+            ASSERT_TRUE(contains(channels, "c0"));
+            ASSERT_TRUE(contains(plugins, "p0"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("drop", m_result["list"][1]["action"].get<std::string>());
+        }
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][2]["servers"];
+            auto channels = m_result["list"][2]["channels"];
+            auto plugins = m_result["list"][2]["plugins"];
+            auto events = m_result["list"][2]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][2]["action"].get<std::string>());
+        }
+    } catch (const std::exception& ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleMoveCommandTest, upward)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-move" },
+            { "from",       0           },
+            { "to",         2           }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][0]["action"].get<std::string>());
+        }
+
+        // Rule 2.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][1]["action"].get<std::string>());
+        }
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][2]["servers"];
+            auto channels = m_result["list"][2]["channels"];
+            auto plugins = m_result["list"][2]["plugins"];
+            auto events = m_result["list"][2]["events"];
+
+            ASSERT_TRUE(contains(servers, "s0"));
+            ASSERT_TRUE(contains(channels, "c0"));
+            ASSERT_TRUE(contains(plugins, "p0"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("drop", m_result["list"][2]["action"].get<std::string>());
+        }
+    } catch (const std::exception& ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleMoveCommandTest, same)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-move" },
+            { "from",       1           },
+            { "to",         1           }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s0"));
+            ASSERT_TRUE(contains(channels, "c0"));
+            ASSERT_TRUE(contains(plugins, "p0"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("drop", m_result["list"][0]["action"].get<std::string>());
+        }
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][1]["action"].get<std::string>());
+        }
+
+        // Rule 2.
+        {
+            auto servers = m_result["list"][2]["servers"];
+            auto channels = m_result["list"][2]["channels"];
+            auto plugins = m_result["list"][2]["plugins"];
+            auto events = m_result["list"][2]["events"];
+
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][2]["action"].get<std::string>());
+        }
+    } catch (const std::exception& ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleMoveCommandTest, beyond)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-move" },
+            { "from",       0           },
+            { "to",         123         }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][0]["action"].get<std::string>());
+        }
+
+        // Rule 2.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][1]["action"].get<std::string>());
+        }
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][2]["servers"];
+            auto channels = m_result["list"][2]["channels"];
+            auto plugins = m_result["list"][2]["plugins"];
+            auto events = m_result["list"][2]["events"];
+
+            ASSERT_TRUE(contains(servers, "s0"));
+            ASSERT_TRUE(contains(channels, "c0"));
+            ASSERT_TRUE(contains(plugins, "p0"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("drop", m_result["list"][2]["action"].get<std::string>());
+        }
+    } catch (const std::exception& ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleMoveCommandTest, outOfBounds)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-move" },
+            { "from",       1024        },
+            { "to",         0           }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_FALSE(m_result["status"].get<bool>());
+    } catch (const std::exception& ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-remove/CMakeLists.txt	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-remove
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-remove/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -0,0 +1,160 @@
+/*
+ * main.cpp -- test rule-remove remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleRemoveCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleRemoveCommandTest()
+        : CommandTester(std::make_unique<RuleRemoveCommand>())
+    {
+        m_irccd.commands().add(std::make_unique<RuleListCommand>());
+        m_irccd.rules().add(Rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            RuleAction::Drop
+        ));
+        m_irccd.rules().add(Rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            RuleAction::Accept
+        ));
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleRemoveCommandTest, basic)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-remove"   },
+            { "index",      1               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result["list"].is_array());
+        ASSERT_EQ(1U, m_result["list"].size());
+
+        auto servers = m_result["list"][0]["servers"];
+        auto channels = m_result["list"][0]["channels"];
+        auto plugins = m_result["list"][0]["plugins"];
+        auto events = m_result["list"][0]["events"];
+
+        ASSERT_TRUE(contains(servers, "s1"));
+        ASSERT_TRUE(contains(servers, "s2"));
+        ASSERT_TRUE(contains(channels, "c1"));
+        ASSERT_TRUE(contains(channels, "c2"));
+        ASSERT_TRUE(contains(plugins, "p1"));
+        ASSERT_TRUE(contains(plugins, "p2"));
+        ASSERT_TRUE(contains(events, "onMessage"));
+        ASSERT_TRUE(contains(events, "onCommand"));
+        ASSERT_EQ("drop", m_result["list"][0]["action"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleRemoveCommandTest, empty)
+{
+    m_irccd.rules().remove(0);
+    m_irccd.rules().remove(0);
+
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-remove"   },
+            { "index",      1               }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_FALSE(m_result["status"].get<bool>());
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleRemoveCommandTest, outOfBounds)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-remove"   },
+            { "index",      123             }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_FALSE(m_result["status"].get<bool>());
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
--- a/tests/plugin-hangman/main.cpp	Tue Nov 06 11:08:29 2018 +0100
+++ b/tests/plugin-hangman/main.cpp	Tue Sep 26 12:33:56 2017 +0200
@@ -65,6 +65,7 @@
             { "dead", "dead=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}" },
             { "found", "found=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}" },
             { "start", "start=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}" },
+            { "running", "running=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}" },
             { "win", "win=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}" },
             { "wrong-letter", "wrong-letter=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{letter}" },
             { "wrong-player", "wrong-player=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{letter}" },
@@ -229,6 +230,16 @@
     ASSERT_EQ("jean:win=hangman:!hangman:test:jean:jean!jean@localhost:jean:sky", m_server->last());
 }
 
+TEST_F(HangmanTest, running)
+{
+    load();
+
+    m_plugin->onCommand(m_irccd, MessageEvent{m_server, "jean!jean@localhost", "#hangman", ""});
+    m_plugin->onMessage(m_irccd, MessageEvent{m_server, "jean!jean@localhost", "#hangman", "y"});
+    m_plugin->onCommand(m_irccd, MessageEvent{m_server, "jean!jean@localhost", "#hangman", ""});
+    ASSERT_EQ("#hangman:running=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:_ _ y", m_server->last());
+}
+
 TEST_F(HangmanTest, wordlist_fix_644)
 {
     /*