Initialize with version 2019-09-18

Also deleted AUTHORS, LICENSE, and PATENTS files since this isn't a
Google-owned project, and updated the README.

Bug: 37626
Change-Id: Ie2b5b121a163febee3d41cb36aecf3a9bbe914d0
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index c2a4eac..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,8 +0,0 @@
-# This is the list of Fuchsia Authors.
-
-# Names should be added to this file as one of
-#     Organization's name
-#     Individual's name <submission email address>
-#     Individual's name <submission email address> <email2> <emailN>
-
-Google Inc.
diff --git a/Changelog b/Changelog
new file mode 100644
index 0000000..18b5e54
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,61 @@
+2019-09-18:
+
+- added os.exec and other system calls
+- exported JS_ValueToAtom()
+- qjsc: added 'qjsc_' prefix to the generated C identifiers
+- added cross-compilation support
+- misc bug fixes
+
+2019-09-01:
+
+- added globalThis
+- documented JS_EVAL_FLAG_COMPILE_ONLY
+- added import.meta.url and import.meta.main
+- added 'debugger' statement
+- misc bug fixes
+
+2019-08-18:
+
+- added os.realpath, os.getcwd, os.mkdir, os.stat, os.lstat,
+  os.readlink, os.readdir, os.utimes, std.popen
+- module autodetection
+- added import.meta
+- misc bug fixes
+
+2019-08-10:
+
+- added public class fields and private class fields, methods and
+  accessors (TC39 proposal)
+- changed JS_ToCStringLen() prototype
+- qjsc: handle '-' in module names and modules with the same filename
+- added std.urlGet
+- exported JS_GetOwnPropertyNames() and JS_GetOwnProperty()
+- exported some bigint C functions
+- added support for eshost in run-test262
+- misc bug fixes
+
+2019-07-28:
+
+- added dynamic import
+- added Promise.allSettled
+- added String.prototype.matchAll
+- added Object.fromEntries
+- reduced number of ticks in await
+- added BigInt support in Atomics
+- exported JS_NewPromiseCapability()
+- misc async function and async generator fixes
+- enabled hashbang support by default
+
+2019-07-21:
+
+- updated test262 tests
+- updated to Unicode version 12.1.0
+- fixed missing Date object in qjsc
+- fixed multi-context creation
+- misc ES2020 related fixes
+- simplified power and division operators in bignum extension
+- fixed several crash conditions
+
+2019-07-09:
+
+- first public release
diff --git a/LICENSE b/LICENSE
index 6d6ddbc..ada77ac 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,27 +1,19 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//    * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//    * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//    * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Copyright 2019 Fabrice Bellard and Charlie Gordon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..377043c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,498 @@
+#
+# QuickJS Javascript Engine
+# 
+# Copyright (c) 2017-2019 Fabrice Bellard
+# Copyright (c) 2017-2019 Charlie Gordon
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+ifeq ($(shell uname -s),Darwin)
+CONFIG_DARWIN=y
+endif
+# Windows cross compilation from Linux
+#CONFIG_WIN32=y
+# use link time optimization (smaller and faster executables but slower build)
+CONFIG_LTO=y
+# consider warnings as errors (for development)
+#CONFIG_WERROR=y
+# force 32 bit build for some utilities
+#CONFIG_M32=y
+
+ifdef CONFIG_DARWIN
+# use clang instead of gcc
+CONFIG_CLANG=y
+CONFIG_DEFAULT_AR=y
+endif
+
+# installation directory
+prefix=/usr/local
+
+# use the gprof profiler
+#CONFIG_PROFILE=y
+# use address sanitizer
+#CONFIG_ASAN=y
+
+OBJDIR=.obj
+
+ifdef CONFIG_WIN32
+  CROSS_PREFIX=i686-w64-mingw32-
+  EXE=.exe
+else
+  CROSS_PREFIX=
+  EXE=
+endif
+ifdef CONFIG_CLANG
+  HOST_CC=clang
+  CC=$(CROSS_PREFIX)clang
+  CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d
+  CFLAGS += -Wextra
+  CFLAGS += -Wno-sign-compare
+  CFLAGS += -Wno-missing-field-initializers
+  CFLAGS += -Wundef -Wuninitialized
+  CFLAGS += -Wunused -Wno-unused-parameter
+  CFLAGS += -Wwrite-strings
+  CFLAGS += -Wchar-subscripts -funsigned-char
+  CFLAGS += -MMD -MF $(OBJDIR)/$(@F).d
+  ifdef CONFIG_DEFAULT_AR
+    AR=$(CROSS_PREFIX)ar
+  else
+    ifdef CONFIG_LTO
+      AR=$(CROSS_PREFIX)llvm-ar
+    else
+      AR=$(CROSS_PREFIX)ar
+    endif
+  endif
+else
+  HOST_CC=gcc
+  CC=$(CROSS_PREFIX)gcc
+  CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d
+  CFLAGS += -Wno-array-bounds -Wno-format-truncation
+  ifdef CONFIG_LTO
+    AR=$(CROSS_PREFIX)gcc-ar
+  else
+    AR=$(CROSS_PREFIX)ar
+  endif
+endif
+STRIP=$(CROSS_PREFIX)strip
+ifdef CONFIG_WERROR
+CFLAGS+=-Werror
+endif
+DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(shell cat VERSION)\"
+CFLAGS+=$(DEFINES)
+CFLAGS_DEBUG=$(CFLAGS) -O0
+CFLAGS_SMALL=$(CFLAGS) -Os
+CFLAGS_OPT=$(CFLAGS) -O2
+CFLAGS_NOLTO:=$(CFLAGS_OPT)
+LDFLAGS=-g
+ifdef CONFIG_LTO
+CFLAGS_SMALL+=-flto
+CFLAGS_OPT+=-flto
+LDFLAGS+=-flto
+endif
+ifdef CONFIG_PROFILE
+CFLAGS+=-p
+LDFLAGS+=-p
+endif
+ifdef CONFIG_ASAN
+CFLAGS+=-fsanitize=address
+LDFLAGS+=-fsanitize=address
+endif
+ifdef CONFIG_WIN32
+LDEXPORT=
+else
+LDEXPORT=-rdynamic
+endif
+
+PROGS=qjs$(EXE) qjsbn$(EXE) qjsc$(EXE) qjsbnc$(EXE) run-test262 run-test262-bn
+ifneq ($(CROSS_PREFIX),)
+QJSC_CC=gcc
+QJSC=./host-qjsc
+QJSBNC=./host-qjsbnc
+PROGS+=$(QJSC) $(QJSBNC)
+else
+QJSC_CC=$(CC)
+QJSC=./qjsc$(EXE)
+QJSBNC=./qjsbnc$(EXE)
+endif
+ifndef CONFIG_WIN32
+PROGS+=qjscalc
+endif
+ifdef CONFIG_M32
+PROGS+=qjs32 qjs32_s qjsbn32
+endif
+PROGS+=libquickjs.a libquickjs.bn.a
+ifdef CONFIG_LTO
+PROGS+=libquickjs.lto.a libquickjs.bn.lto.a
+endif
+
+# examples
+ifeq ($(CROSS_PREFIX),)
+ifdef CONFIG_ASAN
+PROGS+=
+else
+PROGS+=examples/hello examples/hello_module examples/c_module
+endif
+endif
+
+all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS)
+
+QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o
+
+QJSBN_LIB_OBJS=$(patsubst %.o, %.bn.o, $(QJS_LIB_OBJS)) $(OBJDIR)/libbf.bn.o
+
+QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS)
+
+QJSBN_OBJS=$(OBJDIR)/qjs.bn.o $(OBJDIR)/repl-bn.bn.o $(OBJDIR)/qjscalc.bn.o $(QJSBN_LIB_OBJS)
+
+LIBS=-lm
+ifndef CONFIG_WIN32
+LIBS+=-ldl
+endif
+
+$(OBJDIR):
+	mkdir -p $(OBJDIR)
+
+qjs$(EXE): $(QJS_OBJS)
+	$(CC) $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS)
+
+qjs-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJS_OBJS))
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+qjsbnc$(EXE): $(OBJDIR)/qjsc.bn.o $(QJSBN_LIB_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+ifneq ($(CROSS_PREFIX),)
+
+$(QJSC): $(OBJDIR)/qjsc.host.o \
+    $(patsubst %.o, %.host.o, $(QJS_LIB_OBJS))
+	$(HOST_CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+$(QJSBNC): $(OBJDIR)/qjsc.bn.host.o \
+    $(patsubst %.o, %.host.o, $(QJSBN_LIB_OBJS))
+	$(HOST_CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+endif #CROSS_PREFIX
+
+QJSC_DEFINES:=-DCONFIG_CC=\"$(QJSC_CC)\" -DCONFIG_PREFIX=\"$(prefix)\"
+ifdef CONFIG_LTO
+QJSC_DEFINES+=-DCONFIG_LTO
+endif
+QJSC_HOST_DEFINES:=-DCONFIG_CC=\"$(HOST_CC)\" -DCONFIG_PREFIX=\"$(prefix)\"
+
+$(OBJDIR)/qjsc.o $(OBJDIR)/qjsc.bn.o: CFLAGS+=$(QJSC_DEFINES)
+$(OBJDIR)/qjsc.host.o $(OBJDIR)/qjsc.bn.host.o: CFLAGS+=$(QJSC_HOST_DEFINES)
+
+qjs32: $(patsubst %.o, %.m32.o, $(QJS_OBJS))
+	$(CC) -m32 $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS)
+
+qjs32_s: $(patsubst %.o, %.m32s.o, $(QJS_OBJS))
+	$(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS)
+	@size $@
+
+qjsbn$(EXE): $(QJSBN_OBJS)
+	$(CC) $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS)
+
+qjsbn32: $(patsubst %.o, %.m32.o, $(QJSBN_OBJS))
+	$(CC) -m32 $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS)
+
+qjscalc: qjsbn
+	ln -sf $< $@
+
+qjsbn-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJSBN_OBJS))
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+ifdef CONFIG_LTO
+LTOEXT=.lto
+else
+LTOEXT=
+endif
+
+libquickjs$(LTOEXT).a: $(QJS_LIB_OBJS)
+	$(AR) rcs $@ $^
+
+libquickjs.bn$(LTOEXT).a: $(QJSBN_LIB_OBJS)
+	$(AR) rcs $@ $^
+
+ifdef CONFIG_LTO
+libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS))
+	$(AR) rcs $@ $^
+
+libquickjs.bn.a: $(patsubst %.o, %.nolto.o, $(QJSBN_LIB_OBJS))
+	$(AR) rcs $@ $^
+endif # CONFIG_LTO
+
+repl.c: $(QJSC) repl.js 
+	$(QJSC) -c -o $@ -m repl.js
+
+repl-bn.c: $(QJSBNC) repl.js 
+	$(QJSBNC) -c -o $@ -m repl.js
+
+qjscalc.c: $(QJSBNC) qjscalc.js
+	$(QJSBNC) -c -o $@ qjscalc.js
+
+ifneq ($(wildcard unicode/UnicodeData.txt),)
+$(OBJDIR)/libunicode.o $(OBJDIR)/libunicode.m32.o $(OBJDIR)/libunicode.m32s.o $(OBJDIR)/libunicode.bn.o $(OBJDIR)/libunicode.bn.m32.o \
+    $(OBJDIR)/libunicode.nolto.o $(OBJDIR)/libunicode.bn.nolto.o: libunicode-table.h
+
+libunicode-table.h: unicode_gen
+	./unicode_gen unicode $@
+endif
+
+run-test262: $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread
+
+run-test262-bn: $(OBJDIR)/run-test262.bn.o $(QJSBN_LIB_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread
+
+run-test262-debug: $(patsubst %.o, %.debug.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS))
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread
+
+run-test262-32: $(patsubst %.o, %.m32.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS))
+	$(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread
+
+run-test262-bn32: $(patsubst %.o, %.m32.o, $(OBJDIR)/run-test262.bn.o $(QJSBN_LIB_OBJS))
+	$(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread
+
+# object suffix order: bn, nolto, [m32|m32s]
+
+$(OBJDIR)/%.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_OPT) -c -o $@ $<
+
+$(OBJDIR)/%.host.o: %.c | $(OBJDIR)
+	$(HOST_CC) $(CFLAGS_OPT) -c -o $@ $<
+
+$(OBJDIR)/%.pic.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_OPT) -fPIC -DJS_SHARED_LIBRARY -c -o $@ $<
+
+$(OBJDIR)/%.bn.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_OPT) -DCONFIG_BIGNUM -c -o $@ $<
+
+$(OBJDIR)/%.bn.host.o: %.c | $(OBJDIR)
+	$(HOST_CC) $(CFLAGS_OPT) -DCONFIG_BIGNUM -c -o $@ $<
+
+$(OBJDIR)/%.nolto.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_NOLTO) -c -o $@ $<
+
+$(OBJDIR)/%.bn.nolto.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_NOLTO) -DCONFIG_BIGNUM -c -o $@ $<
+
+$(OBJDIR)/%.m32.o: %.c | $(OBJDIR)
+	$(CC) -m32 $(CFLAGS_OPT) -c -o $@ $<
+
+$(OBJDIR)/%.m32s.o: %.c | $(OBJDIR)
+	$(CC) -m32 $(CFLAGS_SMALL) -c -o $@ $<
+
+$(OBJDIR)/%.bn.m32.o: %.c | $(OBJDIR)
+	$(CC) -m32 $(CFLAGS_OPT) -DCONFIG_BIGNUM -c -o $@ $<
+
+$(OBJDIR)/%.debug.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_DEBUG) -c -o $@ $<
+
+$(OBJDIR)/%.bn.debug.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS_DEBUG) -DCONFIG_BIGNUM -c -o $@ $<
+
+$(OBJDIR)/%.check.o: %.c | $(OBJDIR)
+	$(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $<
+
+regexp_test: libregexp.c libunicode.c cutils.c
+	$(CC) $(LDFLAGS) $(CFLAGS) -DTEST -o $@ libregexp.c libunicode.c cutils.c $(LIBS)
+
+jscompress: jscompress.c
+	$(CC) $(LDFLAGS) $(CFLAGS) -o $@ jscompress.c
+
+unicode_gen: $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o libunicode.c unicode_gen_def.h
+	$(HOST_CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o
+
+clean:
+	rm -f repl.c repl-bn.c qjscalc.c out.c
+	rm -f *.a *.so *.o *.d *~ jscompress unicode_gen regexp_test $(PROGS)
+	rm -f hello.c hello_module.c c_module.c 
+	rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug qjsbn-debug
+	rm -rf run-test262-debug run-test262-32 run-test262-bn32
+
+install: all
+	mkdir -p "$(DESTDIR)$(prefix)/bin"
+	$(STRIP) qjs qjsbn qjsc qjsbnc
+	install -m755 qjs qjsbn qjsc qjsbnc "$(DESTDIR)$(prefix)/bin"
+	ln -sf qjsbn "$(DESTDIR)$(prefix)/bin/qjscalc"
+	mkdir -p "$(DESTDIR)$(prefix)/lib/quickjs"
+	install -m644 libquickjs.a libquickjs.bn.a "$(DESTDIR)$(prefix)/lib/quickjs"
+ifdef CONFIG_LTO
+	install -m644 libquickjs.lto.a libquickjs.bn.lto.a "$(DESTDIR)$(prefix)/lib/quickjs"
+endif
+	mkdir -p "$(DESTDIR)$(prefix)/include/quickjs"
+	install -m644 quickjs.h quickjs-libc.h "$(DESTDIR)$(prefix)/include/quickjs"
+
+###############################################################################
+# examples
+
+# example of static JS compilation
+HELLO_SRCS=examples/hello.js
+HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \
+           -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \
+           -fno-date -fno-module-loader
+
+hello.c: $(QJSC) $(HELLO_SRCS)
+	$(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS)
+
+ifdef CONFIG_M32
+examples/hello: $(OBJDIR)/hello.m32s.o $(patsubst %.o, %.m32s.o, $(QJS_LIB_OBJS))
+	$(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS)
+else
+examples/hello: $(OBJDIR)/hello.o $(QJS_LIB_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+endif
+
+# example of static JS compilation with modules
+HELLO_MODULE_SRCS=examples/hello_module.js
+HELLO_MODULE_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \
+           -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \
+           -fno-date -m
+examples/hello_module: $(QJSC) libquickjs$(LTOEXT).a $(HELLO_MODULE_SRCS)
+	$(QJSC) $(HELLO_MODULE_OPTS) -o $@ $(HELLO_MODULE_SRCS)
+
+# use of an external C module (static compilation)
+
+c_module.c: $(QJSC) examples/c_module.js
+	$(QJSC) -e -M examples/fib.so,fib -m -o $@ examples/c_module.js
+
+examples/c_module: $(OBJDIR)/c_module.o $(OBJDIR)/fib.o libquickjs$(LTOEXT).a
+	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+$(OBJDIR)/fib.o: examples/fib.c
+	$(CC) $(CFLAGS_OPT) -c -o $@ $<
+
+###############################################################################
+# documentation
+
+DOCS=doc/quickjs.pdf doc/quickjs.html doc/jsbignum.pdf doc/jsbignum.html 
+
+build_doc: $(DOCS)
+
+clean_doc: 
+	rm -f $(DOCS)
+
+doc/%.pdf: doc/%.texi
+	texi2pdf --clean -o $@ -q $<
+
+doc/%.html.pre: doc/%.texi
+	makeinfo --html --no-headers --no-split --number-sections -o $@ $<
+
+doc/%.html: doc/%.html.pre
+	sed -e 's|</style>|</style>\n<meta name="viewport" content="width=device-width, initial-scale=1.0">|' < $< > $@
+
+###############################################################################
+# tests
+
+ifndef CONFIG_DARWIN
+test: bjson.so
+endif
+
+test: qjs qjsbn
+	./qjs tests/test_closure.js
+	./qjs tests/test_op.js
+	./qjs tests/test_builtin.js
+	./qjs tests/test_loop.js
+	./qjs tests/test_std.js
+ifndef CONFIG_DARWIN
+	./qjs tests/test_bjson.js
+endif
+	./qjsbn tests/test_closure.js
+	./qjsbn tests/test_op.js
+	./qjsbn tests/test_builtin.js
+	./qjsbn tests/test_loop.js
+	./qjsbn tests/test_std.js
+	./qjsbn --qjscalc tests/test_bignum.js
+
+test-32: qjs32 qjsbn32
+	./qjs32 tests/test_closure.js
+	./qjs32 tests/test_op.js
+	./qjs32 tests/test_builtin.js
+	./qjs32 tests/test_loop.js
+	./qjs32 tests/test_std.js
+	./qjsbn32 tests/test_closure.js
+	./qjsbn32 tests/test_op.js
+	./qjsbn32 tests/test_builtin.js
+	./qjsbn32 tests/test_loop.js
+	./qjsbn32 tests/test_std.js
+	./qjsbn32 --qjscalc tests/test_bignum.js
+
+stats: qjs qjs32
+	./qjs -qd
+	./qjs32 -qd
+
+microbench: qjs
+	./qjs --std tests/microbench.js
+
+microbench-32: qjs32
+	./qjs32 --std tests/microbench.js
+
+# ES5 tests (obsolete)
+test2o: run-test262
+	time ./run-test262 -m -c test262o.conf
+
+test2o-32: run-test262-32
+	time ./run-test262-32 -m -c test262o.conf
+
+test2o-update: run-test262
+	./run-test262 -u -c test262o.conf
+
+# Test262 tests
+test2-default: run-test262
+	time ./run-test262 -m -c test262.conf
+
+test2: run-test262
+	time ./run-test262 -m -c test262.conf -a
+
+test2-32: run-test262-32
+	time ./run-test262-32 -m -c test262.conf -a
+
+test2-update: run-test262
+	./run-test262 -u -c test262.conf -a
+
+test2-check: run-test262
+	time ./run-test262 -m -c test262.conf -E -a
+
+# Test262 + BigInt tests
+test2bn-default: run-test262-bn
+	time ./run-test262-bn -m -c test262bn.conf
+
+test2bn: run-test262-bn
+	time ./run-test262-bn -m -c test262bn.conf -a
+
+test2bn-32: run-test262-bn32
+	time ./run-test262-bn32 -m -c test262bn.conf -a
+
+testall: all test microbench test2o test2 test2bn
+
+testall-32: all test-32 microbench-32 test2o-32 test2-32 test2bn-32
+
+testall-complete: testall testall-32
+
+bench-v8: qjs qjs32
+	make -C tests/bench-v8
+	./qjs -d tests/bench-v8/combined.js
+
+bjson.so: $(OBJDIR)/bjson.pic.o
+	$(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS)
+
+-include $(wildcard $(OBJDIR)/*.d)
diff --git a/PATENTS b/PATENTS
deleted file mode 100644
index 2746e78..0000000
--- a/PATENTS
+++ /dev/null
@@ -1,22 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Fuchsia project.
-
-Google hereby grants to you a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this
-section) patent license to make, have made, use, offer to sell, sell,
-import, transfer, and otherwise run, modify and propagate the contents
-of this implementation of Fuchsia, where such license applies only to
-those patent claims, both currently owned by Google and acquired in
-the future, licensable by Google that are necessarily infringed by
-this implementation. This grant does not include claims that would be
-infringed only as a consequence of further modification of this
-implementation. If you or your agent or exclusive licensee institute
-or order or agree to the institution of patent litigation or any other
-patent enforcement activity against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that this
-implementation of Fuchsia constitutes direct or contributory patent
-infringement, or inducement of patent infringement, then any patent
-rights granted to you under this License for this implementation of
-Fuchsia shall terminate as of the date such litigation is filed.
diff --git a/README.md b/README.md
index 8b90b74..eb4e98d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-Fuchsia Open Source Template Repository
+QuickJS
 =======================================
 
-This repository is a template that we will use when creating new open source
-repositories for Fuchsia.
+Downloaded and updated manually from the [QuickJS
+homepage](https://bellard.org/quickjs/).
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..9edcedd
--- /dev/null
+++ b/TODO
@@ -0,0 +1,80 @@
+- 64-bit atoms in 64-bit mode?
+- rename CONFIG_ALL_UNICODE, CONFIG_BIGNUM, CONFIG_ATOMICS, CONFIG_CHECK_JSVALUE ?
+- unify coding style and naming conventions
+- use names from the ECMA spec in library implementation
+- modules: if no ".", use a well known module loading path ?
+- use JSHoistedDef only for global variables (JSHoistedDef.var_name != JS_ATOM_NULL)
+- add index in JSVarDef and is_arg flag to merge args and vars in JSFunctionDef
+- replace most JSVarDef flags with var_type enumeration
+- use byte code emitters with typed arguments (for clarity)
+- use 2 bytecode DynBufs in JSFunctionDef, one for reading, one for writing
+  and use the same wrappers in all phases
+- use more generic method for line numbers in resolve_variables and resolve_labels
+
+Memory:
+- test border cases for max number of atoms, object properties, string length
+- add emergency malloc mode for out of memory exceptions.
+- test all DynBuf memory errors
+- test all js_realloc memory errors
+- bignum: handle memory errors
+- use memory pools for objects, etc?
+- improve JS_ComputeMemoryUsage() with more info
+
+Optimizations:
+- use auto-init properties for more global objects
+- reuse stack slots for disjoint scopes, if strip
+- optimize `for of` iterator for built-in array objects
+- add heuristic to avoid some cycles in closures
+- small String (0-2 charcodes) with immediate storage
+- perform static string concatenation at compile time
+- optimize string concatenation with ropes or miniropes?
+- add implicit numeric strings for Uint32 numbers?
+- optimize `s += a + b`, `s += a.b` and similar simple expressions
+- ensure string canonical representation and optimise comparisons and hashes?
+- remove JSObject.first_weak_ref, use bit+context based hashed array for weak references
+- optimize function storage with length and name accessors?
+- property access optimization on the global object, functions,
+  prototypes and special non extensible objects.
+- create object literals with the correct length by backpatching length argument
+- remove redundant set_loc_uninitialized/check_uninitialized opcodes
+- peephole optim: push_atom_value, to_propkey -> push_atom_value
+- peephole optim: put_loc x, get_loc_check x -> set_loc x
+- comparative performance benchmark
+- use variable name when throwing uninitialized exception if available
+- convert slow array to fast array when all properties != length are numeric
+- optimize destructuring assignments for global and local variables
+- implement some form of tail-call-optimization
+- debugger keyword support
+- optimize OP_apply
+- optimize f(...b)
+
+Extensions:
+- support more features in [features] section
+- add built-in preprocessor in compiler, get rid of jscompress
+  handle #if, #ifdef, #line, limited support for #define
+- limited support for web assembly 
+- get rid of __loadScript, use more common name
+- BSD sockets
+- Process or thread control
+- use custom printf to avoid C library compatibility issues
+- use custom timezone support to avoid C library compatibility issues
+
+REPL:
+- strip internal functions from stack trace
+- readline: support MS Windows terminal
+- readline: handle dynamic terminal resizing
+- multiline editing
+- debugger
+- runtime object and function inspectors
+- interactive object browser
+- use more generic approach to display evaluation results
+- improve directive handling: dispatch, colorize, completion...
+- save history
+- close all predefined methods in repl.js and jscalc.js
+
+Test262o:   0/11262 errors, 463 excluded
+Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch)
+
+Test262:   2/67351 errors, 839 excluded, 1370 skipped
+Test262bn: 2/69452 errors, 772 excluded, 383 skipped
+test262 commit: d65b9b35be091147edf31ec527a47cb95a327217
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..80c17d9
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+2019-09-18
diff --git a/bjson.c b/bjson.c
new file mode 100644
index 0000000..ed08cbd
--- /dev/null
+++ b/bjson.c
@@ -0,0 +1,88 @@
+/*
+ * QuickJS: binary JSON module (test only)
+ * 
+ * Copyright (c) 2017-2019 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "quickjs-libc.h"
+#include "cutils.h"
+
+static JSValue js_bjson_read(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    uint8_t *buf;
+    uint64_t pos, len;
+    JSValue obj;
+    size_t size;
+    
+    if (JS_ToIndex(ctx, &pos, argv[1]))
+        return JS_EXCEPTION;
+    if (JS_ToIndex(ctx, &len, argv[2]))
+        return JS_EXCEPTION;
+    buf = JS_GetArrayBuffer(ctx, &size, argv[0]);
+    if (!buf)
+        return JS_EXCEPTION;
+    if (pos + len > size)
+        return JS_ThrowRangeError(ctx, "array buffer overflow");
+    obj = JS_ReadObject(ctx, buf + pos, len, 0);
+    return obj;
+}
+
+static JSValue js_bjson_write(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    size_t len;
+    uint8_t *buf;
+    JSValue array;
+    
+    buf = JS_WriteObject(ctx, &len, argv[0], 0);
+    if (!buf)
+        return JS_EXCEPTION;
+    array = JS_NewArrayBufferCopy(ctx, buf, len);
+    js_free(ctx, buf);
+    return array;
+}
+
+static const JSCFunctionListEntry js_bjson_funcs[] = {
+    JS_CFUNC_DEF("read", 3, js_bjson_read ),
+    JS_CFUNC_DEF("write", 1, js_bjson_write ),
+};
+
+static int js_bjson_init(JSContext *ctx, JSModuleDef *m)
+{
+    return JS_SetModuleExportList(ctx, m, js_bjson_funcs,
+                                  countof(js_bjson_funcs));
+}
+
+#ifdef JS_SHARED_LIBRARY
+#define JS_INIT_MODULE js_init_module
+#else
+#define JS_INIT_MODULE js_init_module_bjson
+#endif
+
+JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
+{
+    JSModuleDef *m;
+    m = JS_NewCModule(ctx, module_name, js_bjson_init);
+    if (!m)
+        return NULL;
+    JS_AddModuleExportList(ctx, m, js_bjson_funcs, countof(js_bjson_funcs));
+    return m;
+}
diff --git a/cutils.c b/cutils.c
new file mode 100644
index 0000000..64a32d3
--- /dev/null
+++ b/cutils.c
@@ -0,0 +1,620 @@
+/*
+ * C utilities
+ * 
+ * Copyright (c) 2017 Fabrice Bellard
+ * Copyright (c) 2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "cutils.h"
+
+void pstrcpy(char *buf, int buf_size, const char *str)
+{
+    int c;
+    char *q = buf;
+
+    if (buf_size <= 0)
+        return;
+
+    for(;;) {
+        c = *str++;
+        if (c == 0 || q >= buf + buf_size - 1)
+            break;
+        *q++ = c;
+    }
+    *q = '\0';
+}
+
+/* strcat and truncate. */
+char *pstrcat(char *buf, int buf_size, const char *s)
+{
+    int len;
+    len = strlen(buf);
+    if (len < buf_size)
+        pstrcpy(buf + len, buf_size - len, s);
+    return buf;
+}
+
+int strstart(const char *str, const char *val, const char **ptr)
+{
+    const char *p, *q;
+    p = str;
+    q = val;
+    while (*q != '\0') {
+        if (*p != *q)
+            return 0;
+        p++;
+        q++;
+    }
+    if (ptr)
+        *ptr = p;
+    return 1;
+}
+
+int has_suffix(const char *str, const char *suffix)
+{
+    size_t len = strlen(str);
+    size_t slen = strlen(suffix);
+    return (len >= slen && !memcmp(str + len - slen, suffix, slen));
+}
+
+/* Dynamic buffer package */
+
+static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size)
+{
+    return realloc(ptr, size);
+}
+
+void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func)
+{
+    memset(s, 0, sizeof(*s));
+    if (!realloc_func)
+        realloc_func = dbuf_default_realloc;
+    s->opaque = opaque;
+    s->realloc_func = realloc_func;
+}
+
+void dbuf_init(DynBuf *s)
+{
+    dbuf_init2(s, NULL, NULL);
+}
+
+/* return < 0 if error */
+int dbuf_realloc(DynBuf *s, size_t new_size)
+{
+    size_t size;
+    uint8_t *new_buf;
+    if (new_size > s->allocated_size) {
+        if (s->error)
+            return -1;
+        size = s->allocated_size * 3 / 2;
+        if (size > new_size)
+            new_size = size;
+        new_buf = s->realloc_func(s->opaque, s->buf, new_size);
+        if (!new_buf) {
+            s->error = TRUE;
+            return -1;
+        }
+        s->buf = new_buf;
+        s->allocated_size = new_size;
+    }
+    return 0;
+}
+
+int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len)
+{
+    size_t end;
+    end = offset + len;
+    if (dbuf_realloc(s, end))
+        return -1;
+    memcpy(s->buf + offset, data, len);
+    if (end > s->size)
+        s->size = end;
+    return 0;
+}
+
+int dbuf_put(DynBuf *s, const uint8_t *data, size_t len)
+{
+    if (unlikely((s->size + len) > s->allocated_size)) {
+        if (dbuf_realloc(s, s->size + len))
+            return -1;
+    }
+    memcpy(s->buf + s->size, data, len);
+    s->size += len;
+    return 0;
+}
+
+int dbuf_put_self(DynBuf *s, size_t offset, size_t len)
+{
+    if (unlikely((s->size + len) > s->allocated_size)) {
+        if (dbuf_realloc(s, s->size + len))
+            return -1;
+    }
+    memcpy(s->buf + s->size, s->buf + offset, len);
+    s->size += len;
+    return 0;
+}
+
+int dbuf_putc(DynBuf *s, uint8_t c)
+{
+    return dbuf_put(s, &c, 1);
+}
+
+int dbuf_putstr(DynBuf *s, const char *str)
+{
+    return dbuf_put(s, (const uint8_t *)str, strlen(str));
+}
+
+int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s,
+                                                      const char *fmt, ...)
+{
+    va_list ap;
+    char buf[128];
+    int len;
+    
+    va_start(ap, fmt);
+    len = vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+    if (len < sizeof(buf)) {
+        /* fast case */
+        return dbuf_put(s, (uint8_t *)buf, len);
+    } else {
+        if (dbuf_realloc(s, s->size + len + 1))
+            return -1;
+        va_start(ap, fmt);
+        vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size,
+                  fmt, ap);
+        va_end(ap);
+        s->size += len;
+    }
+    return 0;
+}
+
+void dbuf_free(DynBuf *s)
+{
+    /* we test s->buf as a fail safe to avoid crashing if dbuf_free()
+       is called twice */
+    if (s->buf) {
+        s->realloc_func(s->opaque, s->buf, 0);
+    }
+    memset(s, 0, sizeof(*s));
+}
+
+/* Note: at most 31 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes
+   are output. */
+int unicode_to_utf8(uint8_t *buf, unsigned int c)
+{
+    uint8_t *q = buf;
+
+    if (c < 0x80) {
+        *q++ = c;
+    } else {
+        if (c < 0x800) {
+            *q++ = (c >> 6) | 0xc0;
+        } else {
+            if (c < 0x10000) {
+                *q++ = (c >> 12) | 0xe0;
+            } else {
+                if (c < 0x00200000) {
+                    *q++ = (c >> 18) | 0xf0;
+                } else {
+                    if (c < 0x04000000) {
+                        *q++ = (c >> 24) | 0xf8;
+                    } else if (c < 0x80000000) {
+                        *q++ = (c >> 30) | 0xfc;
+                        *q++ = ((c >> 24) & 0x3f) | 0x80;
+                    } else {
+                        return 0;
+                    }
+                    *q++ = ((c >> 18) & 0x3f) | 0x80;
+                }
+                *q++ = ((c >> 12) & 0x3f) | 0x80;
+            }
+            *q++ = ((c >> 6) & 0x3f) | 0x80;
+        }
+        *q++ = (c & 0x3f) | 0x80;
+    }
+    return q - buf;
+}
+
+static const unsigned int utf8_min_code[5] = {
+    0x80, 0x800, 0x10000, 0x00200000, 0x04000000,
+};
+
+static const unsigned char utf8_first_code_mask[5] = {
+    0x1f, 0xf, 0x7, 0x3, 0x1,
+};
+
+/* return -1 if error. *pp is not updated in this case. max_len must
+   be >= 1. The maximum length for a UTF8 byte sequence is 6 bytes. */
+int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp)
+{
+    int l, c, b, i;
+
+    c = *p++;
+    if (c < 0x80) {
+        *pp = p;
+        return c;
+    }
+    switch(c) {
+    case 0xc0 ... 0xdf:
+        l = 1;
+        break;
+    case 0xe0 ... 0xef:
+        l = 2;
+        break;
+    case 0xf0 ... 0xf7:
+        l = 3;
+        break;
+    case 0xf8 ... 0xfb:
+        l = 4;
+        break;
+    case 0xfc ... 0xfd:
+        l = 5;
+        break;
+    default:
+        return -1;
+    }
+    /* check that we have enough characters */
+    if (l > (max_len - 1))
+        return -1;
+    c &= utf8_first_code_mask[l - 1];
+    for(i = 0; i < l; i++) {
+        b = *p++;
+        if (b < 0x80 || b >= 0xc0)
+            return -1;
+        c = (c << 6) | (b & 0x3f);
+    }
+    if (c < utf8_min_code[l - 1])
+        return -1;
+    *pp = p;
+    return c;
+}
+
+#if 0
+
+#if defined(EMSCRIPTEN) || defined(__ANDROID__)
+
+static void *rqsort_arg;
+static int (*rqsort_cmp)(const void *, const void *, void *);
+
+static int rqsort_cmp2(const void *p1, const void *p2)
+{
+    return rqsort_cmp(p1, p2, rqsort_arg);
+}
+
+/* not reentrant, but not needed with emscripten */
+void rqsort(void *base, size_t nmemb, size_t size,
+            int (*cmp)(const void *, const void *, void *),
+            void *arg)
+{
+    rqsort_arg = arg;
+    rqsort_cmp = cmp;
+    qsort(base, nmemb, size, rqsort_cmp2);
+}
+
+#endif
+
+#else
+
+typedef void (*exchange_f)(void *a, void *b, size_t size);
+typedef int (*cmp_f)(const void *, const void *, void *opaque);
+
+static void exchange_bytes(void *a, void *b, size_t size) {
+    uint8_t *ap = (uint8_t *)a;
+    uint8_t *bp = (uint8_t *)b;
+
+    while (size-- != 0) {
+        uint8_t t = *ap;
+        *ap++ = *bp;
+        *bp++ = t;
+    }
+}
+
+static void exchange_one_byte(void *a, void *b, size_t size) {
+    uint8_t *ap = (uint8_t *)a;
+    uint8_t *bp = (uint8_t *)b;
+    uint8_t t = *ap;
+    *ap = *bp;
+    *bp = t;
+}
+
+static void exchange_int16s(void *a, void *b, size_t size) {
+    uint16_t *ap = (uint16_t *)a;
+    uint16_t *bp = (uint16_t *)b;
+
+    for (size /= sizeof(uint16_t); size-- != 0;) {
+        uint16_t t = *ap;
+        *ap++ = *bp;
+        *bp++ = t;
+    }
+}
+
+static void exchange_one_int16(void *a, void *b, size_t size) {
+    uint16_t *ap = (uint16_t *)a;
+    uint16_t *bp = (uint16_t *)b;
+    uint16_t t = *ap;
+    *ap = *bp;
+    *bp = t;
+}
+
+static void exchange_int32s(void *a, void *b, size_t size) {
+    uint32_t *ap = (uint32_t *)a;
+    uint32_t *bp = (uint32_t *)b;
+
+    for (size /= sizeof(uint32_t); size-- != 0;) {
+        uint32_t t = *ap;
+        *ap++ = *bp;
+        *bp++ = t;
+    }
+}
+
+static void exchange_one_int32(void *a, void *b, size_t size) {
+    uint32_t *ap = (uint32_t *)a;
+    uint32_t *bp = (uint32_t *)b;
+    uint32_t t = *ap;
+    *ap = *bp;
+    *bp = t;
+}
+
+static void exchange_int64s(void *a, void *b, size_t size) {
+    uint64_t *ap = (uint64_t *)a;
+    uint64_t *bp = (uint64_t *)b;
+
+    for (size /= sizeof(uint64_t); size-- != 0;) {
+        uint64_t t = *ap;
+        *ap++ = *bp;
+        *bp++ = t;
+    }
+}
+
+static void exchange_one_int64(void *a, void *b, size_t size) {
+    uint64_t *ap = (uint64_t *)a;
+    uint64_t *bp = (uint64_t *)b;
+    uint64_t t = *ap;
+    *ap = *bp;
+    *bp = t;
+}
+
+static void exchange_int128s(void *a, void *b, size_t size) {
+    uint64_t *ap = (uint64_t *)a;
+    uint64_t *bp = (uint64_t *)b;
+
+    for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) {
+        uint64_t t = ap[0];
+        uint64_t u = ap[1];
+        ap[0] = bp[0];
+        ap[1] = bp[1];
+        bp[0] = t;
+        bp[1] = u;
+    }
+}
+
+static void exchange_one_int128(void *a, void *b, size_t size) {
+    uint64_t *ap = (uint64_t *)a;
+    uint64_t *bp = (uint64_t *)b;
+    uint64_t t = ap[0];
+    uint64_t u = ap[1];
+    ap[0] = bp[0];
+    ap[1] = bp[1];
+    bp[0] = t;
+    bp[1] = u;
+}
+
+static inline exchange_f exchange_func(const void *base, size_t size) {
+    switch (((uintptr_t)base | (uintptr_t)size) & 15) {
+    case 0:
+        if (size == sizeof(uint64_t) * 2)
+            return exchange_one_int128;
+        else
+            return exchange_int128s;
+    case 8:
+        if (size == sizeof(uint64_t))
+            return exchange_one_int64;
+        else
+            return exchange_int64s;
+    case 4:
+    case 12:
+        if (size == sizeof(uint32_t))
+            return exchange_one_int32;
+        else
+            return exchange_int32s;
+    case 2:
+    case 6:
+    case 10:
+    case 14:
+        if (size == sizeof(uint16_t))
+            return exchange_one_int16;
+        else
+            return exchange_int16s;
+    default:
+        if (size == 1)
+            return exchange_one_byte;
+        else
+            return exchange_bytes;
+    }
+}
+
+static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque)
+{
+    uint8_t *basep = (uint8_t *)base;
+    size_t i, n, c, r;
+    exchange_f swap = exchange_func(base, size);
+
+    if (nmemb > 1) {
+        i = (nmemb / 2) * size;
+        n = nmemb * size;
+
+        while (i > 0) {
+            i -= size;
+            for (r = i; (c = r * 2 + size) < n; r = c) {
+                if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0)
+                    c += size;
+                if (cmp(basep + r, basep + c, opaque) > 0)
+                    break;
+                swap(basep + r, basep + c, size);
+            }
+        }
+        for (i = n - size; i > 0; i -= size) {
+            swap(basep, basep + i, size);
+
+            for (r = 0; (c = r * 2 + size) < i; r = c) {
+                if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0)
+                    c += size;
+                if (cmp(basep + r, basep + c, opaque) > 0)
+                    break;
+                swap(basep + r, basep + c, size);
+            }
+        }
+    }
+}
+
+static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque)
+{
+    return cmp(a, b, opaque) < 0 ?
+        (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) :
+        (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c ));
+}
+
+/* pointer based version with local stack and insertion sort threshhold */
+void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque)
+{
+    struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack;
+    uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m;
+    size_t m4, i, lt, gt, span, span2;
+    int c, depth;
+    exchange_f swap = exchange_func(base, size);
+    exchange_f swap_block = exchange_func(base, size | 128);
+
+    if (nmemb < 2 || size <= 0)
+        return;
+
+    sp->base = (uint8_t *)base;
+    sp->count = nmemb;
+    sp->depth = 0;
+    sp++;
+
+    while (sp > stack) {
+        sp--;
+        ptr = sp->base;
+        nmemb = sp->count;
+        depth = sp->depth;
+
+        while (nmemb > 6) {
+            if (++depth > 50) {
+                /* depth check to ensure worst case logarithmic time */
+                heapsortx(ptr, nmemb, size, cmp, opaque);
+                nmemb = 0;
+                break;
+            }
+            /* select median of 3 from 1/4, 1/2, 3/4 positions */
+            /* should use median of 5 or 9? */
+            m4 = (nmemb >> 2) * size;
+            m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque);
+            swap(ptr, m, size);  /* move the pivot to the start or the array */
+            i = lt = 1;
+            pi = plt = ptr + size;
+            gt = nmemb;
+            pj = pgt = top = ptr + nmemb * size;
+            for (;;) {
+                while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) {
+                    if (c == 0) {
+                        swap(plt, pi, size);
+                        lt++;
+                        plt += size;
+                    }
+                    i++;
+                    pi += size;
+                }
+                while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) {
+                    if (c == 0) {
+                        gt--;
+                        pgt -= size;
+                        swap(pgt, pj, size);
+                    }
+                }
+                if (pi >= pj)
+                    break;
+                swap(pi, pj, size);
+                i++;
+                pi += size;
+            }
+            /* array has 4 parts:
+             * from 0 to lt excluded: elements identical to pivot
+             * from lt to pi excluded: elements smaller than pivot
+             * from pi to gt excluded: elements greater than pivot
+             * from gt to n excluded: elements identical to pivot
+             */
+            /* move elements identical to pivot in the middle of the array: */
+            /* swap values in ranges [0..lt[ and [i-lt..i[
+               swapping the smallest span between lt and i-lt is sufficient
+             */
+            span = plt - ptr;
+            span2 = pi - plt;
+            lt = i - lt;
+            if (span > span2)
+                span = span2;
+            swap_block(ptr, pi - span, span);
+            /* swap values in ranges [gt..top[ and [i..top-(top-gt)[
+               swapping the smallest span between top-gt and gt-i is sufficient
+             */
+            span = top - pgt;
+            span2 = pgt - pi;
+            pgt = top - span2;
+            gt = nmemb - (gt - i);
+            if (span > span2)
+                span = span2;
+            swap_block(pi, top - span, span);
+
+            /* now array has 3 parts:
+             * from 0 to lt excluded: elements smaller than pivot
+             * from lt to gt excluded: elements identical to pivot
+             * from gt to n excluded: elements greater than pivot
+             */
+            /* stack the larger segment and keep processing the smaller one
+               to minimize stack use for pathological distributions */
+            if (lt > nmemb - gt) {
+                sp->base = ptr;
+                sp->count = lt;
+                sp->depth = depth;
+                sp++;
+                ptr = pgt;
+                nmemb -= gt;
+            } else {
+                sp->base = pgt;
+                sp->count = nmemb - gt;
+                sp->depth = depth;
+                sp++;
+                nmemb = lt;
+            }
+        }
+        /* Use insertion sort for small fragments */
+        for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) {
+            for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size)
+                swap(pj, pj - size, size);
+        }
+    }
+}
+
+#endif
diff --git a/cutils.h b/cutils.h
new file mode 100644
index 0000000..26c68ee
--- /dev/null
+++ b/cutils.h
@@ -0,0 +1,293 @@
+/*
+ * C utilities
+ * 
+ * Copyright (c) 2017 Fabrice Bellard
+ * Copyright (c) 2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef CUTILS_H
+#define CUTILS_H
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+/* set if CPU is big endian */
+#undef WORDS_BIGENDIAN
+
+#define likely(x)       __builtin_expect(!!(x), 1)
+#define unlikely(x)     __builtin_expect(!!(x), 0)
+#define force_inline inline __attribute__((always_inline))
+#define no_inline __attribute__((noinline))
+#define __maybe_unused __attribute__((unused))
+
+#define xglue(x, y) x ## y
+#define glue(x, y) xglue(x, y)
+#define stringify(s)    tostring(s)
+#define tostring(s)     #s
+
+#ifndef offsetof
+#define offsetof(type, field) ((size_t) &((type *)0)->field)
+#endif
+#ifndef countof
+#define countof(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+typedef int BOOL;
+
+#ifndef FALSE
+enum {
+    FALSE = 0,
+    TRUE = 1,
+};
+#endif
+
+void pstrcpy(char *buf, int buf_size, const char *str);
+char *pstrcat(char *buf, int buf_size, const char *s);
+int strstart(const char *str, const char *val, const char **ptr);
+int has_suffix(const char *str, const char *suffix);
+
+static inline int max_int(int a, int b)
+{
+    if (a > b)
+        return a;
+    else
+        return b;
+}
+
+static inline int min_int(int a, int b)
+{
+    if (a < b)
+        return a;
+    else
+        return b;
+}
+
+static inline uint32_t max_uint32(uint32_t a, uint32_t b)
+{
+    if (a > b)
+        return a;
+    else
+        return b;
+}
+
+static inline uint32_t min_uint32(uint32_t a, uint32_t b)
+{
+    if (a < b)
+        return a;
+    else
+        return b;
+}
+
+static inline int64_t max_int64(int64_t a, int64_t b)
+{
+    if (a > b)
+        return a;
+    else
+        return b;
+}
+
+static inline int64_t min_int64(int64_t a, int64_t b)
+{
+    if (a < b)
+        return a;
+    else
+        return b;
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int clz32(unsigned int a)
+{
+    return __builtin_clz(a);
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int clz64(uint64_t a)
+{
+    return __builtin_clzll(a);
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int ctz32(unsigned int a)
+{
+    return __builtin_ctz(a);
+}
+
+/* WARNING: undefined if a = 0 */
+static inline int ctz64(uint64_t a)
+{
+    return __builtin_ctzll(a);
+}
+
+struct __attribute__((packed)) packed_u64 {
+    uint64_t v;
+};
+
+struct __attribute__((packed)) packed_u32 {
+    uint32_t v;
+};
+
+struct __attribute__((packed)) packed_u16 {
+    uint16_t v;
+};
+
+static inline uint64_t get_u64(const uint8_t *tab)
+{
+    return ((const struct packed_u64 *)tab)->v;
+}
+
+static inline int64_t get_i64(const uint8_t *tab)
+{
+    return (int64_t)((const struct packed_u64 *)tab)->v;
+}
+
+static inline void put_u64(uint8_t *tab, uint64_t val)
+{
+    ((struct packed_u64 *)tab)->v = val;
+}
+
+static inline uint32_t get_u32(const uint8_t *tab)
+{
+    return ((const struct packed_u32 *)tab)->v;
+}
+
+static inline int32_t get_i32(const uint8_t *tab)
+{
+    return (int32_t)((const struct packed_u32 *)tab)->v;
+}
+
+static inline void put_u32(uint8_t *tab, uint32_t val)
+{
+    ((struct packed_u32 *)tab)->v = val;
+}
+
+static inline uint32_t get_u16(const uint8_t *tab)
+{
+    return ((const struct packed_u16 *)tab)->v;
+}
+
+static inline int32_t get_i16(const uint8_t *tab)
+{
+    return (int16_t)((const struct packed_u16 *)tab)->v;
+}
+
+static inline void put_u16(uint8_t *tab, uint16_t val)
+{
+    ((struct packed_u16 *)tab)->v = val;
+}
+
+static inline uint32_t get_u8(const uint8_t *tab)
+{
+    return *tab;
+}
+
+static inline int32_t get_i8(const uint8_t *tab)
+{
+    return (int8_t)*tab;
+}
+
+static inline void put_u8(uint8_t *tab, uint8_t val)
+{
+    *tab = val;
+}
+
+static inline uint16_t bswap16(uint16_t x)
+{
+    return (x >> 8) | (x << 8);
+}
+
+static inline uint32_t bswap32(uint32_t v)
+{
+    return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >>  8) |
+        ((v & 0x0000ff00) <<  8) | ((v & 0x000000ff) << 24);
+}
+
+static inline uint64_t bswap64(uint64_t v)
+{
+    return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | 
+        ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | 
+        ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | 
+        ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | 
+        ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | 
+        ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | 
+        ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | 
+        ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8));
+}
+
+/* XXX: should take an extra argument to pass slack information to the caller */
+typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size);
+
+typedef struct DynBuf {
+    uint8_t *buf;
+    size_t size;
+    size_t allocated_size;
+    BOOL error; /* true if a memory allocation error occurred */
+    DynBufReallocFunc *realloc_func;
+    void *opaque; /* for realloc_func */
+} DynBuf;
+
+void dbuf_init(DynBuf *s);
+void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func);
+int dbuf_realloc(DynBuf *s, size_t new_size);
+int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len);
+int dbuf_put(DynBuf *s, const uint8_t *data, size_t len);
+int dbuf_put_self(DynBuf *s, size_t offset, size_t len);
+int dbuf_putc(DynBuf *s, uint8_t c);
+int dbuf_putstr(DynBuf *s, const char *str);
+static inline int dbuf_put_u16(DynBuf *s, uint16_t val)
+{
+    return dbuf_put(s, (uint8_t *)&val, 2);
+}
+static inline int dbuf_put_u32(DynBuf *s, uint32_t val)
+{
+    return dbuf_put(s, (uint8_t *)&val, 4);
+}
+static inline int dbuf_put_u64(DynBuf *s, uint64_t val)
+{
+    return dbuf_put(s, (uint8_t *)&val, 8);
+}
+int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s,
+                                                      const char *fmt, ...);
+void dbuf_free(DynBuf *s);
+static inline BOOL dbuf_error(DynBuf *s) {
+    return s->error;
+}
+
+#define UTF8_CHAR_LEN_MAX 6
+
+int unicode_to_utf8(uint8_t *buf, unsigned int c);
+int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp);
+
+static inline int from_hex(int c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    else if (c >= 'A' && c <= 'F')
+        return c - 'A' + 10;
+    else if (c >= 'a' && c <= 'f')
+        return c - 'a' + 10;
+    else
+        return -1;
+}
+
+void rqsort(void *base, size_t nmemb, size_t size,
+            int (*cmp)(const void *, const void *, void *),
+            void *arg);
+
+#endif  /* CUTILS_H */
diff --git a/doc/jsbignum.html b/doc/jsbignum.html
new file mode 100644
index 0000000..f3f6835
--- /dev/null
+++ b/doc/jsbignum.html
@@ -0,0 +1,1037 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<!-- Created by GNU Texinfo 6.5, http://www.gnu.org/software/texinfo/ -->
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>Javascript Bignum Extensions</title>
+
+<meta name="description" content="Javascript Bignum Extensions">
+<meta name="keywords" content="Javascript Bignum Extensions">
+<meta name="resource-type" content="document">
+<meta name="distribution" content="global">
+<meta name="Generator" content="makeinfo">
+<link href="#SEC_Contents" rel="contents" title="Table of Contents">
+<style type="text/css">
+<!--
+a.summary-letter {text-decoration: none}
+blockquote.indentedblock {margin-right: 0em}
+blockquote.smallindentedblock {margin-right: 0em; font-size: smaller}
+blockquote.smallquotation {font-size: smaller}
+div.display {margin-left: 3.2em}
+div.example {margin-left: 3.2em}
+div.lisp {margin-left: 3.2em}
+div.smalldisplay {margin-left: 3.2em}
+div.smallexample {margin-left: 3.2em}
+div.smalllisp {margin-left: 3.2em}
+kbd {font-style: oblique}
+pre.display {font-family: inherit}
+pre.format {font-family: inherit}
+pre.menu-comment {font-family: serif}
+pre.menu-preformatted {font-family: serif}
+pre.smalldisplay {font-family: inherit; font-size: smaller}
+pre.smallexample {font-size: smaller}
+pre.smallformat {font-family: inherit; font-size: smaller}
+pre.smalllisp {font-size: smaller}
+span.nolinebreak {white-space: nowrap}
+span.roman {font-family: initial; font-weight: normal}
+span.sansserif {font-family: sans-serif; font-weight: normal}
+ul.no-bullet {list-style: none}
+-->
+</style>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+
+</head>
+
+<body lang="en">
+<h1 class="settitle" align="center">Javascript Bignum Extensions</h1>
+
+<a name="SEC_Contents"></a>
+<h2 class="contents-heading">Table of Contents</h2>
+
+<div class="contents">
+<ul class="no-bullet">
+<li><a name="toc-Introduction" href="#Introduction">1 Introduction</a></li>
+<li><a name="toc-Operator-overloading" href="#Operator-overloading">2 Operator overloading</a>
+<ul class="no-bullet">
+  <li><a name="toc-Introduction-1" href="#Introduction-1">2.1 Introduction</a></li>
+  <li><a name="toc-Builtin-Object-changes" href="#Builtin-Object-changes">2.2 Builtin Object changes</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Symbol-constructor" href="#Symbol-constructor">2.2.1 <code>Symbol</code> constructor</a></li>
+  </ul></li>
+</ul></li>
+<li><a name="toc-The-BigInt-Mode" href="#The-BigInt-Mode">3 The BigInt Mode</a>
+<ul class="no-bullet">
+  <li><a name="toc-Introduction-2" href="#Introduction-2">3.1 Introduction</a></li>
+  <li><a name="toc-Changes-that-introduce-incompatibilities-with-Javascript" href="#Changes-that-introduce-incompatibilities-with-Javascript">3.2 Changes that introduce incompatibilities with Javascript</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Standard-mode" href="#Standard-mode">3.2.1 Standard mode</a></li>
+    <li><a name="toc-Bigint-mode" href="#Bigint-mode">3.2.2 Bigint mode</a></li>
+  </ul></li>
+  <li><a name="toc-Operators" href="#Operators">3.3 Operators</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Arithmetic-operators" href="#Arithmetic-operators">3.3.1 Arithmetic operators</a></li>
+    <li><a name="toc-Logical-operators" href="#Logical-operators">3.3.2 Logical operators</a></li>
+    <li><a name="toc-Relational-operators" href="#Relational-operators">3.3.3 Relational operators</a></li>
+  </ul></li>
+  <li><a name="toc-Number-literals" href="#Number-literals">3.4 Number literals</a></li>
+  <li><a name="toc-Builtin-Object-changes-1" href="#Builtin-Object-changes-1">3.5 Builtin Object changes</a>
+  <ul class="no-bullet">
+    <li><a name="toc-BigInt-function" href="#BigInt-function">3.5.1 <code>BigInt</code> function</a></li>
+    <li><a name="toc-BigInt_002eprototype" href="#BigInt_002eprototype">3.5.2 <code>BigInt.prototype</code></a></li>
+    <li><a name="toc-Number-constructor" href="#Number-constructor">3.5.3 <code>Number</code> constructor</a></li>
+    <li><a name="toc-Number_002eprototype" href="#Number_002eprototype">3.5.4 <code>Number.prototype</code></a></li>
+    <li><a name="toc-Math-object" href="#Math-object">3.5.5 <code>Math</code> object</a></li>
+  </ul></li>
+</ul></li>
+<li><a name="toc-Arbitrarily-large-floating-point-numbers" href="#Arbitrarily-large-floating-point-numbers">4 Arbitrarily large floating point numbers</a>
+<ul class="no-bullet">
+  <li><a name="toc-Introduction-3" href="#Introduction-3">4.1 Introduction</a></li>
+  <li><a name="toc-Floating-point-rounding" href="#Floating-point-rounding">4.2 Floating point rounding</a></li>
+  <li><a name="toc-Operators-1" href="#Operators-1">4.3 Operators</a></li>
+  <li><a name="toc-BigFloat-literals" href="#BigFloat-literals">4.4 BigFloat literals</a></li>
+  <li><a name="toc-Builtin-Object-changes-2" href="#Builtin-Object-changes-2">4.5 Builtin Object changes</a>
+  <ul class="no-bullet">
+    <li><a name="toc-BigFloat-function" href="#BigFloat-function">4.5.1 <code>BigFloat</code> function</a></li>
+    <li><a name="toc-BigFloat_002eprototype" href="#BigFloat_002eprototype">4.5.2 <code>BigFloat.prototype</code></a></li>
+    <li><a name="toc-BigFloatEnv-constructor" href="#BigFloatEnv-constructor">4.5.3 <code>BigFloatEnv</code> constructor</a></li>
+    <li><a name="toc-Math-object-1" href="#Math-object-1">4.5.4 <code>Math</code> object</a></li>
+  </ul></li>
+</ul></li>
+<li><a name="toc-Math-mode" href="#Math-mode">5 Math mode</a>
+<ul class="no-bullet">
+  <li><a name="toc-Introduction-4" href="#Introduction-4">5.1 Introduction</a></li>
+  <li><a name="toc-Builtin-Object-changes-3" href="#Builtin-Object-changes-3">5.2 Builtin Object changes</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Symbol-constructor-1" href="#Symbol-constructor-1">5.2.1 <code>Symbol</code> constructor</a></li>
+  </ul></li>
+  <li><a name="toc-Remaining-issues" href="#Remaining-issues">5.3 Remaining issues</a></li>
+</ul></li>
+
+</ul>
+</div>
+
+
+<a name="Introduction"></a>
+<h2 class="chapter">1 Introduction</h2>
+
+<p>The Bignum extensions add the following features to the Javascript
+language while being 100% backward compatible:
+</p>
+<ul>
+<li> Overloading of the standard operators
+to support new types such as complex numbers, fractions or matrixes.
+
+</li><li> Bigint mode where arbitrarily large integers are available by default (no <code>n</code> suffix is necessary as in the TC39 BigInt proposal<a name="DOCF1" href="#FOOT1"><sup>1</sup></a>).
+
+</li><li> Arbitrarily large floating point numbers (<code>BigFloat</code>) in base 2 using the IEEE 754 semantics.
+
+</li><li> Optional <code>math</code> mode which modifies the semantics of the division, modulo and power operator. The division and power operator return a fraction with integer operands and the modulo operator is defined as the Euclidian remainder.
+
+</li></ul>
+
+<p>The extensions are independent from each other except the <code>math</code>
+mode which relies on the bigint mode and the operator overloading.
+</p>
+<a name="Operator-overloading"></a>
+<h2 class="chapter">2 Operator overloading</h2>
+
+<a name="Introduction-1"></a>
+<h3 class="section">2.1 Introduction</h3>
+
+<p>If the operands of an operator have at least one object type, a custom
+operator method is searched before doing the legacy Javascript
+<code>ToNumber</code> conversion.
+</p>
+<p>For unary operators, the custom function is looked up in the object
+and has the following name:
+</p>
+<dl compact="compact">
+<dt><code>unary +</code></dt>
+<dd><p><code>Symbol.operatorPlus</code>
+</p>
+</dd>
+<dt><code>unary -</code></dt>
+<dd><p><code>Symbol.operatorNeg</code>
+</p>
+</dd>
+<dt><code>++</code></dt>
+<dd><p><code>Symbol.operatorInc</code>
+</p>
+</dd>
+<dt><code>--</code></dt>
+<dd><p><code>Symbol.operatorDec</code>
+</p>
+</dd>
+<dt><code>~</code></dt>
+<dd><p><code>Symbol.operatorNot</code>
+</p>
+</dd>
+</dl>
+
+<p>For binary operators:
+</p>
+<ul>
+<li> If both operands have the same constructor function, then the operator
+is looked up in the constructor.
+
+</li><li> Otherwise, the property <code>Symbol.operatorOrder</code> is looked up in both
+constructors and converted to <code>Int32</code>. The operator is then
+looked in the constructor with the larger <code>Symbol.operatorOrder</code>
+value. A <code>TypeError</code> is raised if both constructors have the same
+<code>Symbol.operatorOrder</code> value.
+
+</li></ul>
+
+<p>The operator is looked up with the following name:
+</p>
+<dl compact="compact">
+<dt><code>+</code></dt>
+<dd><p><code>Symbol.operatorAdd</code>
+</p>
+</dd>
+<dt><code>-</code></dt>
+<dd><p><code>Symbol.operatorSub</code>
+</p>
+</dd>
+<dt><code>*</code></dt>
+<dd><p><code>Symbol.operatorMul</code>
+</p>
+</dd>
+<dt><code>/</code></dt>
+<dd><p><code>Symbol.operatorDiv</code>
+</p>
+</dd>
+<dt><code>%</code></dt>
+<dd><p><code>Symbol.operatorMod</code>
+</p>
+</dd>
+<dt><code>% (math mode)</code></dt>
+<dd><p><code>Symbol.operatorMathMod</code>
+</p>
+</dd>
+<dt><code>**</code></dt>
+<dd><p><code>Symbol.operatorPow</code>
+</p>
+</dd>
+<dt><code>|</code></dt>
+<dd><p><code>Symbol.operatorOr</code>
+</p>
+</dd>
+<dt><code>^</code></dt>
+<dd><p><code>Symbol.operatorXor</code>
+</p>
+</dd>
+<dt><code>&amp;</code></dt>
+<dd><p><code>Symbol.operatorAnd</code>
+</p>
+</dd>
+<dt><code>&lt;&lt;</code></dt>
+<dd><p><code>Symbol.operatorShl</code>
+</p>
+</dd>
+<dt><code>&gt;&gt;</code></dt>
+<dd><p><code>Symbol.operatorShr</code>
+</p>
+</dd>
+<dt><code>&lt;</code></dt>
+<dd><p><code>Symbol.operatorCmpLT</code>
+</p>
+</dd>
+<dt><code>&gt;</code></dt>
+<dd><p><code>Symbol.operatorCmpLT</code>, operands swapped
+</p>
+</dd>
+<dt><code>&lt;=</code></dt>
+<dd><p><code>Symbol.operatorCmpLE</code>
+</p>
+</dd>
+<dt><code>&gt;=</code></dt>
+<dd><p><code>Symbol.operatorCmpLE</code>, operands swapped
+</p>
+</dd>
+<dt><code>==, !=</code></dt>
+<dd><p><code>Symbol.operatorCmpEQ</code>
+</p>
+</dd>
+</dl>
+
+<p>The return value of <code>Symbol.operatorCmpLT</code>, <code>Symbol.operatorCmpLE</code> and
+<code>Symbol.operatorCmpEQ</code> is converted to <code>Boolean</code>.
+</p>
+<a name="Builtin-Object-changes"></a>
+<h3 class="section">2.2 Builtin Object changes</h3>
+
+<a name="Symbol-constructor"></a>
+<h4 class="subsection">2.2.1 <code>Symbol</code> constructor</h4>
+
+<p>The following global symbols are added for the operator overloading:
+</p><dl compact="compact">
+<dt><code>operatorOrder</code></dt>
+<dt><code>operatorAdd</code></dt>
+<dt><code>operatorSub</code></dt>
+<dt><code>operatorMul</code></dt>
+<dt><code>operatorDiv</code></dt>
+<dt><code>operatorMod</code></dt>
+<dt><code>operatorPow</code></dt>
+<dt><code>operatorShl</code></dt>
+<dt><code>operatorShr</code></dt>
+<dt><code>operatorAnd</code></dt>
+<dt><code>operatorOr</code></dt>
+<dt><code>operatorXor</code></dt>
+<dt><code>operatorCmpLT</code></dt>
+<dt><code>operatorCmpLE</code></dt>
+<dt><code>operatorCmpEQ</code></dt>
+<dt><code>operatorPlus</code></dt>
+<dt><code>operatorNeg</code></dt>
+<dt><code>operatorNot</code></dt>
+<dt><code>operatorInc</code></dt>
+<dt><code>operatorDec</code></dt>
+</dl>
+
+
+<a name="The-BigInt-Mode"></a>
+<h2 class="chapter">3 The BigInt Mode</h2>
+
+<a name="Introduction-2"></a>
+<h3 class="section">3.1 Introduction</h3>
+
+<p>The bigint mode is enabled with the <code>&quot;use bigint&quot;</code> directive. It
+propagates the same way as the strict mode. In bigint mode, all
+integers are considered as <code>bigint</code> (arbitrarily large integer,
+similar to the TC39 BigInt
+proposal<a name="DOCF2" href="#FOOT2"><sup>2</sup></a>)
+instead of <code>number</code> (floating point number). In order to be able
+to exchange data between standard and bigint modes, numbers are
+internally represented as 3 different types:
+</p>
+<ul>
+<li> Small integer (SmallInt): 32 bit integer<a name="DOCF3" href="#FOOT3"><sup>3</sup></a>.
+
+</li><li> Big integer (BigInt): arbitrarily large integer.
+
+</li><li> Floating point number (Float).
+
+</li></ul>
+
+<p>In standard mode, the semantics of each operation is modified so that
+when it returns a <code>number</code>, it is either of SmallInt or
+Float. But the difference between SmallInt and Float is not observable
+in standard mode.
+</p>
+<p>In bigint mode, each operation behaves differently whether its
+operands are integer or float. The difference between SmallInt and
+BigInt is not observable (i.e. they are both integers).
+</p>
+<p>The following table summarizes the observable types:
+</p>
+<table>
+<thead><tr><th width="30%">Internal type</th><th width="30%">Observable type<br> (standard mode)</th><th width="30%">Observable type<br> (bigint mode)</th></tr></thead>
+<tr><td width="30%">SmallInt</td><td width="30%">number</td><td width="30%">bigint</td></tr>
+<tr><td width="30%">BigInt</td><td width="30%">bigint</td><td width="30%">bigint</td></tr>
+<tr><td width="30%">Float</td><td width="30%">number</td><td width="30%">number</td></tr>
+</table>
+
+<a name="Changes-that-introduce-incompatibilities-with-Javascript"></a>
+<h3 class="section">3.2 Changes that introduce incompatibilities with Javascript</h3>
+
+<a name="Standard-mode"></a>
+<h4 class="subsection">3.2.1 Standard mode</h4>
+
+<p>There is no incompatibility with Javascript.
+</p>
+<a name="Bigint-mode"></a>
+<h4 class="subsection">3.2.2 Bigint mode</h4>
+
+<p>The following changes are visible:
+</p>
+<ul>
+<li> Integer and Float are different types. Constants are typed. For example: <code>typeof 1.0 === &quot;number&quot;</code> and <code>typeof 1 === &quot;bigint&quot;</code>. Another consequence is that <code>1.0 === 1</code> is false.
+
+</li><li> The range of integers is unlimited. In standard mode: <code>2**53 + 1 === 2**53</code>. This is no longer true with the bignum extensions.
+
+</li><li> Binary bitwise operators do not truncate to 32 bits i.e. <code>0x800000000 | 1 === 0x800000001</code> while it gives <code>1</code> in standard mode.
+
+</li><li> Bitwise shift operators do not truncate to 32 bits and do not mask the shift count with <code>0x1f</code> i.e. <code>1 &lt;&lt; 32 === 4294967296</code> while it gives <code>1</code> in standard mode. However, the <code>&gt;&gt;&gt;</code> operator (unsigned right shift) which is useless with bignums keeps its standard mode behavior<a name="DOCF4" href="#FOOT4"><sup>4</sup></a>.
+
+</li><li> Operators with integer operands never return the minus zero floating point value as result. Hence <code>Object.is(0, -0) === true</code>. Use <code>-0.0</code> to create a minus zero floating point value.
+
+</li><li> The <code>ToPrimitive</code> abstract operation is called with the <code>&quot;integer&quot;</code> preferred type when an integer is required (e.g. for bitwise binary or shift operations).
+
+</li><li> The prototype of integers is no longer <code>Number.prototype</code>. Instead<br> <code>Object.getPrototypeOf(1) === BigInt.prototype</code>. The prototype of floats remains Number.prototype.
+
+</li><li> If the TC39 BigInt proposal is supported, there is no observable difference between integers and <code>bigint</code>s.
+
+</li></ul>
+
+<a name="Operators"></a>
+<h3 class="section">3.3 Operators</h3>
+
+<a name="Arithmetic-operators"></a>
+<h4 class="subsection">3.3.1 Arithmetic operators</h4>
+
+<p>The operands are converted to number values as in normal
+Javascript. Then the general case is that an Integer is returned if
+both operands are Integer. Otherwise, a float is returned.
+</p>
+<p>The <code>+</code> operator also accepts strings as input and behaves like
+standard Javascript in this case.
+</p>
+<p>The binary operator <code>%</code> returns the truncated remainder of the
+division. When the result is an Integer type, a dividend of zero yields a
+RangeError exception.
+</p>
+<p>The binary operator <code>%</code> in math mode returns the Euclidian
+remainder of the division i.e. it is always positive. 
+</p>
+<p>The binary operator <code>/</code> returns a float.
+</p>
+<p>The binary operator <code>/</code> in math mode returns a float if one of
+the operands is float. Otherwise, <code>BigInt[Symbol.operatorDiv]</code> is
+invoked.
+</p>
+<p>The returned type of <code>a ** b</code> is Float if <em>a</em> or <em>b</em>
+are Float. If <em>a</em> and <em>b</em> are integers:
+</p><ul>
+<li> <em>b &lt; 0</em> returns a Float in bigint mode. In math mode, <code>BigInt[Symbol.operatorPow]</code> is invoked.
+
+</li><li> <em>b &gt;= 0</em> returns an integer.
+</li></ul>
+
+<p>The unary <code>-</code> and unary <code>+</code> return the same type as their
+operand. They performs no floating point rounding when the result is a
+float.
+</p>
+<p>The unary operators <code>++</code> and <code>--</code> return the same type as
+their operand.
+</p>
+<p>In standard mode:
+</p>
+<p>If the operator returns an Integer and that the result fits a
+SmallInt, it is converted to SmallInt. Otherwise, the Integer is
+converted to a Float.
+</p>
+<p>In bigint mode:
+</p>
+<p>If the operator returns an Integer and that the result fits a
+SmallInt, it is converted to SmallInt. Otherwise it is a BigInt.
+</p>
+<a name="Logical-operators"></a>
+<h4 class="subsection">3.3.2 Logical operators</h4>
+
+<p>In standard mode:
+</p>
+<p>The operands have their standard behavior. If the result fits a
+SmallInt it is converted to a SmallInt. Otherwise it is a Float.
+</p>
+<p>In bigint mode:
+</p>
+<p>The operands are converted to integer values. The floating point
+values are converted to integer by rounding them to zero.
+</p>
+<p>The logical operators are defined assuming the integers are
+represented in two complement notation.
+</p>
+<p>For <code>&lt;&lt;</code> and <code>&lt;&lt;</code>, the shift can be positive or negative. So
+<code>a &lt;&lt; b</code> is defined as <em>\lfloor a/2^{-b} \rfloor</em> and
+<code>a &gt;&gt; b</code> is defined as <em>\lfloor a/2^{b} \rfloor</em>.
+</p>
+<p>The operator <code>&gt;&gt;&gt;</code> is supported for backward compatibility and
+behaves the same way as Javascript i.e. implicit conversion to <code>Uint32</code>.
+</p>
+<p>If the result fits a SmallInt it is converted to a SmallInt. Otherwise
+it is a BigInt.
+</p>
+<a name="Relational-operators"></a>
+<h4 class="subsection">3.3.3 Relational operators</h4>
+
+<p>The relational operators &lt;, &lt;=, &gt;, &gt;=, ==, != work as expected with
+integers and floating point numbers (e.g. <code>1.0 == 1</code> is true).
+</p>
+<p>The strict equality operators === and !== have the usual Javascript
+semantics. In particular, different types never equal, so <code>1.0
+=== 1</code> is false.
+</p>
+<a name="Number-literals"></a>
+<h3 class="section">3.4 Number literals</h3>
+
+<p>Number literals in bigint mode have a slightly different behavior than
+in standard Javascript:
+</p>
+<ol>
+<li> A number literal without a decimal point or an exponent is considered
+as an Integer. Otherwise it is a Float.
+
+</li><li> Hexadecimal, octal or binary floating point literals are accepted with
+a decimal point or an exponent. The exponent is specified with the
+<code>p</code> letter assuming a base 2. The same convention is used by
+C99. Example: <code>0x1p3</code> is the same as <code>8.0</code>.
+
+</li></ol>
+
+<a name="Builtin-Object-changes-1"></a>
+<h3 class="section">3.5 Builtin Object changes</h3>
+
+<a name="BigInt-function"></a>
+<h4 class="subsection">3.5.1 <code>BigInt</code> function</h4>
+
+<p>The <code>BigInt</code> function cannot be invoked as a constructor. When
+invoked as a function, it converts its first parameter to an
+integer. When a floating point number is given as parameter, it is
+truncated to an integer with infinite precision.
+</p>
+<p><code>BigInt</code> properties:
+</p>
+<dl compact="compact">
+<dt><code>asIntN(bits, a)</code></dt>
+<dd><p>Set <em>b=a \pmod{2^{bits}}</em>. Return <em>b</em> if <em>b &lt; 2^{bits-1}</em>
+otherwise <em>b-2^{bits}</em>.
+</p>
+</dd>
+<dt><code>asUintN(bits, a)</code></dt>
+<dd><p>Return <em>a \pmod{2^{bits}}</em>.
+</p>
+</dd>
+<dt><code>tdiv(a, b)</code></dt>
+<dd><p>Return <em>trunc(a/b)</em>. <code>b = 0</code> raises a RangeError
+exception.
+</p>
+</dd>
+<dt><code>fdiv(a, b)</code></dt>
+<dd><p>Return <em>\lfloor a/b \rfloor</em>. <code>b = 0</code> raises a RangeError
+exception.
+</p>
+</dd>
+<dt><code>cdiv(a, b)</code></dt>
+<dd><p>Return <em>\lceil a/b \rceil</em>. <code>b = 0</code> raises a RangeError
+exception.
+</p>
+</dd>
+<dt><code>ediv(a, b)</code></dt>
+<dd><p>Return <em>sgn(b) \lfloor a/{|b|} \rfloor</em> (Euclidian
+division). <code>b = 0</code> raises a RangeError exception.
+</p>
+</dd>
+<dt><code>tdivrem(a, b)</code></dt>
+<dt><code>fdivrem(a, b)</code></dt>
+<dt><code>cdivrem(a, b)</code></dt>
+<dt><code>edivrem(a, b)</code></dt>
+<dd><p>Return an array of two elements. The first element is the quotient,
+the second is the remainder. The same rounding is done as the
+corresponding division operation.
+</p>
+</dd>
+<dt><code>sqrt(a)</code></dt>
+<dd><p>Return <em>\lfloor \sqrt(a) \rfloor</em>. A RangeError exception is
+raised if <em>a &lt; 0</em>.
+</p>
+</dd>
+<dt><code>sqrtrem(a)</code></dt>
+<dd><p>Return an array of two elements. The first element is <em>\lfloor
+\sqrt{a} \rfloor</em>. The second element is <em>a-\lfloor \sqrt{a}
+\rfloor^2</em>. A RangeError exception is raised if <em>a &lt; 0</em>.
+</p>
+</dd>
+<dt><code>floorLog2(a)</code></dt>
+<dd><p>Return -1 if <em>a \leq 0</em> otherwise return <em>\lfloor \log2(a) \rfloor</em>.
+</p>
+</dd>
+<dt><code>ctz(a)</code></dt>
+<dd><p>Return the number of trailing zeros in the two&rsquo;s complement binary representation of a. Return -1 if <em>a=0</em>.
+</p>
+</dd>
+</dl>
+
+<a name="BigInt_002eprototype"></a>
+<h4 class="subsection">3.5.2 <code>BigInt.prototype</code></h4>
+
+<p>It is a normal object.
+</p>
+<a name="Number-constructor"></a>
+<h4 class="subsection">3.5.3 <code>Number</code> constructor</h4>
+
+<p>The number constructor returns its argument rounded to a Float using
+the global floating point environement. In bigint mode, the Number
+constructor returns a Float. In standard mode, it returns a SmallInt
+if the value fits it, otherwise a Float.
+</p>
+<a name="Number_002eprototype"></a>
+<h4 class="subsection">3.5.4 <code>Number.prototype</code></h4>
+
+<p>The following properties are modified:
+</p>
+<dl compact="compact">
+<dt><code>toString(radix)</code></dt>
+<dd>
+<p>In bigint mode, integers are converted to the specified radix with
+infinite precision.
+</p>
+</dd>
+<dt><code>toPrecision(p)</code></dt>
+<dt><code>toFixed(p)</code></dt>
+<dt><code>toExponential(p)</code></dt>
+<dd>
+<p>In bigint mode, integers are accepted and converted to string with
+infinite precision.
+</p>
+</dd>
+<dt><code>parseInt(string, radix)</code></dt>
+<dd>
+<p>In bigint mode, an integer is returned and the conversion is done with
+infinite precision.
+</p>
+</dd>
+</dl>
+
+<a name="Math-object"></a>
+<h4 class="subsection">3.5.5 <code>Math</code> object</h4>
+
+<p>The following properties are modified:
+</p>
+<dl compact="compact">
+<dt><code>abs(x)</code></dt>
+<dd><p>Absolute value. Return an integer if <code>x</code> is an Integer. Otherwise
+return a Float. No rounding is performed.
+</p>
+</dd>
+<dt><code>min(a, b)</code></dt>
+<dt><code>max(a, b)</code></dt>
+<dd><p>No rounding is performed. The returned type is the same one as the
+minimum (resp. maximum) value.
+</p>
+</dd>
+</dl>
+
+<a name="Arbitrarily-large-floating-point-numbers"></a>
+<h2 class="chapter">4 Arbitrarily large floating point numbers</h2>
+
+<a name="Introduction-3"></a>
+<h3 class="section">4.1 Introduction</h3>
+
+<p>This extension adds the <code>BigFloat</code> primitive type. The
+<code>BigFloat</code> type represents floating point numbers are in base 2
+with the IEEE 754 semantics. A floating
+point number is represented as a sign, mantissa and exponent. The
+special values <code>NaN</code>, <code>+/-Infinity</code>, <code>+0</code> and <code>-0</code>
+are supported. The mantissa and exponent can have any bit length with
+an implementation specific minimum and maximum.
+</p>
+<a name="Floating-point-rounding"></a>
+<h3 class="section">4.2 Floating point rounding</h3>
+
+<p>Each floating point operation operates with infinite precision and
+then rounds the result according to the specified floating point
+environment (<code>BigFloatEnv</code> object). The status flags of the
+environment are also set according to the result of the operation.
+</p>
+<p>If no floating point environment is provided, the global floating
+point environment is used.
+</p>
+<p>The rounding mode of the global floating point environment is always
+<code>RNDN</code> (&ldquo;round to nearest with ties to even&rdquo;)<a name="DOCF5" href="#FOOT5"><sup>5</sup></a>. The status flags of the global environment cannot be
+read<a name="DOCF6" href="#FOOT6"><sup>6</sup></a>. The precision of the global environment is
+<code>BigFloatEnv.prec</code>. The number of exponent bits of the global
+environment is <code>BigFloatEnv.expBits</code>.  If <code>BigFloatEnv.expBits</code> is
+strictly smaller than the maximum allowed number of exponent bits
+(<code>BigFloatEnv.expBitsMax</code>), then the global environment subnormal
+flag is set to <code>true</code>. Otherwise it is set to <code>false</code>;
+</p>
+<p>For example, <code>prec = 53</code> and <code> expBits = 11</code> give exactly
+the same precision as the IEEE 754 64 bit floating point type. It is
+the default floating point precision.
+</p>
+<p>The global floating point environment can only be modified temporarily
+when calling a function (see <code>BigFloatEnv.setPrec</code>). Hence a
+function can change the global floating point environment for its
+callees but not for its caller.
+</p>
+<a name="Operators-1"></a>
+<h3 class="section">4.3 Operators</h3>
+
+<p>The builtin operators are extended so that a BigFloat is returned if
+at least one operand is a BigFloat. The computations are always done
+with infinite precision and rounded according to the global floating
+point environment.
+</p>
+<p><code>typeof</code> applied on a <code>BigFloat</code> returns <code>bigfloat</code>.
+</p>
+<p>BigFloat can be compared with all the other numeric types and the
+result follows the expected mathematical relations.
+</p>
+<p>However, since BigFloat and Number are different types they are never
+equal when using the strict comparison operators (e.g. <code>0.0 ===
+0.0l</code> is false).
+</p>
+<a name="BigFloat-literals"></a>
+<h3 class="section">4.4 BigFloat literals</h3>
+
+<p>BigFloat literals are floating point numbers with a trailing <code>l</code>
+suffix. BigFloat literals have an infinite precision. They are rounded
+according to the global floating point environment when they are
+evaluated.<a name="DOCF7" href="#FOOT7"><sup>7</sup></a>
+</p>
+<a name="Builtin-Object-changes-2"></a>
+<h3 class="section">4.5 Builtin Object changes</h3>
+
+<a name="BigFloat-function"></a>
+<h4 class="subsection">4.5.1 <code>BigFloat</code> function</h4>
+
+<p>The <code>BigFloat</code> function cannot be invoked as a constructor. When
+invoked as a function: the parameter is converted to a primitive
+type. If the result is a numeric type, it is converted to BigFloat
+without rounding. If the result is a string, it is converted to
+BigFloat using the precision of the global floating point environment.
+</p>
+<p><code>BigFloat</code> properties:
+</p>
+<dl compact="compact">
+<dt><code>LN2</code></dt>
+<dt><code>PI</code></dt>
+<dd><p>Getter. Return the value of the corresponding mathematical constant
+rounded to nearest, ties to even with the current global
+precision. The constant values are cached for small precisions.
+</p>
+</dd>
+<dt><code>MIN_VALUE</code></dt>
+<dt><code>MAX_VALUE</code></dt>
+<dt><code>EPSILON</code></dt>
+<dd><p>Getter. Return the minimum, maximum and epsilon <code>BigFloat</code> values
+(same definition as the corresponding <code>Number</code> constants).
+</p>
+</dd>
+<dt><code>fpRound(a[, e])</code></dt>
+<dd><p>Round the floating point number <code>a</code> according to the floating
+point environment <code>e</code> or the global environment if <code>e</code> is
+undefined.
+</p>
+</dd>
+<dt><code>parseFloat(a[, radix[, e]])</code></dt>
+<dd><p>Parse the string <code>a</code> as a floating point number in radix
+<code>radix</code>. The radix is 0 (default) or from 2 to 36. The radix 0
+means radix 10 unless there is a hexadecimal or binary prefix. The
+result is rounded according to the floating point environment <code>e</code>
+or the global environment if <code>e</code> is undefined.
+</p>
+</dd>
+<dt><code>add(a, b[, e])</code></dt>
+<dt><code>sub(a, b[, e])</code></dt>
+<dt><code>mul(a, b[, e])</code></dt>
+<dt><code>div(a, b[, e])</code></dt>
+<dd><p>Perform the specified floating point operation and round the floating
+point number <code>a</code> according to the floating point environment
+<code>e</code> or the global environment if <code>e</code> is undefined. If
+<code>e</code> is specified, the floating point status flags are updated.
+</p>
+</dd>
+<dt><code>floor(x[, e])</code></dt>
+<dt><code>ceil(x[, e])</code></dt>
+<dt><code>round(x[, e])</code></dt>
+<dt><code>trunc(x[, e])</code></dt>
+<dd><p>Round to integer. A rounded <code>BigFloat</code> is returned. <code>e</code> is an
+optional floating point environment.
+</p>
+</dd>
+<dt><code>fmod(x, y[, e])</code></dt>
+<dt><code>remainder(x, y[, e])</code></dt>
+<dd><p>Floating point remainder. The quotient is truncated to zero (fmod) or
+to the nearest integer with ties to even (remainder). <code>e</code> is an
+optional floating point environment.
+</p>
+</dd>
+<dt><code>sqrt(x[, e])</code></dt>
+<dd><p>Square root. Return a rounded floating point number. <code>e</code> is an
+optional floating point environment.
+</p>
+</dd>
+<dt><code>sin(x[, e])</code></dt>
+<dt><code>cos(x[, e])</code></dt>
+<dt><code>tan(x[, e])</code></dt>
+<dt><code>asin(x[, e])</code></dt>
+<dt><code>acos(x[, e])</code></dt>
+<dt><code>atan(x[, e])</code></dt>
+<dt><code>atan2(x, y[, e])</code></dt>
+<dt><code>exp(x[, e])</code></dt>
+<dt><code>log(x[, e])</code></dt>
+<dt><code>pow(x, y[, e])</code></dt>
+<dd><p>Transcendental operations. Return a rounded floating point
+number. <code>e</code> is an optional floating point environment.
+</p>
+</dd>
+</dl>
+
+<a name="BigFloat_002eprototype"></a>
+<h4 class="subsection">4.5.2 <code>BigFloat.prototype</code></h4>
+
+<p>The following properties are modified:
+</p>
+<dl compact="compact">
+<dt><code>toString(radix)</code></dt>
+<dd>
+<p>For floating point numbers:
+</p>
+<ul>
+<li> If the radix is a power of two, the conversion is done with infinite
+precision.
+</li><li> Otherwise, the number is rounded to nearest with ties to even using
+the global precision. It is then converted to string using the minimum
+number of digits so that its conversion back to a floating point using
+the global precision and round to nearest gives the same number. 
+
+</li></ul>
+
+</dd>
+<dt><code>toPrecision(p[, rnd_mode])</code></dt>
+<dt><code>toFixed(p[, rnd_mode])</code></dt>
+<dt><code>toExponential(p[, rnd_mode])</code></dt>
+<dd><p>Same semantics as the corresponding <code>Number</code> functions with
+BigFloats. There is no limit on the accepted precision <code>p</code>. The
+rounding mode can be optionally specified. It is set by default to
+<code>BigFloatEnv.RNDNA</code>.
+</p>
+</dd>
+</dl>
+
+<a name="BigFloatEnv-constructor"></a>
+<h4 class="subsection">4.5.3 <code>BigFloatEnv</code> constructor</h4>
+
+<p>The <code>BigFloatEnv([p, [,rndMode]]</code> constructor cannot be invoked as a
+function. The floating point environment contains:
+</p>
+<ul>
+<li> the mantissa precision in bits
+
+</li><li> the exponent size in bits assuming an IEEE 754 representation;
+
+</li><li> the subnormal flag (if true, subnormal floating point numbers can
+be generated by the floating point operations).
+
+</li><li> the rounding mode
+
+</li><li> the floating point status. The status flags can only be set by the floating point operations. They can be reset with <code>BigFloatEnv.prototype.clearStatus()</code> or with the various status flag setters.
+
+</li></ul>
+
+<p><code>new BigFloatEnv([p, [,rndMode]]</code> creates a new floating point
+environment. The status flags are reset. If no parameter is given the
+precision, exponent bits and subnormal flags are copied from the
+global floating point environment. Otherwise, the precision is set to
+<code>p</code>, the number of exponent bits is set to <code>expBitsMax</code> and the
+subnormal flags is set to <code>false</code>. If <code>rndMode</code> is
+<code>undefined</code>, the rounding mode is set to <code>RNDN</code>.
+</p>
+<p><code>BigFloatEnv</code> properties:
+</p>
+<dl compact="compact">
+<dt><code>prec</code></dt>
+<dd><p>Getter. Return the precision in bits of the global floating point
+environment. The initial value is <code>53</code>.
+</p>
+</dd>
+<dt><code>expBits</code></dt>
+<dd><p>Getter. Return the exponent size in bits of the global floating point
+environment assuming an IEEE 754 representation. If <code>expBits &lt;
+expBitsMax</code>, then subnormal numbers are supported. The initial value
+is <code>11</code>.
+</p>
+</dd>
+<dt><code>setPrec(f, p[, e])</code></dt>
+<dd><p>Set the precision of the global floating point environment to <code>p</code>
+and the exponent size to <code>e</code> then call the function
+<code>f</code>. Then the Float precision and exponent size are reset to
+their precious value and the return value of <code>f</code> is returned (or
+an exception is raised if <code>f</code> raised an exception). If <code>e</code>
+is <code>undefined</code> it is set to <code>BigFloatEnv.expBitsMax</code>. <code>p</code>
+must be &gt;= 53 and <code>e</code> must be &gt;= 11 so that the global precision
+is at least equivalent to the IEEE 754 64 bit doubles.
+</p>
+</dd>
+<dt><code>precMin</code></dt>
+<dd><p>Read-only integer. Return the minimum allowed precision. Must be at least 2.
+</p>
+</dd>
+<dt><code>precMax</code></dt>
+<dd><p>Read-only integer. Return the maximum allowed precision. Must be at least 53.
+</p>
+</dd>
+<dt><code>expBitsMin</code></dt>
+<dd><p>Read-only integer. Return the minimum allowed exponent size in
+bits. Must be at least 3.
+</p>
+</dd>
+<dt><code>expBitsMax</code></dt>
+<dd><p>Read-only integer. Return the maximum allowed exponent size in
+bits. Must be at least 11.
+</p>
+</dd>
+<dt><code>RNDN</code></dt>
+<dd><p>Read-only integer. Round to nearest, with ties to even rounding mode.
+</p>
+</dd>
+<dt><code>RNDZ</code></dt>
+<dd><p>Read-only integer. Round to zero rounding mode.
+</p>
+</dd>
+<dt><code>RNDD</code></dt>
+<dd><p>Read-only integer. Round to -Infinity rounding mode.
+</p>
+</dd>
+<dt><code>RNDU</code></dt>
+<dd><p>Read-only integer. Round to +Infinity rounding mode.
+</p>
+</dd>
+<dt><code>RNDNA</code></dt>
+<dd><p>Read-only integer. Round to nearest, with ties away from zero rounding mode.
+</p>
+</dd>
+<dt><code>RNDNU</code></dt>
+<dd><p>Read-only integer. Round to nearest, with ties to +Infinity rounding mode.
+</p>
+</dd>
+<dt><code>RNDF<a name="DOCF8" href="#FOOT8"><sup>8</sup></a></code></dt>
+<dd><p>Read-only integer. Faithful rounding mode. The result is
+non-deterministicly rounded to -Infinity or +Infinity. This rounding
+mode usually gives a faster and deterministic running time for the
+floating point operations.
+</p>
+</dd>
+</dl>
+
+<p><code>BigFloatEnv.prototype</code> properties:
+</p>
+<dl compact="compact">
+<dt><code>prec</code></dt>
+<dd><p>Getter and setter (Integer). Return or set the precision in bits.
+</p>
+</dd>
+<dt><code>expBits</code></dt>
+<dd><p>Getter and setter (Integer). Return or set the exponent size in bits
+assuming an IEEE 754 representation.
+</p>
+</dd>
+<dt><code>rndMode</code></dt>
+<dd><p>Getter and setter (Integer). Return or set the rounding mode.
+</p>
+</dd>
+<dt><code>subnormal</code></dt>
+<dd><p>Getter and setter (Boolean). subnormal flag. It is false when
+<code>expBits = expBitsMax</code>.
+</p>
+</dd>
+<dt><code>clearStatus()</code></dt>
+<dd><p>Clear the status flags.
+</p>
+</dd>
+<dt><code>invalidOperation</code></dt>
+<dt><code>divideByZero</code></dt>
+<dt><code>overflow</code></dt>
+<dt><code>underflow</code></dt>
+<dt><code>inexact</code></dt>
+<dd><p>Getter and setter (Boolean). Status flags.
+</p>
+</dd>
+</dl>
+
+<a name="Math-object-1"></a>
+<h4 class="subsection">4.5.4 <code>Math</code> object</h4>
+
+<p>The following properties are modified:
+</p>
+<dl compact="compact">
+<dt><code>abs(x)</code></dt>
+<dd><p>Absolute value. If <code>x</code> is a BigFloat, its absolute value is
+returned as a BigFloat. No rounding is performed.
+</p>
+</dd>
+<dt><code>min(a, b)</code></dt>
+<dt><code>max(a, b)</code></dt>
+<dd><p>The returned type is the same one as the minimum (resp. maximum)
+value, so <code>BigFloat</code> values are accepted. When a <code>BigFloat</code>
+is returned, no rounding is performed.
+</p>
+</dd>
+</dl>
+
+<a name="Math-mode"></a>
+<h2 class="chapter">5 Math mode</h2>
+
+<a name="Introduction-4"></a>
+<h3 class="section">5.1 Introduction</h3>
+
+<p>A new <em>math mode</em> is enabled with the <code>&quot;use math&quot;</code>
+directive. <code>&quot;use bigint&quot;</code> is implied in math mode. With this
+mode, writing mathematical expressions is more intuitive, exact
+results (e.g. fractions) can be computed for all operators and floating
+point literals have the <code>BigFloat</code> type by default.
+</p>
+<p>It propagates the same way as the <em>strict mode</em>. In
+this mode:
+</p>
+<ul>
+<li> The <code>^</code> operator is a similar to the power operator (<code>**</code>).
+
+</li><li> The power operator (both <code>^</code> and <code>**</code>) grammar is modified so that <code>-2^2</code> is allowed and yields <code>-4</code>.
+
+</li><li> The logical xor operator is still available with the <code>^^</code> operator.
+
+</li><li> The division operator invokes <code>BigInt[Symbol.operatorDiv]</code> in case both operands are integers.
+
+</li><li> The power operator invokes <code>BigInt[Symbol.operatorPow]</code> in case both operands are integers and the exponent is strictly negative.
+
+</li><li> The modulo operator returns the Euclidian remainder (always positive) instead of the truncated remainder.
+
+</li><li> Floating point literals are <code>BigFloat</code> by default (i.e. a <code>l</code> suffix is implied).
+
+</li></ul>
+
+<a name="Builtin-Object-changes-3"></a>
+<h3 class="section">5.2 Builtin Object changes</h3>
+
+<a name="Symbol-constructor-1"></a>
+<h4 class="subsection">5.2.1 <code>Symbol</code> constructor</h4>
+
+<p>The following global symbol is added for the operator overloading:
+</p><dl compact="compact">
+<dt><code>operatorMathMod</code></dt>
+</dl>
+
+<a name="Remaining-issues"></a>
+<h3 class="section">5.3 Remaining issues</h3>
+
+<ol>
+<li> A new floating point literal suffix could be added for <code>Number</code> literals.
+
+</li></ol>
+
+<div class="footnote">
+<hr>
+<h4 class="footnotes-heading">Footnotes</h4>
+
+<h3><a name="FOOT1" href="#DOCF1">(1)</a></h3>
+<p><a href="https://tc39.github.io/proposal-bigint/">https://tc39.github.io/proposal-bigint/</a></p>
+<h3><a name="FOOT2" href="#DOCF2">(2)</a></h3>
+<p><a href="https://tc39.github.io/proposal-bigint/">https://tc39.github.io/proposal-bigint/</a></p>
+<h3><a name="FOOT3" href="#DOCF3">(3)</a></h3>
+<p>Could be extended to 53 bits without changing the principle.</p>
+<h3><a name="FOOT4" href="#DOCF4">(4)</a></h3>
+<p>The unsigned right right operator could be removed in bigint mode.</p>
+<h3><a name="FOOT5" href="#DOCF5">(5)</a></h3>
+<p>The
+rationale is that the rounding mode changes must always be
+explicit.</p>
+<h3><a name="FOOT6" href="#DOCF6">(6)</a></h3>
+<p>The rationale is to avoid side effects for the built-in
+operators.</p>
+<h3><a name="FOOT7" href="#DOCF7">(7)</a></h3>
+<p>Base 10 floating point literals cannot usually be
+exactly represented as base 2 floating point number. In order to
+ensure that the literal is represented accurately with the current
+precision, it must be evaluated at runtime.</p>
+<h3><a name="FOOT8" href="#DOCF8">(8)</a></h3>
+<p>Could be removed in case a deterministic behvior for floating point operations is required.</p>
+</div>
+<hr>
+
+
+
+</body>
+</html>
diff --git a/doc/jsbignum.pdf b/doc/jsbignum.pdf
new file mode 100644
index 0000000..8dea61e
--- /dev/null
+++ b/doc/jsbignum.pdf
Binary files differ
diff --git a/doc/jsbignum.texi b/doc/jsbignum.texi
new file mode 100644
index 0000000..9525387
--- /dev/null
+++ b/doc/jsbignum.texi
@@ -0,0 +1,829 @@
+\input texinfo
+
+@iftex
+@afourpaper
+@headings double
+@end iftex
+
+@titlepage
+@afourpaper
+@sp 7
+@center @titlefont{Javascript Bignum Extensions}
+@sp 3
+@center Version 2018-06-16
+@sp 3
+@center Author: Fabrice Bellard
+@end titlepage
+
+@setfilename spec.info
+@settitle Javascript Bignum Extensions
+
+@contents
+
+@chapter Introduction
+
+The Bignum extensions add the following features to the Javascript
+language while being 100% backward compatible:
+
+@itemize
+
+@item Overloading of the standard operators
+to support new types such as complex numbers, fractions or matrixes.
+
+@item Bigint mode where arbitrarily large integers are available by default (no @code{n} suffix is necessary as in the TC39 BigInt proposal@footnote{@url{https://tc39.github.io/proposal-bigint/}}).
+
+@item Arbitrarily large floating point numbers (@code{BigFloat}) in base 2 using the IEEE 754 semantics.
+
+@item Optional @code{math} mode which modifies the semantics of the division, modulo and power operator. The division and power operator return a fraction with integer operands and the modulo operator is defined as the Euclidian remainder.
+
+@end itemize
+
+The extensions are independent from each other except the @code{math}
+mode which relies on the bigint mode and the operator overloading.
+
+@chapter Operator overloading
+
+@section Introduction
+
+If the operands of an operator have at least one object type, a custom
+operator method is searched before doing the legacy Javascript
+@code{ToNumber} conversion.
+
+For unary operators, the custom function is looked up in the object
+and has the following name:
+
+@table @code
+@item unary + 
+@code{Symbol.operatorPlus}
+
+@item unary -
+@code{Symbol.operatorNeg}
+
+@item ++
+@code{Symbol.operatorInc}
+
+@item --
+@code{Symbol.operatorDec}
+
+@item ~
+@code{Symbol.operatorNot}
+
+@end table
+
+For binary operators:
+
+@itemize
+
+@item
+If both operands have the same constructor function, then the operator
+is looked up in the constructor.
+
+@item
+Otherwise, the property @code{Symbol.operatorOrder} is looked up in both
+constructors and converted to @code{Int32}. The operator is then
+looked in the constructor with the larger @code{Symbol.operatorOrder}
+value. A @code{TypeError} is raised if both constructors have the same
+@code{Symbol.operatorOrder} value.
+
+@end itemize
+
+The operator is looked up with the following name:
+
+@table @code
+@item + 
+@code{Symbol.operatorAdd}
+
+@item -
+@code{Symbol.operatorSub}
+
+@item *
+@code{Symbol.operatorMul}
+
+@item /
+@code{Symbol.operatorDiv}
+
+@item %
+@code{Symbol.operatorMod}
+
+@item % (math mode)
+@code{Symbol.operatorMathMod}
+
+@item **
+@code{Symbol.operatorPow}
+
+@item |
+@code{Symbol.operatorOr}
+
+@item ^
+@code{Symbol.operatorXor}
+
+@item &
+@code{Symbol.operatorAnd}
+
+@item <<
+@code{Symbol.operatorShl}
+
+@item >>
+@code{Symbol.operatorShr}
+
+@item <
+@code{Symbol.operatorCmpLT}
+
+@item >
+@code{Symbol.operatorCmpLT}, operands swapped
+
+@item <=
+@code{Symbol.operatorCmpLE}
+
+@item >=
+@code{Symbol.operatorCmpLE}, operands swapped
+
+@item ==, !=
+@code{Symbol.operatorCmpEQ}
+
+@end table
+
+The return value of @code{Symbol.operatorCmpLT}, @code{Symbol.operatorCmpLE} and
+@code{Symbol.operatorCmpEQ} is converted to @code{Boolean}.
+
+@section Builtin Object changes
+
+@subsection @code{Symbol} constructor
+
+The following global symbols are added for the operator overloading:
+@table @code
+@item operatorOrder
+@item operatorAdd
+@item operatorSub
+@item operatorMul
+@item operatorDiv
+@item operatorMod
+@item operatorPow
+@item operatorShl
+@item operatorShr
+@item operatorAnd
+@item operatorOr
+@item operatorXor
+@item operatorCmpLT
+@item operatorCmpLE
+@item operatorCmpEQ
+@item operatorPlus
+@item operatorNeg
+@item operatorNot
+@item operatorInc
+@item operatorDec
+@end table
+
+
+@chapter The BigInt Mode
+
+@section Introduction
+
+The bigint mode is enabled with the @code{"use bigint"} directive. It
+propagates the same way as the strict mode. In bigint mode, all
+integers are considered as @code{bigint} (arbitrarily large integer,
+similar to the TC39 BigInt
+proposal@footnote{@url{https://tc39.github.io/proposal-bigint/}})
+instead of @code{number} (floating point number). In order to be able
+to exchange data between standard and bigint modes, numbers are
+internally represented as 3 different types:
+
+@itemize
+
+@item Small integer (SmallInt): 32 bit integer@footnote{Could be extended to 53 bits without changing the principle.}.
+
+@item Big integer (BigInt): arbitrarily large integer.
+
+@item Floating point number (Float).
+
+@end itemize
+
+In standard mode, the semantics of each operation is modified so that
+when it returns a @code{number}, it is either of SmallInt or
+Float. But the difference between SmallInt and Float is not observable
+in standard mode.
+
+In bigint mode, each operation behaves differently whether its
+operands are integer or float. The difference between SmallInt and
+BigInt is not observable (i.e. they are both integers).
+
+The following table summarizes the observable types:
+
+@multitable @columnfractions .3 .3 .3
+@headitem Internal type @tab Observable type@* (standard mode) @tab Observable type@* (bigint mode)
+@item SmallInt @tab number @tab bigint
+@item BigInt @tab bigint @tab bigint
+@item Float @tab number @tab number
+@end multitable
+
+@section Changes that introduce incompatibilities with Javascript
+
+@subsection Standard mode
+
+There is no incompatibility with Javascript.
+
+@subsection Bigint mode
+
+The following changes are visible:
+
+@itemize
+
+@item Integer and Float are different types. Constants are typed. For example: @code{typeof 1.0 === "number"} and @code{typeof 1 === "bigint"}. Another consequence is that @code{1.0 === 1} is false.
+
+@item The range of integers is unlimited. In standard mode: @code{2**53 + 1 === 2**53}. This is no longer true with the bignum extensions.
+
+@item Binary bitwise operators do not truncate to 32 bits i.e. @code{0x800000000 | 1 === 0x800000001} while it gives @code{1} in standard mode.
+
+@item Bitwise shift operators do not truncate to 32 bits and do not mask the shift count with @code{0x1f} i.e. @code{1 << 32 === 4294967296} while it gives @code{1} in standard mode. However, the @code{>>>} operator (unsigned right shift) which is useless with bignums keeps its standard mode behavior@footnote{The unsigned right right operator could be removed in bigint mode.}.
+
+@item Operators with integer operands never return the minus zero floating point value as result. Hence @code{Object.is(0, -0) === true}. Use @code{-0.0} to create a minus zero floating point value.
+
+@item The @code{ToPrimitive} abstract operation is called with the @code{"integer"} preferred type when an integer is required (e.g. for bitwise binary or shift operations).
+
+@item The prototype of integers is no longer @code{Number.prototype}. Instead@* @code{Object.getPrototypeOf(1) === BigInt.prototype}. The prototype of floats remains Number.prototype.
+
+@item If the TC39 BigInt proposal is supported, there is no observable difference between integers and @code{bigint}s.
+
+@end itemize
+
+@section Operators
+
+@subsection Arithmetic operators
+
+The operands are converted to number values as in normal
+Javascript. Then the general case is that an Integer is returned if
+both operands are Integer. Otherwise, a float is returned.
+
+The @code{+} operator also accepts strings as input and behaves like
+standard Javascript in this case.
+
+The binary operator @code{%} returns the truncated remainder of the
+division. When the result is an Integer type, a dividend of zero yields a
+RangeError exception.
+
+The binary operator @code{%} in math mode returns the Euclidian
+remainder of the division i.e. it is always positive. 
+
+The binary operator @code{/} returns a float.
+
+The binary operator @code{/} in math mode returns a float if one of
+the operands is float. Otherwise, @code{BigInt[Symbol.operatorDiv]} is
+invoked.
+
+The returned type of @code{a ** b} is Float if @math{a} or @math{b}
+are Float. If @math{a} and @math{b} are integers:
+@itemize
+@item @math{b < 0} returns a Float in bigint mode. In math mode, @code{BigInt[Symbol.operatorPow]} is invoked.
+
+@item @math{b >= 0} returns an integer.
+@end itemize
+
+The unary @code{-} and unary @code{+} return the same type as their
+operand. They performs no floating point rounding when the result is a
+float.
+
+The unary operators @code{++} and @code{--} return the same type as
+their operand.
+
+In standard mode:
+
+If the operator returns an Integer and that the result fits a
+SmallInt, it is converted to SmallInt. Otherwise, the Integer is
+converted to a Float.
+
+In bigint mode:
+
+If the operator returns an Integer and that the result fits a
+SmallInt, it is converted to SmallInt. Otherwise it is a BigInt.
+
+@subsection Logical operators
+
+In standard mode:
+
+The operands have their standard behavior. If the result fits a
+SmallInt it is converted to a SmallInt. Otherwise it is a Float.
+
+In bigint mode:
+
+The operands are converted to integer values. The floating point
+values are converted to integer by rounding them to zero.
+
+The logical operators are defined assuming the integers are
+represented in two complement notation.
+
+For @code{<<} and @code{<<}, the shift can be positive or negative. So
+@code{a << b} is defined as @math{\lfloor a/2^{-b} \rfloor} and
+@code{a >> b} is defined as @math{\lfloor a/2^{b} \rfloor}.
+
+The operator @code{>>>} is supported for backward compatibility and
+behaves the same way as Javascript i.e. implicit conversion to @code{Uint32}.
+
+If the result fits a SmallInt it is converted to a SmallInt. Otherwise
+it is a BigInt.
+
+@subsection Relational operators
+
+The relational operators <, <=, >, >=, ==, != work as expected with
+integers and floating point numbers (e.g. @code{1.0 == 1} is true).
+
+The strict equality operators === and !== have the usual Javascript
+semantics. In particular, different types never equal, so @code{1.0
+=== 1} is false.
+
+@section Number literals
+
+Number literals in bigint mode have a slightly different behavior than
+in standard Javascript:
+
+@enumerate
+
+@item
+A number literal without a decimal point or an exponent is considered
+as an Integer. Otherwise it is a Float.
+
+@item
+Hexadecimal, octal or binary floating point literals are accepted with
+a decimal point or an exponent. The exponent is specified with the
+@code{p} letter assuming a base 2. The same convention is used by
+C99. Example: @code{0x1p3} is the same as @code{8.0}.
+
+@end enumerate
+
+@section Builtin Object changes
+
+@subsection @code{BigInt} function
+
+The @code{BigInt} function cannot be invoked as a constructor. When
+invoked as a function, it converts its first parameter to an
+integer. When a floating point number is given as parameter, it is
+truncated to an integer with infinite precision.
+
+@code{BigInt} properties:
+
+@table @code
+
+@item asIntN(bits, a)
+Set @math{b=a \pmod{2^{bits}}}. Return @math{b} if @math{b < 2^{bits-1}}
+otherwise @math{b-2^{bits}}.
+
+@item asUintN(bits, a)
+Return @math{a \pmod{2^{bits}}}.
+
+@item tdiv(a, b)
+Return @math{trunc(a/b)}. @code{b = 0} raises a RangeError
+exception.
+
+@item fdiv(a, b)
+Return @math{\lfloor a/b \rfloor}. @code{b = 0} raises a RangeError
+exception.
+
+@item cdiv(a, b)
+Return @math{\lceil a/b \rceil}. @code{b = 0} raises a RangeError
+exception.
+
+@item ediv(a, b)
+Return @math{sgn(b) \lfloor a/{|b|} \rfloor} (Euclidian
+division). @code{b = 0} raises a RangeError exception.
+
+@item tdivrem(a, b)
+@item fdivrem(a, b)
+@item cdivrem(a, b)
+@item edivrem(a, b)
+Return an array of two elements. The first element is the quotient,
+the second is the remainder. The same rounding is done as the
+corresponding division operation.
+
+@item sqrt(a)
+Return @math{\lfloor \sqrt(a) \rfloor}. A RangeError exception is
+raised if @math{a < 0}.
+
+@item sqrtrem(a)
+Return an array of two elements. The first element is @math{\lfloor
+\sqrt{a} \rfloor}. The second element is @math{a-\lfloor \sqrt{a}
+\rfloor^2}. A RangeError exception is raised if @math{a < 0}.
+
+@item floorLog2(a)
+Return -1 if @math{a \leq 0} otherwise return @math{\lfloor \log2(a) \rfloor}.
+
+@item ctz(a)
+Return the number of trailing zeros in the two's complement binary representation of a. Return -1 if @math{a=0}.
+
+@end table
+
+@subsection @code{BigInt.prototype}
+
+It is a normal object.
+
+@subsection @code{Number} constructor
+
+The number constructor returns its argument rounded to a Float using
+the global floating point environement. In bigint mode, the Number
+constructor returns a Float. In standard mode, it returns a SmallInt
+if the value fits it, otherwise a Float.
+
+@subsection @code{Number.prototype}
+
+The following properties are modified:
+
+@table @code
+@item toString(radix)
+
+In bigint mode, integers are converted to the specified radix with
+infinite precision.
+
+@item toPrecision(p)
+@item toFixed(p)
+@item toExponential(p)
+
+In bigint mode, integers are accepted and converted to string with
+infinite precision.
+
+@item parseInt(string, radix)
+
+In bigint mode, an integer is returned and the conversion is done with
+infinite precision.
+
+@end table
+
+@subsection @code{Math} object
+
+The following properties are modified:
+
+@table @code
+@item abs(x)
+Absolute value. Return an integer if @code{x} is an Integer. Otherwise
+return a Float. No rounding is performed.
+
+@item min(a, b)
+@item max(a, b)
+No rounding is performed. The returned type is the same one as the
+minimum (resp. maximum) value.
+
+@end table
+
+@chapter Arbitrarily large floating point numbers
+
+@section Introduction
+
+This extension adds the @code{BigFloat} primitive type. The
+@code{BigFloat} type represents floating point numbers are in base 2
+with the IEEE 754 semantics. A floating
+point number is represented as a sign, mantissa and exponent. The
+special values @code{NaN}, @code{+/-Infinity}, @code{+0} and @code{-0}
+are supported. The mantissa and exponent can have any bit length with
+an implementation specific minimum and maximum.
+
+@section Floating point rounding
+
+Each floating point operation operates with infinite precision and
+then rounds the result according to the specified floating point
+environment (@code{BigFloatEnv} object). The status flags of the
+environment are also set according to the result of the operation.
+
+If no floating point environment is provided, the global floating
+point environment is used.
+
+The rounding mode of the global floating point environment is always
+@code{RNDN} (``round to nearest with ties to even'')@footnote{The
+rationale is that the rounding mode changes must always be
+explicit.}. The status flags of the global environment cannot be
+read@footnote{The rationale is to avoid side effects for the built-in
+operators.}. The precision of the global environment is
+@code{BigFloatEnv.prec}. The number of exponent bits of the global
+environment is @code{BigFloatEnv.expBits}.  If @code{BigFloatEnv.expBits} is
+strictly smaller than the maximum allowed number of exponent bits
+(@code{BigFloatEnv.expBitsMax}), then the global environment subnormal
+flag is set to @code{true}. Otherwise it is set to @code{false};
+
+For example, @code{prec = 53} and @code{ expBits = 11} give exactly
+the same precision as the IEEE 754 64 bit floating point type. It is
+the default floating point precision.
+
+The global floating point environment can only be modified temporarily
+when calling a function (see @code{BigFloatEnv.setPrec}). Hence a
+function can change the global floating point environment for its
+callees but not for its caller.
+
+@section Operators
+
+The builtin operators are extended so that a BigFloat is returned if
+at least one operand is a BigFloat. The computations are always done
+with infinite precision and rounded according to the global floating
+point environment.
+
+@code{typeof} applied on a @code{BigFloat} returns @code{bigfloat}.
+
+BigFloat can be compared with all the other numeric types and the
+result follows the expected mathematical relations.
+
+However, since BigFloat and Number are different types they are never
+equal when using the strict comparison operators (e.g. @code{0.0 ===
+0.0l} is false).
+
+@section BigFloat literals
+
+BigFloat literals are floating point numbers with a trailing @code{l}
+suffix. BigFloat literals have an infinite precision. They are rounded
+according to the global floating point environment when they are
+evaluated.@footnote{Base 10 floating point literals cannot usually be
+exactly represented as base 2 floating point number. In order to
+ensure that the literal is represented accurately with the current
+precision, it must be evaluated at runtime.}
+
+@section Builtin Object changes
+
+@subsection @code{BigFloat} function
+
+The @code{BigFloat} function cannot be invoked as a constructor. When
+invoked as a function: the parameter is converted to a primitive
+type. If the result is a numeric type, it is converted to BigFloat
+without rounding. If the result is a string, it is converted to
+BigFloat using the precision of the global floating point environment.
+
+@code{BigFloat} properties:
+
+@table @code
+
+@item LN2
+@item PI
+Getter. Return the value of the corresponding mathematical constant
+rounded to nearest, ties to even with the current global
+precision. The constant values are cached for small precisions.
+
+@item MIN_VALUE
+@item MAX_VALUE
+@item EPSILON
+Getter. Return the minimum, maximum and epsilon @code{BigFloat} values
+(same definition as the corresponding @code{Number} constants).
+
+@item fpRound(a[, e])
+Round the floating point number @code{a} according to the floating
+point environment @code{e} or the global environment if @code{e} is
+undefined.
+
+@item parseFloat(a[, radix[, e]])
+Parse the string @code{a} as a floating point number in radix
+@code{radix}. The radix is 0 (default) or from 2 to 36. The radix 0
+means radix 10 unless there is a hexadecimal or binary prefix. The
+result is rounded according to the floating point environment @code{e}
+or the global environment if @code{e} is undefined.
+
+@item add(a, b[, e])
+@item sub(a, b[, e])
+@item mul(a, b[, e])
+@item div(a, b[, e])
+Perform the specified floating point operation and round the floating
+point number @code{a} according to the floating point environment
+@code{e} or the global environment if @code{e} is undefined. If
+@code{e} is specified, the floating point status flags are updated.
+
+@item floor(x[, e])
+@item ceil(x[, e])
+@item round(x[, e])
+@item trunc(x[, e])
+Round to integer. A rounded @code{BigFloat} is returned. @code{e} is an
+optional floating point environment.
+
+@item fmod(x, y[, e])
+@item remainder(x, y[, e])
+Floating point remainder. The quotient is truncated to zero (fmod) or
+to the nearest integer with ties to even (remainder). @code{e} is an
+optional floating point environment.
+
+@item sqrt(x[, e])
+Square root. Return a rounded floating point number. @code{e} is an
+optional floating point environment.
+
+@item sin(x[, e])
+@item cos(x[, e])
+@item tan(x[, e])
+@item asin(x[, e])
+@item acos(x[, e])
+@item atan(x[, e])
+@item atan2(x, y[, e])
+@item exp(x[, e])
+@item log(x[, e])
+@item pow(x, y[, e])
+Transcendental operations. Return a rounded floating point
+number. @code{e} is an optional floating point environment.
+
+@end table
+
+@subsection @code{BigFloat.prototype}
+
+The following properties are modified:
+
+@table @code
+@item toString(radix)
+
+For floating point numbers:
+
+@itemize
+@item
+If the radix is a power of two, the conversion is done with infinite
+precision.
+@item
+Otherwise, the number is rounded to nearest with ties to even using
+the global precision. It is then converted to string using the minimum
+number of digits so that its conversion back to a floating point using
+the global precision and round to nearest gives the same number. 
+
+@end itemize
+
+@item toPrecision(p[, rnd_mode])
+@item toFixed(p[, rnd_mode])
+@item toExponential(p[, rnd_mode])
+Same semantics as the corresponding @code{Number} functions with
+BigFloats. There is no limit on the accepted precision @code{p}. The
+rounding mode can be optionally specified. It is set by default to
+@code{BigFloatEnv.RNDNA}.
+
+@end table
+
+@subsection @code{BigFloatEnv} constructor
+
+The @code{BigFloatEnv([p, [,rndMode]]} constructor cannot be invoked as a
+function. The floating point environment contains:
+
+@itemize
+@item the mantissa precision in bits
+
+@item the exponent size in bits assuming an IEEE 754 representation;
+
+@item the subnormal flag (if true, subnormal floating point numbers can
+be generated by the floating point operations).
+
+@item the rounding mode
+
+@item the floating point status. The status flags can only be set by the floating point operations. They can be reset with @code{BigFloatEnv.prototype.clearStatus()} or with the various status flag setters.
+
+@end itemize
+
+@code{new BigFloatEnv([p, [,rndMode]]} creates a new floating point
+environment. The status flags are reset. If no parameter is given the
+precision, exponent bits and subnormal flags are copied from the
+global floating point environment. Otherwise, the precision is set to
+@code{p}, the number of exponent bits is set to @code{expBitsMax} and the
+subnormal flags is set to @code{false}. If @code{rndMode} is
+@code{undefined}, the rounding mode is set to @code{RNDN}.
+
+@code{BigFloatEnv} properties:
+
+@table @code
+
+@item prec
+Getter. Return the precision in bits of the global floating point
+environment. The initial value is @code{53}.
+
+@item expBits
+Getter. Return the exponent size in bits of the global floating point
+environment assuming an IEEE 754 representation. If @code{expBits <
+expBitsMax}, then subnormal numbers are supported. The initial value
+is @code{11}.
+
+@item setPrec(f, p[, e])
+Set the precision of the global floating point environment to @code{p}
+and the exponent size to @code{e} then call the function
+@code{f}. Then the Float precision and exponent size are reset to
+their precious value and the return value of @code{f} is returned (or
+an exception is raised if @code{f} raised an exception). If @code{e}
+is @code{undefined} it is set to @code{BigFloatEnv.expBitsMax}. @code{p}
+must be >= 53 and @code{e} must be >= 11 so that the global precision
+is at least equivalent to the IEEE 754 64 bit doubles.
+
+@item precMin
+Read-only integer. Return the minimum allowed precision. Must be at least 2.
+
+@item precMax
+Read-only integer. Return the maximum allowed precision. Must be at least 53.
+
+@item expBitsMin
+Read-only integer. Return the minimum allowed exponent size in
+bits. Must be at least 3.
+
+@item expBitsMax
+Read-only integer. Return the maximum allowed exponent size in
+bits. Must be at least 11.
+
+@item RNDN
+Read-only integer. Round to nearest, with ties to even rounding mode.
+
+@item RNDZ
+Read-only integer. Round to zero rounding mode.
+
+@item RNDD
+Read-only integer. Round to -Infinity rounding mode.
+
+@item RNDU
+Read-only integer. Round to +Infinity rounding mode.
+
+@item RNDNA
+Read-only integer. Round to nearest, with ties away from zero rounding mode.
+
+@item RNDNU
+Read-only integer. Round to nearest, with ties to +Infinity rounding mode.
+
+@item RNDF@footnote{Could be removed in case a deterministic behvior for floating point operations is required.}
+Read-only integer. Faithful rounding mode. The result is
+non-deterministicly rounded to -Infinity or +Infinity. This rounding
+mode usually gives a faster and deterministic running time for the
+floating point operations.
+
+@end table
+
+@code{BigFloatEnv.prototype} properties:
+
+@table @code
+
+@item prec
+Getter and setter (Integer). Return or set the precision in bits.
+
+@item expBits
+Getter and setter (Integer). Return or set the exponent size in bits
+assuming an IEEE 754 representation.
+
+@item rndMode
+Getter and setter (Integer). Return or set the rounding mode.
+
+@item subnormal
+Getter and setter (Boolean). subnormal flag. It is false when
+@code{expBits = expBitsMax}.
+
+@item clearStatus()
+Clear the status flags.
+
+@item invalidOperation
+@item divideByZero
+@item overflow
+@item underflow
+@item inexact
+Getter and setter (Boolean). Status flags.
+
+@end table
+
+@subsection @code{Math} object
+
+The following properties are modified:
+
+@table @code
+@item abs(x)
+Absolute value. If @code{x} is a BigFloat, its absolute value is
+returned as a BigFloat. No rounding is performed.
+
+@item min(a, b)
+@item max(a, b)
+The returned type is the same one as the minimum (resp. maximum)
+value, so @code{BigFloat} values are accepted. When a @code{BigFloat}
+is returned, no rounding is performed.
+
+@end table
+
+@chapter Math mode
+
+@section Introduction
+
+A new @emph{math mode} is enabled with the @code{"use math"}
+directive. @code{"use bigint"} is implied in math mode. With this
+mode, writing mathematical expressions is more intuitive, exact
+results (e.g. fractions) can be computed for all operators and floating
+point literals have the @code{BigFloat} type by default.
+
+It propagates the same way as the @emph{strict mode}. In
+this mode:
+
+@itemize
+
+@item The @code{^} operator is a similar to the power operator (@code{**}).
+
+@item The power operator (both @code{^} and @code{**}) grammar is modified so that @code{-2^2} is allowed and yields @code{-4}.
+
+@item The logical xor operator is still available with the @code{^^} operator.
+
+@item The division operator invokes @code{BigInt[Symbol.operatorDiv]} in case both operands are integers.
+
+@item The power operator invokes @code{BigInt[Symbol.operatorPow]} in case both operands are integers and the exponent is strictly negative.
+
+@item The modulo operator returns the Euclidian remainder (always positive) instead of the truncated remainder.
+
+@item Floating point literals are @code{BigFloat} by default (i.e. a @code{l} suffix is implied).
+
+@end itemize
+
+@section Builtin Object changes
+
+@subsection @code{Symbol} constructor
+
+The following global symbol is added for the operator overloading:
+@table @code
+@item operatorMathMod
+@end table
+
+@section Remaining issues
+
+@enumerate
+
+@item A new floating point literal suffix could be added for @code{Number} literals.
+
+@end enumerate
+
+@bye
diff --git a/doc/quickjs.html b/doc/quickjs.html
new file mode 100644
index 0000000..153d955
--- /dev/null
+++ b/doc/quickjs.html
@@ -0,0 +1,1245 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<!-- Created by GNU Texinfo 6.5, http://www.gnu.org/software/texinfo/ -->
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>QuickJS Javascript Engine</title>
+
+<meta name="description" content="QuickJS Javascript Engine">
+<meta name="keywords" content="QuickJS Javascript Engine">
+<meta name="resource-type" content="document">
+<meta name="distribution" content="global">
+<meta name="Generator" content="makeinfo">
+<link href="#SEC_Contents" rel="contents" title="Table of Contents">
+<style type="text/css">
+<!--
+a.summary-letter {text-decoration: none}
+blockquote.indentedblock {margin-right: 0em}
+blockquote.smallindentedblock {margin-right: 0em; font-size: smaller}
+blockquote.smallquotation {font-size: smaller}
+div.display {margin-left: 3.2em}
+div.example {margin-left: 3.2em}
+div.lisp {margin-left: 3.2em}
+div.smalldisplay {margin-left: 3.2em}
+div.smallexample {margin-left: 3.2em}
+div.smalllisp {margin-left: 3.2em}
+kbd {font-style: oblique}
+pre.display {font-family: inherit}
+pre.format {font-family: inherit}
+pre.menu-comment {font-family: serif}
+pre.menu-preformatted {font-family: serif}
+pre.smalldisplay {font-family: inherit; font-size: smaller}
+pre.smallexample {font-size: smaller}
+pre.smallformat {font-family: inherit; font-size: smaller}
+pre.smalllisp {font-size: smaller}
+span.nolinebreak {white-space: nowrap}
+span.roman {font-family: initial; font-weight: normal}
+span.sansserif {font-family: sans-serif; font-weight: normal}
+ul.no-bullet {list-style: none}
+-->
+</style>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+
+</head>
+
+<body lang="en">
+<h1 class="settitle" align="center">QuickJS Javascript Engine</h1>
+
+<a name="SEC_Contents"></a>
+<h2 class="contents-heading">Table of Contents</h2>
+
+<div class="contents">
+<ul class="no-bullet">
+<li><a name="toc-Introduction" href="#Introduction">1 Introduction</a>
+<ul class="no-bullet">
+  <li><a name="toc-Main-Features" href="#Main-Features">1.1 Main Features</a></li>
+</ul></li>
+<li><a name="toc-Usage" href="#Usage">2 Usage</a>
+<ul class="no-bullet">
+  <li><a name="toc-Installation" href="#Installation">2.1 Installation</a></li>
+  <li><a name="toc-Quick-start" href="#Quick-start">2.2 Quick start</a></li>
+  <li><a name="toc-Command-line-options" href="#Command-line-options">2.3 Command line options</a>
+  <ul class="no-bullet">
+    <li><a name="toc-qjs-interpreter" href="#qjs-interpreter">2.3.1 <code>qjs</code> interpreter</a></li>
+    <li><a name="toc-qjsc-compiler" href="#qjsc-compiler">2.3.2 <code>qjsc</code> compiler</a></li>
+  </ul></li>
+  <li><a name="toc-qjscalc-application" href="#qjscalc-application">2.4 <code>qjscalc</code> application</a></li>
+  <li><a name="toc-Built_002din-tests" href="#Built_002din-tests">2.5 Built-in tests</a></li>
+  <li><a name="toc-Test262-_0028ECMAScript-Test-Suite_0029" href="#Test262-_0028ECMAScript-Test-Suite_0029">2.6 Test262 (ECMAScript Test Suite)</a></li>
+</ul></li>
+<li><a name="toc-Specifications" href="#Specifications">3 Specifications</a>
+<ul class="no-bullet">
+  <li><a name="toc-Language-support" href="#Language-support">3.1 Language support</a>
+  <ul class="no-bullet">
+    <li><a name="toc-ES2019-support" href="#ES2019-support">3.1.1 ES2019 support</a></li>
+    <li><a name="toc-JSON" href="#JSON">3.1.2 JSON</a></li>
+    <li><a name="toc-ECMA402" href="#ECMA402">3.1.3 ECMA402</a></li>
+    <li><a name="toc-Extensions" href="#Extensions">3.1.4 Extensions</a></li>
+    <li><a name="toc-Mathematical-extensions" href="#Mathematical-extensions">3.1.5 Mathematical extensions</a></li>
+  </ul></li>
+  <li><a name="toc-Modules" href="#Modules">3.2 Modules</a></li>
+  <li><a name="toc-Standard-library" href="#Standard-library">3.3 Standard library</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Global-objects" href="#Global-objects">3.3.1 Global objects</a></li>
+    <li><a name="toc-std-module" href="#std-module">3.3.2 <code>std</code> module</a></li>
+    <li><a name="toc-os-module" href="#os-module">3.3.3 <code>os</code> module</a></li>
+  </ul></li>
+  <li><a name="toc-QuickJS-C-API" href="#QuickJS-C-API">3.4 QuickJS C API</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Runtime-and-contexts" href="#Runtime-and-contexts">3.4.1 Runtime and contexts</a></li>
+    <li><a name="toc-JSValue" href="#JSValue">3.4.2 JSValue</a></li>
+    <li><a name="toc-C-functions" href="#C-functions">3.4.3 C functions</a></li>
+    <li><a name="toc-Exceptions" href="#Exceptions">3.4.4 Exceptions</a></li>
+    <li><a name="toc-Script-evaluation" href="#Script-evaluation">3.4.5 Script evaluation</a></li>
+    <li><a name="toc-JS-Classes" href="#JS-Classes">3.4.6 JS Classes</a></li>
+    <li><a name="toc-C-Modules" href="#C-Modules">3.4.7 C Modules</a></li>
+    <li><a name="toc-Memory-handling" href="#Memory-handling">3.4.8 Memory handling</a></li>
+    <li><a name="toc-Execution-timeout-and-interrupts" href="#Execution-timeout-and-interrupts">3.4.9 Execution timeout and interrupts</a></li>
+  </ul></li>
+</ul></li>
+<li><a name="toc-Internals" href="#Internals">4 Internals</a>
+<ul class="no-bullet">
+  <li><a name="toc-Bytecode" href="#Bytecode">4.1 Bytecode</a></li>
+  <li><a name="toc-Executable-generation" href="#Executable-generation">4.2 Executable generation</a>
+  <ul class="no-bullet">
+    <li><a name="toc-qjsc-compiler-1" href="#qjsc-compiler-1">4.2.1 <code>qjsc</code> compiler</a></li>
+    <li><a name="toc-Binary-JSON" href="#Binary-JSON">4.2.2 Binary JSON</a></li>
+  </ul></li>
+  <li><a name="toc-Runtime" href="#Runtime">4.3 Runtime</a>
+  <ul class="no-bullet">
+    <li><a name="toc-Strings" href="#Strings">4.3.1 Strings</a></li>
+    <li><a name="toc-Objects" href="#Objects">4.3.2 Objects</a></li>
+    <li><a name="toc-Atoms" href="#Atoms">4.3.3 Atoms</a></li>
+    <li><a name="toc-Numbers" href="#Numbers">4.3.4 Numbers</a></li>
+    <li><a name="toc-Garbage-collection" href="#Garbage-collection">4.3.5 Garbage collection</a></li>
+    <li><a name="toc-JSValue-1" href="#JSValue-1">4.3.6 JSValue</a></li>
+    <li><a name="toc-Function-call" href="#Function-call">4.3.7 Function call</a></li>
+  </ul></li>
+  <li><a name="toc-RegExp" href="#RegExp">4.4 RegExp</a></li>
+  <li><a name="toc-Unicode" href="#Unicode">4.5 Unicode</a></li>
+  <li><a name="toc-BigInt-and-BigFloat" href="#BigInt-and-BigFloat">4.6 BigInt and BigFloat</a></li>
+</ul></li>
+<li><a name="toc-License" href="#License">5 License</a></li>
+
+</ul>
+</div>
+
+
+<a name="Introduction"></a>
+<h2 class="chapter">1 Introduction</h2>
+
+<p>QuickJS is a small and embeddable Javascript engine. It supports the
+ES2019 specification
+<a name="DOCF1" href="#FOOT1"><sup>1</sup></a>
+including modules, asynchronous generators and proxies.
+</p>
+<p>It optionally supports mathematical extensions such as big integers
+(BigInt), big floating point numbers (BigFloat) and operator
+overloading.
+</p>
+<a name="Main-Features"></a>
+<h3 class="section">1.1 Main Features</h3>
+
+<ul>
+<li> Small and easily embeddable: just a few C files, no external dependency, 180 KiB of x86 code for a simple &ldquo;hello world&rdquo; program.
+
+</li><li> Fast interpreter with very low startup time: runs the 69000 tests of the ECMAScript Test Suite<a name="DOCF2" href="#FOOT2"><sup>2</sup></a> in about 95 seconds on a single core of a desktop PC. The complete life cycle of a runtime instance completes in less than 300 microseconds.
+
+</li><li> Almost complete ES2019 support including modules, asynchronous
+generators and full Annex B support (legacy web compatibility). Many
+features from the upcoming ES2020 specification
+<a name="DOCF3" href="#FOOT3"><sup>3</sup></a> are also supported.
+
+</li><li> Passes nearly 100% of the ECMAScript Test Suite tests when selecting the ES2019 features.
+
+</li><li> Can compile Javascript sources to executables with no external dependency.
+
+</li><li> Garbage collection using reference counting (to reduce memory usage and have deterministic behavior) with cycle removal.
+
+</li><li> Mathematical extensions: BigInt, BigFloat, operator overloading, bigint mode, math mode.
+
+</li><li> Command line interpreter with contextual colorization and completion implemented in Javascript.
+
+</li><li> Small built-in standard library with C library wrappers.
+
+</li></ul>
+
+<a name="Usage"></a>
+<h2 class="chapter">2 Usage</h2>
+
+<a name="Installation"></a>
+<h3 class="section">2.1 Installation</h3>
+
+<p>A Makefile is provided to compile the engine on Linux or MacOS/X.  A
+preliminary Windows support is available thru cross compilation on a
+Linux host with the MingGW tools.
+</p>
+<p>Edit the top of the <code>Makefile</code> if you wish to select specific
+options then run <code>make</code>.
+</p>
+<p>You can type <code>make install</code> as root if you wish to install the binaries and support files to
+<code>/usr/local</code> (this is not necessary to use QuickJS).
+</p>
+<a name="Quick-start"></a>
+<h3 class="section">2.2 Quick start</h3>
+
+<p><code>qjs</code> is the command line interpreter (Read-Eval-Print Loop). You can pass
+Javascript files and/or expressions as arguments to execute them:
+</p>
+<div class="example">
+<pre class="example">./qjs examples/hello.js
+</pre></div>
+
+<p><code>qjsc</code> is the command line compiler:
+</p>
+<div class="example">
+<pre class="example">./qjsc -o hello examples/hello.js
+./hello
+</pre></div>
+
+<p>generates a <code>hello</code> executable with no external dependency.
+</p>
+<p><code>qjsbn</code> and <code>qjscbn</code> are the corresponding interpreter and
+compiler with the mathematical extensions:
+</p>
+<div class="example">
+<pre class="example">./qjsbn examples/pi.js 1000
+</pre></div>
+
+<p>displays 1000 digits of PI.
+</p>
+<div class="example">
+<pre class="example">./qjsbnc -o pi examples/pi.js
+./pi 1000
+</pre></div>
+
+<p>compiles and executes the PI program.
+</p>
+<a name="Command-line-options"></a>
+<h3 class="section">2.3 Command line options</h3>
+
+<a name="qjs-interpreter"></a>
+<h4 class="subsection">2.3.1 <code>qjs</code> interpreter</h4>
+
+<pre class="verbatim">usage: qjs [options] [files]
+</pre>
+<p>Options are:
+</p><dl compact="compact">
+<dt><code>-h</code></dt>
+<dt><code>--help</code></dt>
+<dd><p>List options.
+</p>
+</dd>
+<dt><code>-e <code>EXPR</code></code></dt>
+<dt><code>--eval <code>EXPR</code></code></dt>
+<dd><p>Evaluate EXPR.
+</p>
+</dd>
+<dt><code>-i</code></dt>
+<dt><code>--interactive</code></dt>
+<dd><p>Go to interactive mode (it is not the default when files are provided on the command line).
+</p>
+</dd>
+<dt><code>-m</code></dt>
+<dt><code>--module</code></dt>
+<dd><p>Load as ES6 module (default=autodetect).
+</p>
+</dd>
+<dt><code>--script</code></dt>
+<dd><p>Load as ES6 script (default=autodetect).
+</p>
+</dd>
+</dl>
+
+<p>Advanced options are:
+</p>
+<dl compact="compact">
+<dt><code>-d</code></dt>
+<dt><code>--dump</code></dt>
+<dd><p>Dump the memory usage stats.
+</p>
+</dd>
+<dt><code>-q</code></dt>
+<dt><code>--quit</code></dt>
+<dd><p>just instantiate the interpreter and quit.
+</p>
+</dd>
+</dl>
+
+<a name="qjsc-compiler"></a>
+<h4 class="subsection">2.3.2 <code>qjsc</code> compiler</h4>
+
+<pre class="verbatim">usage: qjsc [options] [files]
+</pre>
+<p>Options are:
+</p><dl compact="compact">
+<dt><code>-c</code></dt>
+<dd><p>Only output bytecode in a C file. The default is to output an executable file.
+</p></dd>
+<dt><code>-e</code></dt>
+<dd><p>Output <code>main()</code> and bytecode in a C file. The default is to output an
+executable file.
+</p></dd>
+<dt><code>-o output</code></dt>
+<dd><p>Set the output filename (default = <samp>out.c</samp> or <samp>a.out</samp>).
+</p>
+</dd>
+<dt><code>-N cname</code></dt>
+<dd><p>Set the C name of the generated data.
+</p>
+</dd>
+<dt><code>-m</code></dt>
+<dd><p>Compile as Javascript module (default=autodetect).
+</p>
+</dd>
+<dt><code>-M module_name[,cname]</code></dt>
+<dd><p>Add initialization code for an external C module. See the
+<code>c_module</code> example.
+</p>
+</dd>
+<dt><code>-x</code></dt>
+<dd><p>Byte swapped output (only used for cross compilation).
+</p>
+</dd>
+<dt><code>-flto</code></dt>
+<dd><p>Use link time optimization. The compilation is slower but the
+executable is smaller and faster. This option is automatically set
+when the <code>-fno-x</code> options are used.
+</p>
+</dd>
+<dt><code>-fno-[eval|string-normalize|regexp|json|proxy|map|typedarray|promise]</code></dt>
+<dd><p>Disable selected language features to produce a smaller executable file.
+</p>
+</dd>
+</dl>
+
+<a name="qjscalc-application"></a>
+<h3 class="section">2.4 <code>qjscalc</code> application</h3>
+
+<p>The <code>qjscalc</code> application is a superset of the <code>qjsbn</code>
+command line interpreter implementing a Javascript calculator with
+arbitrarily large integer and floating point numbers, fractions,
+complex numbers, polynomials and matrices. The source code is in
+<samp>qjscalc.js</samp>. More documentation and a web version are available at
+<a href="http://numcalc.com">http://numcalc.com</a>.
+</p>
+<a name="Built_002din-tests"></a>
+<h3 class="section">2.5 Built-in tests</h3>
+
+<p>Run <code>make test</code> to run the few built-in tests included in the
+QuickJS archive.
+</p>
+<a name="Test262-_0028ECMAScript-Test-Suite_0029"></a>
+<h3 class="section">2.6 Test262 (ECMAScript Test Suite)</h3>
+
+<p>A test262 runner is included in the QuickJS archive.
+</p>
+<p>For reference, the full test262 tests are provided in the archive
+<samp>qjs-tests-yyyy-mm-dd.tar.xz</samp>. You just need to untar it into the
+QuickJS source code directory.
+</p>
+<p>Alternatively, the test262 tests can be installed with:
+</p>
+<div class="example">
+<pre class="example">git clone https://github.com/tc39/test262.git test262
+cd test262
+patch -p1 &lt; ../tests/test262.patch
+cd ..
+</pre></div>
+
+<p>The patch adds the implementation specific <code>harness</code> functions
+and optimizes the inefficient RegExp character classes and Unicode
+property escapes tests (the tests themselves are not modified, only a
+slow string initialization function is optimized).
+</p>
+<p>The tests can be run with
+</p><div class="example">
+<pre class="example">make test2
+</pre></div>
+
+<p>The configuration files <code>test262.conf</code> (resp
+<code>test262bn.conf</code> for the bignum version, <code>test262o.conf</code> for
+the old ES5.1 tests<a name="DOCF4" href="#FOOT4"><sup>4</sup></a>)) contain the options
+to run the various tests. Tests can be excluded based on features or
+filename.
+</p>
+<p>The file <code>test262_errors.txt</code> contains the current list of
+errors. The runner displays a message when a new error appears or when
+an existing error is corrected or modified. Use the <code>-u</code> option
+to update the current list of errors (or <code>make test2-update</code>).
+</p>
+<p>The file <code>test262_report.txt</code> contains the logs of all the
+tests. It is useful to have a clearer analysis of a particular
+error. In case of crash, the last line corresponds to the failing
+test.
+</p>
+<p>Use the syntax <code>./run-test262 -c test262.conf -f filename.js</code> to
+run a single test. Use the syntax <code>./run-test262 -c test262.conf
+N</code> to start testing at test number <code>N</code>.
+</p>
+<p>For more information, run <code>./run-test262</code> to see the command line
+options of the test262 runner.
+</p>
+<p><code>run-test262</code> accepts the <code>-N</code> option to be invoked from
+<code>test262-harness</code><a name="DOCF5" href="#FOOT5"><sup>5</sup></a>
+thru <code>eshost</code>. Unless you want to compare QuickJS with other
+engines under the same conditions, we do not recommend to run the
+tests this way as it is much slower (typically half an hour instead of
+about 100 seconds).
+</p>
+<a name="Specifications"></a>
+<h2 class="chapter">3 Specifications</h2>
+
+<a name="Language-support"></a>
+<h3 class="section">3.1 Language support</h3>
+
+<a name="ES2019-support"></a>
+<h4 class="subsection">3.1.1 ES2019 support</h4>
+
+<p>The ES2019 specification is almost fully supported including the Annex
+B (legacy web compatibility) and the Unicode related features.
+</p>
+<p>The following features are not supported yet:
+</p>
+<ul>
+<li> Realms (althougth the C API supports different runtimes and contexts)
+
+</li><li> Tail calls<a name="DOCF6" href="#FOOT6"><sup>6</sup></a>
+
+</li></ul>
+
+<a name="JSON"></a>
+<h4 class="subsection">3.1.2 JSON</h4>
+
+<p>The JSON parser is currently more tolerant than the specification.
+</p>
+<a name="ECMA402"></a>
+<h4 class="subsection">3.1.3 ECMA402</h4>
+
+<p>ECMA402 (Internationalization API) is not supported.
+</p>
+<a name="Extensions"></a>
+<h4 class="subsection">3.1.4 Extensions</h4>
+
+<ul>
+<li> The directive <code>&quot;use strip&quot;</code> indicates that the debug information (including the source code of the functions) should not be retained to save memory. As <code>&quot;use strict&quot;</code>, the directive can be global to a script or local to a function.
+
+</li><li> The first line of a script beginning with <code>#!</code> is ignored.
+
+</li></ul>
+
+<a name="Mathematical-extensions"></a>
+<h4 class="subsection">3.1.5 Mathematical extensions</h4>
+
+<p>The mathematical extensions are available in the <code>qjsbn</code> version and are fully
+backward compatible with standard Javascript. See <code>jsbignum.pdf</code>
+for more information.
+</p>
+<ul>
+<li> The <code>BigInt</code> (big integers) TC39 proposal is supported.
+
+</li><li> <code>BigFloat</code> support: arbitrary large floating point numbers in base 2.
+
+</li><li> Operator overloading.
+
+</li><li> The directive <code>&quot;use bigint&quot;</code> enables the bigint mode where integers are <code>BigInt</code> by default.
+
+</li><li> The directive <code>&quot;use math&quot;</code> enables the math mode where the division and power operators on integers produce fractions. Floating point literals are <code>BigFloat</code> by default and integers are <code>BigInt</code> by default.
+
+</li></ul>
+
+<a name="Modules"></a>
+<h3 class="section">3.2 Modules</h3>
+
+<p>ES6 modules are fully supported. The default name resolution is the
+following:
+</p>
+<ul>
+<li> Module names with a leading <code>.</code> or <code>..</code> are relative
+to the current module path.
+
+</li><li> Module names without a leading <code>.</code> or <code>..</code> are system
+modules, such as <code>std</code> or <code>os</code>.
+
+</li><li> Module names ending with <code>.so</code> are native modules using the
+QuickJS C API.
+
+</li></ul>
+
+<a name="Standard-library"></a>
+<h3 class="section">3.3 Standard library</h3>
+
+<p>The standard library is included by default in the command line
+interpreter. It contains the two modules <code>std</code> and <code>os</code> and
+a few global objects.
+</p>
+<a name="Global-objects"></a>
+<h4 class="subsection">3.3.1 Global objects</h4>
+
+<dl compact="compact">
+<dt><code>scriptArgs</code></dt>
+<dd><p>Provides the command line arguments. The first argument is the script name.
+</p></dd>
+<dt><code>print(...args)</code></dt>
+<dd><p>Print the arguments separated by spaces and a trailing newline.
+</p></dd>
+<dt><code>console.log(...args)</code></dt>
+<dd><p>Same as print().
+</p>
+</dd>
+</dl>
+
+<a name="std-module"></a>
+<h4 class="subsection">3.3.2 <code>std</code> module</h4>
+
+<p>The <code>std</code> module provides wrappers to the libc <samp>stdlib.h</samp>
+and <samp>stdio.h</samp> and a few other utilities.
+</p>
+<p>Available exports:
+</p>
+<dl compact="compact">
+<dt><code>exit(n)</code></dt>
+<dd><p>Exit the process.
+</p>
+</dd>
+<dt><code>evalScript(str)</code></dt>
+<dd><p>Evaluate the string <code>str</code> as a script (global eval).
+</p>
+</dd>
+<dt><code>loadScript(filename)</code></dt>
+<dd><p>Evaluate the file <code>filename</code> as a script (global eval).
+</p>
+</dd>
+<dt><code>Error(errno)</code></dt>
+<dd>
+<p><code>std.Error</code> constructor. Error instances contain the field
+<code>errno</code> (error code) and <code>message</code> (result of
+<code>std.Error.strerror(errno)</code>).
+</p>
+<p>The constructor contains the following fields:
+</p>
+<dl compact="compact">
+<dt><code>EINVAL</code></dt>
+<dt><code>EIO</code></dt>
+<dt><code>EACCES</code></dt>
+<dt><code>EEXIST</code></dt>
+<dt><code>ENOSPC</code></dt>
+<dt><code>ENOSYS</code></dt>
+<dt><code>EBUSY</code></dt>
+<dt><code>ENOENT</code></dt>
+<dt><code>EPERM</code></dt>
+<dt><code>EPIPE</code></dt>
+<dd><p>Integer value of common errors (additional error codes may be defined).
+  </p></dd>
+<dt><code>strerror(errno)</code></dt>
+<dd><p>Return a string that describes the error <code>errno</code>.
+  </p></dd>
+</dl>
+
+</dd>
+<dt><code>open(filename, flags)</code></dt>
+<dd><p>Open a file (wrapper to the libc <code>fopen()</code>). Throws 
+<code>std.Error</code> in case of I/O error.
+</p>
+</dd>
+<dt><code>popen(command, flags)</code></dt>
+<dd><p>Open a process by creating a pipe (wrapper to the libc <code>popen()</code>). Throws 
+<code>std.Error</code> in case of I/O error.
+</p>
+</dd>
+<dt><code>fdopen(fd, flags)</code></dt>
+<dd><p>Open a file from a file handle (wrapper to the libc
+<code>fdopen()</code>). Throws <code>std.Error</code> in case of I/O error.
+</p>
+</dd>
+<dt><code>tmpfile()</code></dt>
+<dd><p>Open a temporary file. Throws <code>std.Error</code> in case of I/O error.
+</p>
+</dd>
+<dt><code>puts(str)</code></dt>
+<dd><p>Equivalent to <code>std.out.puts(str)</code>.
+</p>
+</dd>
+<dt><code>printf(fmt, ...args)</code></dt>
+<dd><p>Equivalent to <code>std.out.printf(fmt, ...args)</code>
+</p>
+</dd>
+<dt><code>sprintf(fmt, ...args)</code></dt>
+<dd><p>Equivalent to the libc sprintf().
+</p>
+</dd>
+<dt><code>in</code></dt>
+<dt><code>out</code></dt>
+<dt><code>err</code></dt>
+<dd><p>Wrappers to the libc file <code>stdin</code>, <code>stdout</code>, <code>stderr</code>.
+</p>
+</dd>
+<dt><code>SEEK_SET</code></dt>
+<dt><code>SEEK_CUR</code></dt>
+<dt><code>SEEK_END</code></dt>
+<dd><p>Constants for seek().
+</p>
+</dd>
+<dt><code>gc()</code></dt>
+<dd><p>Manually invoke the cycle removal algorithm. The cycle removal
+algorithm is automatically started when needed, so this function is
+useful in case of specific memory constraints or for testing.
+</p>
+</dd>
+<dt><code>getenv(name)</code></dt>
+<dd><p>Return the value of the environment variable <code>name</code> or
+<code>undefined</code> if it is not defined.
+</p>
+</dd>
+<dt><code>urlGet(url, options = undefined)</code></dt>
+<dd>
+<p>Download <code>url</code> using the <samp>curl</samp> command line
+utility. <code>options</code> is an optional object containing the following
+optional properties:
+</p>
+<dl compact="compact">
+<dt><code>binary</code></dt>
+<dd><p>Boolean (default = false). If true, the response is an ArrayBuffer
+  instead of a string. When a string is returned, the data is assumed
+  to be UTF-8 encoded.
+</p>
+</dd>
+<dt><code>full</code></dt>
+<dd><p>Boolean (default = false). If true, return the an object contains
+  the properties <code>response</code> (response content),
+  <code>responseHeaders</code> (headers separated by CRLF), <code>status</code>
+  (status code). If <code>full</code> is false, only the response is
+  returned if the status is between 200 and 299. Otherwise an
+  <code>std.Error</code> exception is raised.
+</p>
+</dd>
+</dl>
+
+</dd>
+</dl>
+
+<p>FILE prototype:
+</p>
+<dl compact="compact">
+<dt><code>close()</code></dt>
+<dd><p>Close the file.
+</p></dd>
+<dt><code>puts(str)</code></dt>
+<dd><p>Outputs the string with the UTF-8 encoding.
+</p></dd>
+<dt><code>printf(fmt, ...args)</code></dt>
+<dd><p>Formatted printf, same formats as the libc printf.
+</p></dd>
+<dt><code>flush()</code></dt>
+<dd><p>Flush the buffered file.
+</p></dd>
+<dt><code>seek(offset, whence)</code></dt>
+<dd><p>Seek to a give file position (whence is <code>std.SEEK_*</code>). Throws a
+<code>std.Error</code> in case of I/O error.
+</p></dd>
+<dt><code>tell()</code></dt>
+<dd><p>Return the current file position.
+</p></dd>
+<dt><code>eof()</code></dt>
+<dd><p>Return true if end of file.
+</p></dd>
+<dt><code>fileno()</code></dt>
+<dd><p>Return the associated OS handle.
+</p>
+</dd>
+<dt><code>read(buffer, position, length)</code></dt>
+<dd><p>Read <code>length</code> bytes from the file to the ArrayBuffer <code>buffer</code> at byte
+position <code>position</code> (wrapper to the libc <code>fread</code>).
+</p>
+</dd>
+<dt><code>write(buffer, position, length)</code></dt>
+<dd><p>Write <code>length</code> bytes to the file from the ArrayBuffer <code>buffer</code> at byte
+position <code>position</code> (wrapper to the libc <code>fread</code>).
+</p>
+</dd>
+<dt><code>getline()</code></dt>
+<dd><p>Return the next line from the file, assuming UTF-8 encoding, excluding
+the trailing line feed.
+</p>
+</dd>
+<dt><code>getByte()</code></dt>
+<dd><p>Return the next byte from the file.
+</p>
+</dd>
+<dt><code>putByte(c)</code></dt>
+<dd><p>Write one byte to the file.
+</p></dd>
+</dl>
+
+<a name="os-module"></a>
+<h4 class="subsection">3.3.3 <code>os</code> module</h4>
+
+<p>The <code>os</code> module provides Operating System specific functions:
+</p>
+<ul>
+<li> low level file access
+</li><li> signals
+</li><li> timers
+</li><li> asynchronous I/O
+</li></ul>
+
+<p>The OS functions usually return 0 if OK or an OS specific negative
+error code.
+</p>
+<p>Available exports:
+</p>
+<dl compact="compact">
+<dt><code>open(filename, flags, mode = 0o666)</code></dt>
+<dd><p>Open a file. Return a handle or &lt; 0 if error.
+</p>
+</dd>
+<dt><code>O_RDONLY</code></dt>
+<dt><code>O_WRONLY</code></dt>
+<dt><code>O_RDWR</code></dt>
+<dt><code>O_APPEND</code></dt>
+<dt><code>O_CREAT</code></dt>
+<dt><code>O_EXCL</code></dt>
+<dt><code>O_TRUNC</code></dt>
+<dd><p>POSIX open flags.
+</p>
+</dd>
+<dt><code>O_TEXT</code></dt>
+<dd><p>(Windows specific). Open the file in text mode. The default is binary mode.
+</p>
+</dd>
+<dt><code>close(fd)</code></dt>
+<dd><p>Close the file handle <code>fd</code>.
+</p>
+</dd>
+<dt><code>seek(fd, offset, whence)</code></dt>
+<dd><p>Seek in the file. Use <code>std.SEEK_*</code> for <code>whence</code>.
+</p>
+</dd>
+<dt><code>read(fd, buffer, offset, length)</code></dt>
+<dd><p>Read <code>length</code> bytes from the file handle <code>fd</code> to the
+ArrayBuffer <code>buffer</code> at byte position <code>offset</code>.
+Return the number of read bytes or &lt; 0 if error.
+</p>
+</dd>
+<dt><code>write(fd, buffer, offset, length)</code></dt>
+<dd><p>Write <code>length</code> bytes to the file handle <code>fd</code> from the
+ArrayBuffer <code>buffer</code> at byte position <code>offset</code>.
+Return the number of written bytes or &lt; 0 if error.
+</p>
+</dd>
+<dt><code>isatty(fd)</code></dt>
+<dd><p>Return <code>true</code> is <code>fd</code> is a TTY (terminal) handle.
+</p>
+</dd>
+<dt><code>ttyGetWinSize(fd)</code></dt>
+<dd><p>Return the TTY size as <code>[width, height]</code> or <code>null</code> if not available.
+</p>
+</dd>
+<dt><code>ttySetRaw(fd)</code></dt>
+<dd><p>Set the TTY in raw mode.
+</p>
+</dd>
+<dt><code>remove(filename)</code></dt>
+<dd><p>Remove a file. Return 0 if OK or &lt; 0 if error.
+</p>
+</dd>
+<dt><code>rename(oldname, newname)</code></dt>
+<dd><p>Rename a file. Return 0 if OK or &lt; 0 if error.
+</p>
+</dd>
+<dt><code>realpath(path)</code></dt>
+<dd><p>Return <code>[str, err]</code> where <code>str</code> is the canonicalized absolute
+pathname of <code>path</code> and <code>err</code> the error code.
+</p>
+</dd>
+<dt><code>getcwd()</code></dt>
+<dd><p>Return <code>[str, err]</code> where <code>str</code> is the current working directory
+and <code>err</code> the error code.
+</p>
+</dd>
+<dt><code>mkdir(path, mode = 0o777)</code></dt>
+<dd><p>Create a directory at <code>path</code>. Return the error code.
+</p>
+</dd>
+<dt><code>stat(path)</code></dt>
+<dt><code>lstat(path)</code></dt>
+<dd>
+<p>Return <code>[obj, err]</code> where <code>obj</code> is an object containing the
+file status of <code>path</code>. <code>err</code> is the error code. The
+following fields are defined in <code>obj</code>: dev, ino, mode, nlink,
+uid, gid, rdev, size, blocks, atime, mtime, ctime. The times are
+specified in milliseconds since 1970. <code>lstat()</code> is the same as
+<code>stat()</code> excepts that it returns information about the link
+itself.
+</p>
+</dd>
+<dt><code>S_IFMT</code></dt>
+<dt><code>S_IFIFO</code></dt>
+<dt><code>S_IFCHR</code></dt>
+<dt><code>S_IFDIR</code></dt>
+<dt><code>S_IFBLK</code></dt>
+<dt><code>S_IFREG</code></dt>
+<dt><code>S_IFSOCK</code></dt>
+<dt><code>S_IFLNK</code></dt>
+<dt><code>S_ISGID</code></dt>
+<dt><code>S_ISUID</code></dt>
+<dd><p>Constants to interpret the <code>mode</code> property returned by
+<code>stat()</code>. They have the same value as in the C system header
+<samp>sys/stat.h</samp>.
+</p>
+</dd>
+<dt><code>utimes(path, atime, mtime)</code></dt>
+<dd><p>Change the access and modification times of the file <code>path</code>. The
+times are specified in milliseconds since 1970.
+</p>
+</dd>
+<dt><code>symlink(target, linkpath)</code></dt>
+<dd><p>Create a link at <code>linkpath</code> containing the string <code>target</code>.
+</p>
+</dd>
+<dt><code>readlink(path)</code></dt>
+<dd><p>Return <code>[str, err]</code> where <code>str</code> is the link target and <code>err</code>
+the error code.
+</p>
+</dd>
+<dt><code>readdir(path)</code></dt>
+<dd><p>Return <code>[array, err]</code> where <code>array</code> is an array of strings
+containing the filenames of the directory <code>path</code>. <code>err</code> is
+the error code.
+</p>
+</dd>
+<dt><code>setReadHandler(fd, func)</code></dt>
+<dd><p>Add a read handler to the file handle <code>fd</code>. <code>func</code> is called
+each time there is data pending for <code>fd</code>. A single read handler
+per file handle is supported. Use <code>func = null</code> to remove the
+hander.
+</p>
+</dd>
+<dt><code>setWriteHandler(fd, func)</code></dt>
+<dd><p>Add a write handler to the file handle <code>fd</code>. <code>func</code> is
+called each time data can be written to <code>fd</code>. A single write
+handler per file handle is supported. Use <code>func = null</code> to remove
+the hander.
+</p>
+</dd>
+<dt><code>signal(signal, func)</code></dt>
+<dd><p>Call the function <code>func</code> when the signal <code>signal</code>
+happens. Only a single handler per signal number is supported. Use
+<code>null</code> to set the default handler or <code>undefined</code> to ignore
+the signal.
+</p>
+</dd>
+<dt><code>SIGINT</code></dt>
+<dt><code>SIGABRT</code></dt>
+<dt><code>SIGFPE</code></dt>
+<dt><code>SIGILL</code></dt>
+<dt><code>SIGSEGV</code></dt>
+<dt><code>SIGTERM</code></dt>
+<dd><p>POSIX signal numbers.
+</p>
+</dd>
+<dt><code>kill(pid, sig)</code></dt>
+<dd><p>Send the signal <code>sig</code> to the process <code>pid</code>.
+</p>
+</dd>
+<dt><code>exec(args[, options])</code></dt>
+<dd><p>Execute a process with the arguments <code>args</code>. <code>options</code> is an
+object containing optional parameters:
+</p>
+<dl compact="compact">
+<dt><code>block</code></dt>
+<dd><p>Boolean (default = true). If true, wait until the process is
+  termined. In this case, <code>exec</code> return the exit code if positive
+  or the negated signal number if the process was interrupted by a
+  signal. If false, do not block and return the process id of the child.
+</p>  
+</dd>
+<dt><code>usePath</code></dt>
+<dd><p>Boolean (default = true). If true, the file is searched in the
+  <code>PATH</code> environment variable.
+</p>
+</dd>
+<dt><code>file</code></dt>
+<dd><p>String (default = <code>args[0]</code>). Set the file to be executed.
+</p>
+</dd>
+<dt><code>cwd</code></dt>
+<dd><p>String. If present, set the working directory of the new process.
+</p>
+</dd>
+<dt><code>stdin</code></dt>
+<dt><code>stdout</code></dt>
+<dt><code>stderr</code></dt>
+<dd><p>If present, set the handle in the child for stdin, stdout or stderr.
+</p> 
+</dd>
+</dl>
+
+</dd>
+<dt><code>waitpid(pid, options)</code></dt>
+<dd><p><code>waitpid</code> Unix system call. Return the array <code>[ret, status]</code>.
+</p>
+</dd>
+<dt><code>WNOHANG</code></dt>
+<dd><p>Constant for the <code>options</code> argument of <code>waitpid</code>.
+</p>
+</dd>
+<dt><code>dup(fd)</code></dt>
+<dd><p><code>dup</code> Unix system call.
+</p>
+</dd>
+<dt><code>dup2(oldfd, newfd)</code></dt>
+<dd><p><code>dup2</code> Unix system call.
+</p>
+</dd>
+<dt><code>pipe()</code></dt>
+<dd><p><code>pipe</code> Unix system call. Return two handles as <code>[read_fd,
+write_fd]</code> or null in case of error.
+</p>
+</dd>
+<dt><code>sleep(delay_ms)</code></dt>
+<dd><p>Sleep during <code>delay_ms</code> milliseconds.
+</p>
+</dd>
+<dt><code>setTimeout(func, delay)</code></dt>
+<dd><p>Call the function <code>func</code> after <code>delay</code> ms. Return a handle
+to the timer.
+</p>
+</dd>
+<dt><code>clearTimeout(handle)</code></dt>
+<dd><p>Cancel a timer.
+</p>
+</dd>
+<dt><code>platform</code></dt>
+<dd><p>Return a string representing the platform: <code>&quot;linux&quot;</code>, <code>&quot;darwin&quot;</code>,
+<code>&quot;win32&quot;</code> or <code>&quot;js&quot;</code>.
+</p>
+</dd>
+</dl>
+
+<a name="QuickJS-C-API"></a>
+<h3 class="section">3.4 QuickJS C API</h3>
+
+<p>The C API was designed to be simple and efficient. The C API is
+defined in the header <code>quickjs.h</code>.
+</p>
+<a name="Runtime-and-contexts"></a>
+<h4 class="subsection">3.4.1 Runtime and contexts</h4>
+
+<p><code>JSRuntime</code> represents a Javascript runtime corresponding to an
+object heap. Several runtimes can exist at the same time but they
+cannot exchange objects. Inside a given runtime, no multi-threading is
+supported.
+</p>
+<p><code>JSContext</code> represents a Javascript context (or Realm). Each
+JSContext has its own global objects and system objects. There can be
+several JSContexts per JSRuntime and they can share objects, similary
+to frames of the same origin sharing Javascript objects in a
+web browser.
+</p>
+<a name="JSValue"></a>
+<h4 class="subsection">3.4.2 JSValue</h4>
+
+<p><code>JSValue</code> represents a Javascript value which can be a primitive
+type or an object. Reference counting is used, so it is important to
+explicitely duplicate (<code>JS_DupValue()</code>, increment the reference
+count) or free (<code>JS_FreeValue()</code>, decrement the reference count)
+JSValues.
+</p>
+<a name="C-functions"></a>
+<h4 class="subsection">3.4.3 C functions</h4>
+
+<p>C functions can be created with
+<code>JS_NewCFunction()</code>. <code>JS_SetPropertyFunctionList()</code> is a
+shortcut to easily add functions, setters and getters properties to a
+given object.
+</p>
+<p>Unlike other embedded Javascript engines, there is no implicit stack,
+so C functions get their parameters as normal C parameters. As a
+general rule, C functions take constant <code>JSValue</code>s as parameters
+(so they don&rsquo;t need to free them) and return a newly allocated (=live)
+<code>JSValue</code>.
+</p>
+<a name="Exceptions"></a>
+<h4 class="subsection">3.4.4 Exceptions</h4>
+
+<p>Exceptions: most C functions can return a Javascript exception. It
+must be explicitely tested and handled by the C code. The specific
+<code>JSValue</code> <code>JS_EXCEPTION</code> indicates that an exception
+occured. The actual exception object is stored in the
+<code>JSContext</code> and can be retrieved with <code>JS_GetException()</code>.
+</p>
+<a name="Script-evaluation"></a>
+<h4 class="subsection">3.4.5 Script evaluation</h4>
+
+<p>Use <code>JS_Eval()</code> to evaluate a script or module source.
+</p>
+<p>If the script or module was compiled to bytecode with <code>qjsc</code>, it
+can be evaluated by calling <code>js_std_eval_binary()</code>. The advantage
+is that no compilation is needed so it is faster and smaller because
+the compiler can be removed from the executable if no <code>eval</code> is
+required.
+</p>
+<p>Note: the bytecode format is linked to a given QuickJS
+version. Moreover, no security check is done before its
+execution. Hence the bytecode should not be loaded from untrusted
+sources. That&rsquo;s why there is no option to output the bytecode to a
+binary file in <code>qjsc</code>.
+</p>
+<a name="JS-Classes"></a>
+<h4 class="subsection">3.4.6 JS Classes</h4>
+
+<p>C opaque data can be attached to a Javascript object. The type of the
+C opaque data is determined with the class ID (<code>JSClassID</code>) of
+the object. Hence the first step is to register a new class ID and JS
+class (<code>JS_NewClassID()</code>, <code>JS_NewClass()</code>). Then you can
+create objects of this class with <code>JS_NewObjectClass()</code> and get or
+set the C opaque point with
+<code>JS_GetOpaque()</code>/<code>JS_SetOpaque()</code>.
+</p>
+<p>When defining a new JS class, it is possible to declare a finalizer
+which is called when the object is destroyed. A <code>gc_mark</code> method
+can be provided so that the cycle removal algorithm can find the other
+objects referenced by this object. Other methods are available to
+define exotic object behaviors.
+</p>
+<p>The Class ID are globally allocated (i.e. for all runtimes). The
+JSClass are allocated per <code>JSRuntime</code>. <code>JS_SetClassProto()</code>
+is used to define a prototype for a given class in a given
+JSContext. <code>JS_NewObjectClass()</code> sets this prototype in the
+created object.
+</p>
+<p>Examples are available in <samp>quickjs-libc.c</samp>.
+</p>
+<a name="C-Modules"></a>
+<h4 class="subsection">3.4.7 C Modules</h4>
+
+<p>Native ES6 modules are supported and can be dynamically or statically
+linked. Look at the <samp>test_bjson</samp> and <samp>bjson.so</samp>
+examples. The standard library <samp>quickjs-libc.c</samp> is also a good example
+of a native module.
+</p>
+<a name="Memory-handling"></a>
+<h4 class="subsection">3.4.8 Memory handling</h4>
+
+<p>Use <code>JS_SetMemoryLimit()</code> to set a global memory allocation limit
+to a given JSRuntime.
+</p>
+<p>Custom memory allocation functions can be provided with
+<code>JS_NewRuntime2()</code>.
+</p>
+<p>The maximum system stack size can be set with <code>JS_SetMaxStackSize()</code>.
+</p>
+<a name="Execution-timeout-and-interrupts"></a>
+<h4 class="subsection">3.4.9 Execution timeout and interrupts</h4>
+
+<p>Use <code>JS_SetInterruptHandler()</code> to set a callback which is
+regularly called by the engine when it is executing code. This
+callback can be used to implement an execution timeout.
+</p>
+<p>It is used by the command line interpreter to implement a
+<code>Ctrl-C</code> handler.
+</p>
+<a name="Internals"></a>
+<h2 class="chapter">4 Internals</h2>
+
+<a name="Bytecode"></a>
+<h3 class="section">4.1 Bytecode</h3>
+
+<p>The compiler generates bytecode directly with no intermediate
+representation such as a parse tree, hence it is very fast. Several
+optimizations passes are done over the generated bytecode.
+</p>
+<p>A stack-based bytecode was chosen because it is simple and generates 
+compact code.
+</p>
+<p>For each function, the maximum stack size is computed at compile time so that
+no runtime stack overflow tests are needed.
+</p>
+<p>A separate compressed line number table is maintained for the debug
+information.
+</p>
+<p>Access to closure variables is optimized and is almost as fast as local
+variables.
+</p>
+<p>Direct <code>eval</code> in strict mode is optimized.
+</p>
+<a name="Executable-generation"></a>
+<h3 class="section">4.2 Executable generation</h3>
+
+<a name="qjsc-compiler-1"></a>
+<h4 class="subsection">4.2.1 <code>qjsc</code> compiler</h4>
+
+<p>The <code>qjsc</code> compiler generates C sources from Javascript files. By
+default the C sources are compiled with the system compiler
+(<code>gcc</code> or <code>clang</code>).
+</p>
+<p>The generated C source contains the bytecode of the compiled functions
+or modules. If a full complete executable is needed, it also
+contains a <code>main()</code> function with the necessary C code to initialize the
+Javascript engine and to load and execute the compiled functions and
+modules.
+</p>
+<p>Javascript code can be mixed with C modules.
+</p>
+<p>In order to have smaller executables, specific Javascript features can
+be disabled, in particular <code>eval</code> or the regular expressions. The
+code removal relies on the Link Time Optimization of the system
+compiler.
+</p>
+<a name="Binary-JSON"></a>
+<h4 class="subsection">4.2.2 Binary JSON</h4>
+
+<p><code>qjsc</code> works by compiling scripts or modules and then serializing
+them to a binary format. A subset of this format (without functions or
+modules) can be used as binary JSON. The example <samp>test_bjson.js</samp>
+shows how to use it.
+</p>
+<p>Warning: the binary JSON format may change without notice, so it
+should not be used to store persistent data. The <samp>test_bjson.js</samp>
+example is only used to test the binary object format functions.
+</p>
+<a name="Runtime"></a>
+<h3 class="section">4.3 Runtime</h3>
+
+<a name="Strings"></a>
+<h4 class="subsection">4.3.1 Strings</h4>
+
+<p>Strings are stored either as an 8 bit or a 16 bit array of
+characters. Hence random access to characters is always fast.
+</p>
+<p>The C API provides functions to convert Javascript Strings to C UTF-8 encoded
+strings. The most common case where the Javascript string contains
+only ASCII characters involves no copying.
+</p>
+<a name="Objects"></a>
+<h4 class="subsection">4.3.2 Objects</h4>
+
+<p>The object shapes (object prototype, property names and flags) are shared
+between objects to save memory.
+</p>
+<p>Arrays with no holes (except at the end of the array) are optimized.
+</p>
+<p>TypedArray accesses are optimized.
+</p>
+<a name="Atoms"></a>
+<h4 class="subsection">4.3.3 Atoms</h4>
+
+<p>Object property names and some strings are stored as Atoms (unique
+strings) to save memory and allow fast comparison. Atoms are
+represented as a 32 bit integer. Half of the atom range is reserved for
+immediate integer literals from <em>0</em> to <em>2^{31}-1</em>.
+</p>
+<a name="Numbers"></a>
+<h4 class="subsection">4.3.4 Numbers</h4>
+
+<p>Numbers are represented either as 32-bit signed integers or 64-bit IEEE-754
+floating point values. Most operations have fast paths for the 32-bit
+integer case.
+</p>
+<a name="Garbage-collection"></a>
+<h4 class="subsection">4.3.5 Garbage collection</h4>
+
+<p>Reference counting is used to free objects automatically and
+deterministically. A separate cycle removal pass is done when the allocated
+memory becomes too large. The cycle removal algorithm only uses the
+reference counts and the object content, so no explicit garbage
+collection roots need to be manipulated in the C code.
+</p>
+<a name="JSValue-1"></a>
+<h4 class="subsection">4.3.6 JSValue</h4>
+
+<p>It is a Javascript value which can be a primitive type (such as
+Number, String, ...) or an Object. NaN boxing is used in the 32-bit version
+to store 64-bit floating point numbers. The representation is
+optimized so that 32-bit integers and reference counted values can be
+efficiently tested.
+</p>
+<p>In 64-bit code, JSValue are 128-bit large and no NaN boxing is used. The
+rationale is that in 64-bit code memory usage is less critical.
+</p>
+<p>In both cases (32 or 64 bits), JSValue exactly fits two CPU registers,
+so it can be efficiently returned by C functions.
+</p>
+<a name="Function-call"></a>
+<h4 class="subsection">4.3.7 Function call</h4>
+
+<p>The engine is optimized so that function calls are fast. The system
+stack holds the Javascript parameters and local variables.
+</p>
+<a name="RegExp"></a>
+<h3 class="section">4.4 RegExp</h3>
+
+<p>A specific regular expression engine was developped. It is both small
+and efficient and supports all the ES2020 features including the
+Unicode properties. As the Javascript compiler, it directly generates
+bytecode without a parse tree.
+</p>
+<p>Backtracking with an explicit stack is used so that there is no
+recursion on the system stack. Simple quantizers are specifically
+optimized to avoid recursions.
+</p>
+<p>Infinite recursions coming from quantizers with empty terms are
+avoided.
+</p>
+<p>The full regexp library weights about 15 KiB (x86 code), excluding the
+Unicode library.
+</p>
+<a name="Unicode"></a>
+<h3 class="section">4.5 Unicode</h3>
+
+<p>A specific Unicode library was developped so that there is no
+dependency on an external large Unicode library such as ICU. All the
+Unicode tables are compressed while keeping a reasonnable access
+speed.
+</p>
+<p>The library supports case conversion, Unicode normalization, Unicode
+script queries, Unicode general category queries and all Unicode
+binary properties.
+</p>
+<p>The full Unicode library weights about 45 KiB (x86 code).
+</p>
+<a name="BigInt-and-BigFloat"></a>
+<h3 class="section">4.6 BigInt and BigFloat</h3>
+
+<p>BigInt and BigFloat are implemented with the <code>libbf</code>
+library<a name="DOCF7" href="#FOOT7"><sup>7</sup></a>. It weights about 60
+KiB (x86 code) and provides arbitrary precision IEEE 754 floating
+point operations and transcendental functions with exact rounding.
+</p>
+<a name="License"></a>
+<h2 class="chapter">5 License</h2>
+
+<p>QuickJS is released under the MIT license.
+</p>
+<p>Unless otherwise specified, the QuickJS sources are copyright Fabrice
+Bellard and Charlie Gordon.
+</p>
+<div class="footnote">
+<hr>
+<h4 class="footnotes-heading">Footnotes</h4>
+
+<h3><a name="FOOT1" href="#DOCF1">(1)</a></h3>
+<p><a href="https://www.ecma-international.org/ecma-262/10.0">https://www.ecma-international.org/ecma-262/10.0</a></p>
+<h3><a name="FOOT2" href="#DOCF2">(2)</a></h3>
+<p><a href="https://github.com/tc39/test262">https://github.com/tc39/test262</a></p>
+<h3><a name="FOOT3" href="#DOCF3">(3)</a></h3>
+<p><a href="https://tc39.github.io/ecma262/">https://tc39.github.io/ecma262/</a></p>
+<h3><a name="FOOT4" href="#DOCF4">(4)</a></h3>
+<p>The old ES5.1 tests can be extracted with
+<code>git clone --single-branch --branch es5-tests
+https://github.com/tc39/test262.git test262o</code></p>
+<h3><a name="FOOT5" href="#DOCF5">(5)</a></h3>
+<p><a href="https://github.com/bterlson/test262-harness">https://github.com/bterlson/test262-harness</a></p>
+<h3><a name="FOOT6" href="#DOCF6">(6)</a></h3>
+<p>We believe the current specification of tails calls is too complicated and presents limited practical interests.</p>
+<h3><a name="FOOT7" href="#DOCF7">(7)</a></h3>
+<p><a href="https://bellard.org/libbf">https://bellard.org/libbf</a></p>
+</div>
+<hr>
+
+
+
+</body>
+</html>
diff --git a/doc/quickjs.pdf b/doc/quickjs.pdf
new file mode 100644
index 0000000..a89edb8
--- /dev/null
+++ b/doc/quickjs.pdf
Binary files differ
diff --git a/doc/quickjs.texi b/doc/quickjs.texi
new file mode 100644
index 0000000..952e6f0
--- /dev/null
+++ b/doc/quickjs.texi
@@ -0,0 +1,979 @@
+\input texinfo
+
+@iftex
+@afourpaper
+@headings double
+@end iftex
+
+@titlepage
+@afourpaper
+@sp 7
+@center @titlefont{QuickJS Javascript Engine}
+@sp 3
+@end titlepage
+
+@setfilename spec.info
+@settitle QuickJS Javascript Engine
+
+@contents
+
+@chapter Introduction
+
+QuickJS is a small and embeddable Javascript engine. It supports the
+ES2019 specification
+@footnote{@url{https://www.ecma-international.org/ecma-262/10.0}}
+including modules, asynchronous generators and proxies.
+
+It optionally supports mathematical extensions such as big integers
+(BigInt), big floating point numbers (BigFloat) and operator
+overloading.
+
+@section Main Features
+
+@itemize
+
+@item Small and easily embeddable: just a few C files, no external dependency, 180 KiB of x86 code for a simple ``hello world'' program.
+
+@item Fast interpreter with very low startup time: runs the 69000 tests of the ECMAScript Test Suite@footnote{@url{https://github.com/tc39/test262}} in about 95 seconds on a single core of a desktop PC. The complete life cycle of a runtime instance completes in less than 300 microseconds.
+
+@item Almost complete ES2019 support including modules, asynchronous
+generators and full Annex B support (legacy web compatibility). Many
+features from the upcoming ES2020 specification
+@footnote{@url{https://tc39.github.io/ecma262/}} are also supported.
+
+@item Passes nearly 100% of the ECMAScript Test Suite tests when selecting the ES2019 features.
+
+@item Can compile Javascript sources to executables with no external dependency.
+
+@item Garbage collection using reference counting (to reduce memory usage and have deterministic behavior) with cycle removal.
+
+@item Mathematical extensions: BigInt, BigFloat, operator overloading, bigint mode, math mode.
+
+@item Command line interpreter with contextual colorization and completion implemented in Javascript.
+
+@item Small built-in standard library with C library wrappers.
+
+@end itemize
+
+@chapter Usage
+
+@section Installation
+
+A Makefile is provided to compile the engine on Linux or MacOS/X.  A
+preliminary Windows support is available thru cross compilation on a
+Linux host with the MingGW tools.
+
+Edit the top of the @code{Makefile} if you wish to select specific
+options then run @code{make}.
+
+You can type @code{make install} as root if you wish to install the binaries and support files to
+@code{/usr/local} (this is not necessary to use QuickJS).
+
+@section Quick start
+
+@code{qjs} is the command line interpreter (Read-Eval-Print Loop). You can pass
+Javascript files and/or expressions as arguments to execute them:
+
+@example
+./qjs examples/hello.js
+@end example
+
+@code{qjsc} is the command line compiler:
+
+@example
+./qjsc -o hello examples/hello.js
+./hello
+@end example
+
+generates a @code{hello} executable with no external dependency.
+
+@code{qjsbn} and @code{qjscbn} are the corresponding interpreter and
+compiler with the mathematical extensions:
+
+@example
+./qjsbn examples/pi.js 1000
+@end example
+
+displays 1000 digits of PI.
+
+@example
+./qjsbnc -o pi examples/pi.js
+./pi 1000
+@end example
+
+compiles and executes the PI program.
+
+@section Command line options
+
+@subsection @code{qjs} interpreter
+
+@verbatim
+usage: qjs [options] [files]
+@end verbatim
+
+Options are:
+@table @code
+@item -h
+@item --help
+List options.
+
+@item -e @code{EXPR}
+@item --eval @code{EXPR}
+Evaluate EXPR.
+
+@item -i
+@item --interactive
+Go to interactive mode (it is not the default when files are provided on the command line).
+
+@item -m
+@item --module
+Load as ES6 module (default=autodetect).
+
+@item --script
+Load as ES6 script (default=autodetect).
+
+@end table
+
+Advanced options are:
+
+@table @code
+@item -d
+@item --dump
+Dump the memory usage stats.
+
+@item -q
+@item --quit
+just instantiate the interpreter and quit.
+
+@end table
+
+@subsection @code{qjsc} compiler
+
+@verbatim
+usage: qjsc [options] [files]
+@end verbatim
+
+Options are:
+@table @code
+@item -c
+Only output bytecode in a C file. The default is to output an executable file.
+@item -e 
+Output @code{main()} and bytecode in a C file. The default is to output an
+executable file.
+@item -o output
+Set the output filename (default = @file{out.c} or @file{a.out}).
+
+@item -N cname
+Set the C name of the generated data.
+
+@item -m
+Compile as Javascript module (default=autodetect).
+
+@item -M module_name[,cname]
+Add initialization code for an external C module. See the
+@code{c_module} example.
+
+@item -x
+Byte swapped output (only used for cross compilation).
+
+@item -flto
+Use link time optimization. The compilation is slower but the
+executable is smaller and faster. This option is automatically set
+when the @code{-fno-x} options are used.
+
+@item -fno-[eval|string-normalize|regexp|json|proxy|map|typedarray|promise]
+Disable selected language features to produce a smaller executable file.
+
+@end table
+
+@section @code{qjscalc} application
+
+The @code{qjscalc} application is a superset of the @code{qjsbn}
+command line interpreter implementing a Javascript calculator with
+arbitrarily large integer and floating point numbers, fractions,
+complex numbers, polynomials and matrices. The source code is in
+@file{qjscalc.js}. More documentation and a web version are available at
+@url{http://numcalc.com}.
+
+@section Built-in tests
+
+Run @code{make test} to run the few built-in tests included in the
+QuickJS archive.
+
+@section Test262 (ECMAScript Test Suite)
+
+A test262 runner is included in the QuickJS archive.
+
+For reference, the full test262 tests are provided in the archive
+@file{qjs-tests-yyyy-mm-dd.tar.xz}. You just need to untar it into the
+QuickJS source code directory.
+
+Alternatively, the test262 tests can be installed with:
+
+@example
+git clone https://github.com/tc39/test262.git test262
+cd test262
+patch -p1 < ../tests/test262.patch
+cd ..
+@end example
+
+The patch adds the implementation specific @code{harness} functions
+and optimizes the inefficient RegExp character classes and Unicode
+property escapes tests (the tests themselves are not modified, only a
+slow string initialization function is optimized).
+
+The tests can be run with
+@example
+make test2
+@end example
+
+The configuration files @code{test262.conf} (resp
+@code{test262bn.conf} for the bignum version, @code{test262o.conf} for
+the old ES5.1 tests@footnote{The old ES5.1 tests can be extracted with
+@code{git clone --single-branch --branch es5-tests
+https://github.com/tc39/test262.git test262o}})) contain the options
+to run the various tests. Tests can be excluded based on features or
+filename.
+
+The file @code{test262_errors.txt} contains the current list of
+errors. The runner displays a message when a new error appears or when
+an existing error is corrected or modified. Use the @code{-u} option
+to update the current list of errors (or @code{make test2-update}).
+
+The file @code{test262_report.txt} contains the logs of all the
+tests. It is useful to have a clearer analysis of a particular
+error. In case of crash, the last line corresponds to the failing
+test.
+
+Use the syntax @code{./run-test262 -c test262.conf -f filename.js} to
+run a single test. Use the syntax @code{./run-test262 -c test262.conf
+N} to start testing at test number @code{N}.
+
+For more information, run @code{./run-test262} to see the command line
+options of the test262 runner.
+
+@code{run-test262} accepts the @code{-N} option to be invoked from
+@code{test262-harness}@footnote{@url{https://github.com/bterlson/test262-harness}}
+thru @code{eshost}. Unless you want to compare QuickJS with other
+engines under the same conditions, we do not recommend to run the
+tests this way as it is much slower (typically half an hour instead of
+about 100 seconds).
+
+@chapter Specifications
+
+@section Language support
+
+@subsection ES2019 support
+
+The ES2019 specification is almost fully supported including the Annex
+B (legacy web compatibility) and the Unicode related features.
+
+The following features are not supported yet:
+
+@itemize
+
+@item Realms (althougth the C API supports different runtimes and contexts)
+
+@item Tail calls@footnote{We believe the current specification of tails calls is too complicated and presents limited practical interests.}
+
+@end itemize
+
+@subsection JSON
+
+The JSON parser is currently more tolerant than the specification.
+
+@subsection ECMA402
+
+ECMA402 (Internationalization API) is not supported.
+
+@subsection Extensions
+
+@itemize
+
+@item The directive @code{"use strip"} indicates that the debug information (including the source code of the functions) should not be retained to save memory. As @code{"use strict"}, the directive can be global to a script or local to a function.
+
+@item The first line of a script beginning with @code{#!} is ignored.
+
+@end itemize
+
+@subsection Mathematical extensions
+
+The mathematical extensions are available in the @code{qjsbn} version and are fully
+backward compatible with standard Javascript. See @code{jsbignum.pdf}
+for more information.
+
+@itemize
+
+@item The @code{BigInt} (big integers) TC39 proposal is supported.
+
+@item @code{BigFloat} support: arbitrary large floating point numbers in base 2.
+
+@item Operator overloading.
+
+@item The directive @code{"use bigint"} enables the bigint mode where integers are @code{BigInt} by default.
+
+@item The directive @code{"use math"} enables the math mode where the division and power operators on integers produce fractions. Floating point literals are @code{BigFloat} by default and integers are @code{BigInt} by default.
+
+@end itemize
+
+@section Modules
+
+ES6 modules are fully supported. The default name resolution is the
+following:
+
+@itemize
+
+@item Module names with a leading @code{.} or @code{..} are relative
+to the current module path.
+
+@item Module names without a leading @code{.} or @code{..} are system
+modules, such as @code{std} or @code{os}.
+
+@item Module names ending with @code{.so} are native modules using the
+QuickJS C API.
+
+@end itemize
+
+@section Standard library
+
+The standard library is included by default in the command line
+interpreter. It contains the two modules @code{std} and @code{os} and
+a few global objects.
+
+@subsection Global objects
+
+@table @code
+@item scriptArgs
+Provides the command line arguments. The first argument is the script name.
+@item print(...args)
+Print the arguments separated by spaces and a trailing newline.
+@item console.log(...args)
+Same as print().
+
+@end table
+
+@subsection @code{std} module
+
+The @code{std} module provides wrappers to the libc @file{stdlib.h}
+and @file{stdio.h} and a few other utilities.
+
+Available exports:
+
+@table @code
+
+@item exit(n)
+Exit the process.
+
+@item evalScript(str)
+Evaluate the string @code{str} as a script (global eval).
+
+@item loadScript(filename)
+Evaluate the file @code{filename} as a script (global eval).
+
+@item Error(errno)
+
+@code{std.Error} constructor. Error instances contain the field
+@code{errno} (error code) and @code{message} (result of
+@code{std.Error.strerror(errno)}).
+
+The constructor contains the following fields:
+
+  @table @code
+  @item EINVAL
+  @item EIO
+  @item EACCES
+  @item EEXIST
+  @item ENOSPC
+  @item ENOSYS
+  @item EBUSY
+  @item ENOENT
+  @item EPERM
+  @item EPIPE
+    Integer value of common errors (additional error codes may be defined).
+  @item strerror(errno)
+    Return a string that describes the error @code{errno}.
+  @end table
+
+@item open(filename, flags)
+Open a file (wrapper to the libc @code{fopen()}). Throws 
+@code{std.Error} in case of I/O error.
+
+@item popen(command, flags)
+Open a process by creating a pipe (wrapper to the libc @code{popen()}). Throws 
+@code{std.Error} in case of I/O error.
+
+@item fdopen(fd, flags)
+Open a file from a file handle (wrapper to the libc
+@code{fdopen()}). Throws @code{std.Error} in case of I/O error.
+
+@item tmpfile()
+Open a temporary file. Throws @code{std.Error} in case of I/O error.
+
+@item puts(str)
+Equivalent to @code{std.out.puts(str)}.
+
+@item printf(fmt, ...args)
+Equivalent to @code{std.out.printf(fmt, ...args)}
+
+@item sprintf(fmt, ...args)
+Equivalent to the libc sprintf().
+
+@item in
+@item out
+@item err
+Wrappers to the libc file @code{stdin}, @code{stdout}, @code{stderr}.
+
+@item SEEK_SET
+@item SEEK_CUR
+@item SEEK_END
+Constants for seek().
+
+@item gc()
+Manually invoke the cycle removal algorithm. The cycle removal
+algorithm is automatically started when needed, so this function is
+useful in case of specific memory constraints or for testing.
+
+@item getenv(name)
+Return the value of the environment variable @code{name} or
+@code{undefined} if it is not defined.
+
+@item urlGet(url, options = undefined)
+
+Download @code{url} using the @file{curl} command line
+utility. @code{options} is an optional object containing the following
+optional properties:
+
+  @table @code
+  @item binary
+  Boolean (default = false). If true, the response is an ArrayBuffer
+  instead of a string. When a string is returned, the data is assumed
+  to be UTF-8 encoded.
+
+  @item full
+  Boolean (default = false). If true, return the an object contains
+  the properties @code{response} (response content),
+  @code{responseHeaders} (headers separated by CRLF), @code{status}
+  (status code). If @code{full} is false, only the response is
+  returned if the status is between 200 and 299. Otherwise an
+  @code{std.Error} exception is raised.
+
+  @end table
+
+@end table
+
+FILE prototype:
+
+@table @code
+@item close()
+Close the file.
+@item puts(str)
+Outputs the string with the UTF-8 encoding.
+@item printf(fmt, ...args)
+Formatted printf, same formats as the libc printf.
+@item flush()
+Flush the buffered file.
+@item seek(offset, whence)
+Seek to a give file position (whence is @code{std.SEEK_*}). Throws a
+@code{std.Error} in case of I/O error.
+@item tell()
+Return the current file position.
+@item eof()
+Return true if end of file.
+@item fileno()
+Return the associated OS handle.
+
+@item read(buffer, position, length)
+Read @code{length} bytes from the file to the ArrayBuffer @code{buffer} at byte
+position @code{position} (wrapper to the libc @code{fread}).
+
+@item write(buffer, position, length)
+Write @code{length} bytes to the file from the ArrayBuffer @code{buffer} at byte
+position @code{position} (wrapper to the libc @code{fread}).
+
+@item getline()
+Return the next line from the file, assuming UTF-8 encoding, excluding
+the trailing line feed.
+
+@item getByte()
+Return the next byte from the file.
+
+@item putByte(c)
+Write one byte to the file.
+@end table
+
+@subsection @code{os} module
+
+The @code{os} module provides Operating System specific functions:
+
+@itemize
+@item low level file access
+@item signals
+@item timers
+@item asynchronous I/O
+@end itemize
+
+The OS functions usually return 0 if OK or an OS specific negative
+error code.
+
+Available exports:
+
+@table @code
+@item open(filename, flags, mode = 0o666)
+Open a file. Return a handle or < 0 if error.
+
+@item O_RDONLY
+@item O_WRONLY        
+@item O_RDWR
+@item O_APPEND
+@item O_CREAT
+@item O_EXCL
+@item O_TRUNC
+POSIX open flags.
+
+@item O_TEXT
+(Windows specific). Open the file in text mode. The default is binary mode.
+
+@item close(fd)
+Close the file handle @code{fd}.
+
+@item seek(fd, offset, whence)
+Seek in the file. Use @code{std.SEEK_*} for @code{whence}.
+
+@item read(fd, buffer, offset, length)
+Read @code{length} bytes from the file handle @code{fd} to the
+ArrayBuffer @code{buffer} at byte position @code{offset}.
+Return the number of read bytes or < 0 if error.
+
+@item write(fd, buffer, offset, length)
+Write @code{length} bytes to the file handle @code{fd} from the
+ArrayBuffer @code{buffer} at byte position @code{offset}.
+Return the number of written bytes or < 0 if error.
+
+@item isatty(fd)
+Return @code{true} is @code{fd} is a TTY (terminal) handle.
+
+@item ttyGetWinSize(fd)
+Return the TTY size as @code{[width, height]} or @code{null} if not available.
+
+@item ttySetRaw(fd)
+Set the TTY in raw mode.
+
+@item remove(filename)
+Remove a file. Return 0 if OK or < 0 if error.
+
+@item rename(oldname, newname)
+Rename a file. Return 0 if OK or < 0 if error.
+
+@item realpath(path)
+Return @code{[str, err]} where @code{str} is the canonicalized absolute
+pathname of @code{path} and @code{err} the error code.
+
+@item getcwd()
+Return @code{[str, err]} where @code{str} is the current working directory
+and @code{err} the error code.
+
+@item mkdir(path, mode = 0o777)
+Create a directory at @code{path}. Return the error code.
+
+@item stat(path)
+@item lstat(path)
+
+Return @code{[obj, err]} where @code{obj} is an object containing the
+file status of @code{path}. @code{err} is the error code. The
+following fields are defined in @code{obj}: dev, ino, mode, nlink,
+uid, gid, rdev, size, blocks, atime, mtime, ctime. The times are
+specified in milliseconds since 1970. @code{lstat()} is the same as
+@code{stat()} excepts that it returns information about the link
+itself.
+
+@item S_IFMT
+@item S_IFIFO
+@item S_IFCHR
+@item S_IFDIR
+@item S_IFBLK
+@item S_IFREG
+@item S_IFSOCK
+@item S_IFLNK
+@item S_ISGID
+@item S_ISUID
+Constants to interpret the @code{mode} property returned by
+@code{stat()}. They have the same value as in the C system header
+@file{sys/stat.h}.
+
+@item utimes(path, atime, mtime)
+Change the access and modification times of the file @code{path}. The
+times are specified in milliseconds since 1970.
+
+@item symlink(target, linkpath)
+Create a link at @code{linkpath} containing the string @code{target}.
+
+@item readlink(path)
+Return @code{[str, err]} where @code{str} is the link target and @code{err}
+the error code.
+
+@item readdir(path)
+Return @code{[array, err]} where @code{array} is an array of strings
+containing the filenames of the directory @code{path}. @code{err} is
+the error code.
+
+@item setReadHandler(fd, func)
+Add a read handler to the file handle @code{fd}. @code{func} is called
+each time there is data pending for @code{fd}. A single read handler
+per file handle is supported. Use @code{func = null} to remove the
+hander.
+
+@item setWriteHandler(fd, func)
+Add a write handler to the file handle @code{fd}. @code{func} is
+called each time data can be written to @code{fd}. A single write
+handler per file handle is supported. Use @code{func = null} to remove
+the hander.
+
+@item signal(signal, func)
+Call the function @code{func} when the signal @code{signal}
+happens. Only a single handler per signal number is supported. Use
+@code{null} to set the default handler or @code{undefined} to ignore
+the signal.
+
+@item SIGINT
+@item SIGABRT
+@item SIGFPE
+@item SIGILL
+@item SIGSEGV
+@item SIGTERM
+POSIX signal numbers.
+
+@item kill(pid, sig)
+Send the signal @code{sig} to the process @code{pid}.
+
+@item exec(args[, options])
+Execute a process with the arguments @code{args}. @code{options} is an
+object containing optional parameters:
+
+  @table @code
+  @item block
+  Boolean (default = true). If true, wait until the process is
+  termined. In this case, @code{exec} return the exit code if positive
+  or the negated signal number if the process was interrupted by a
+  signal. If false, do not block and return the process id of the child.
+  
+  @item usePath
+  Boolean (default = true). If true, the file is searched in the
+  @code{PATH} environment variable.
+
+  @item file
+  String (default = @code{args[0]}). Set the file to be executed.
+
+  @item cwd
+  String. If present, set the working directory of the new process.
+
+  @item stdin
+  @item stdout
+  @item stderr
+  If present, set the handle in the child for stdin, stdout or stderr.
+ 
+  @end table
+
+@item waitpid(pid, options)
+@code{waitpid} Unix system call. Return the array @code{[ret, status]}.
+
+@item WNOHANG
+Constant for the @code{options} argument of @code{waitpid}.
+
+@item dup(fd)
+@code{dup} Unix system call.
+
+@item dup2(oldfd, newfd)
+@code{dup2} Unix system call.
+
+@item pipe()
+@code{pipe} Unix system call. Return two handles as @code{[read_fd,
+write_fd]} or null in case of error.
+
+@item sleep(delay_ms)
+Sleep during @code{delay_ms} milliseconds.
+
+@item setTimeout(func, delay)
+Call the function @code{func} after @code{delay} ms. Return a handle
+to the timer.
+
+@item clearTimeout(handle)
+Cancel a timer.
+
+@item platform
+Return a string representing the platform: @code{"linux"}, @code{"darwin"},
+@code{"win32"} or @code{"js"}.
+
+@end table
+
+@section QuickJS C API
+
+The C API was designed to be simple and efficient. The C API is
+defined in the header @code{quickjs.h}.
+
+@subsection Runtime and contexts
+
+@code{JSRuntime} represents a Javascript runtime corresponding to an
+object heap. Several runtimes can exist at the same time but they
+cannot exchange objects. Inside a given runtime, no multi-threading is
+supported.
+
+@code{JSContext} represents a Javascript context (or Realm). Each
+JSContext has its own global objects and system objects. There can be
+several JSContexts per JSRuntime and they can share objects, similary
+to frames of the same origin sharing Javascript objects in a
+web browser.
+
+@subsection JSValue
+
+@code{JSValue} represents a Javascript value which can be a primitive
+type or an object. Reference counting is used, so it is important to
+explicitely duplicate (@code{JS_DupValue()}, increment the reference
+count) or free (@code{JS_FreeValue()}, decrement the reference count)
+JSValues.
+
+@subsection C functions
+
+C functions can be created with
+@code{JS_NewCFunction()}. @code{JS_SetPropertyFunctionList()} is a
+shortcut to easily add functions, setters and getters properties to a
+given object.
+
+Unlike other embedded Javascript engines, there is no implicit stack,
+so C functions get their parameters as normal C parameters. As a
+general rule, C functions take constant @code{JSValue}s as parameters
+(so they don't need to free them) and return a newly allocated (=live)
+@code{JSValue}.
+
+@subsection Exceptions
+
+Exceptions: most C functions can return a Javascript exception. It
+must be explicitely tested and handled by the C code. The specific
+@code{JSValue} @code{JS_EXCEPTION} indicates that an exception
+occured. The actual exception object is stored in the
+@code{JSContext} and can be retrieved with @code{JS_GetException()}.
+
+@subsection Script evaluation
+
+Use @code{JS_Eval()} to evaluate a script or module source.
+
+If the script or module was compiled to bytecode with @code{qjsc}, it
+can be evaluated by calling @code{js_std_eval_binary()}. The advantage
+is that no compilation is needed so it is faster and smaller because
+the compiler can be removed from the executable if no @code{eval} is
+required.
+
+Note: the bytecode format is linked to a given QuickJS
+version. Moreover, no security check is done before its
+execution. Hence the bytecode should not be loaded from untrusted
+sources. That's why there is no option to output the bytecode to a
+binary file in @code{qjsc}.
+
+@subsection JS Classes
+
+C opaque data can be attached to a Javascript object. The type of the
+C opaque data is determined with the class ID (@code{JSClassID}) of
+the object. Hence the first step is to register a new class ID and JS
+class (@code{JS_NewClassID()}, @code{JS_NewClass()}). Then you can
+create objects of this class with @code{JS_NewObjectClass()} and get or
+set the C opaque point with
+@code{JS_GetOpaque()}/@code{JS_SetOpaque()}.
+
+When defining a new JS class, it is possible to declare a finalizer
+which is called when the object is destroyed. A @code{gc_mark} method
+can be provided so that the cycle removal algorithm can find the other
+objects referenced by this object. Other methods are available to
+define exotic object behaviors.
+
+The Class ID are globally allocated (i.e. for all runtimes). The
+JSClass are allocated per @code{JSRuntime}. @code{JS_SetClassProto()}
+is used to define a prototype for a given class in a given
+JSContext. @code{JS_NewObjectClass()} sets this prototype in the
+created object.
+
+Examples are available in @file{quickjs-libc.c}.
+
+@subsection C Modules
+
+Native ES6 modules are supported and can be dynamically or statically
+linked. Look at the @file{test_bjson} and @file{bjson.so}
+examples. The standard library @file{quickjs-libc.c} is also a good example
+of a native module.
+
+@subsection Memory handling
+
+Use @code{JS_SetMemoryLimit()} to set a global memory allocation limit
+to a given JSRuntime.
+
+Custom memory allocation functions can be provided with
+@code{JS_NewRuntime2()}.
+
+The maximum system stack size can be set with @code{JS_SetMaxStackSize()}.
+
+@subsection Execution timeout and interrupts
+
+Use @code{JS_SetInterruptHandler()} to set a callback which is
+regularly called by the engine when it is executing code. This
+callback can be used to implement an execution timeout.
+
+It is used by the command line interpreter to implement a
+@code{Ctrl-C} handler.
+
+@chapter Internals
+
+@section Bytecode
+
+The compiler generates bytecode directly with no intermediate
+representation such as a parse tree, hence it is very fast. Several
+optimizations passes are done over the generated bytecode.
+
+A stack-based bytecode was chosen because it is simple and generates 
+compact code.
+
+For each function, the maximum stack size is computed at compile time so that
+no runtime stack overflow tests are needed.
+
+A separate compressed line number table is maintained for the debug
+information.
+
+Access to closure variables is optimized and is almost as fast as local
+variables.
+
+Direct @code{eval} in strict mode is optimized.
+
+@section Executable generation
+
+@subsection @code{qjsc} compiler
+
+The @code{qjsc} compiler generates C sources from Javascript files. By
+default the C sources are compiled with the system compiler
+(@code{gcc} or @code{clang}).
+
+The generated C source contains the bytecode of the compiled functions
+or modules. If a full complete executable is needed, it also
+contains a @code{main()} function with the necessary C code to initialize the
+Javascript engine and to load and execute the compiled functions and
+modules.
+
+Javascript code can be mixed with C modules.
+
+In order to have smaller executables, specific Javascript features can
+be disabled, in particular @code{eval} or the regular expressions. The
+code removal relies on the Link Time Optimization of the system
+compiler.
+
+@subsection Binary JSON
+
+@code{qjsc} works by compiling scripts or modules and then serializing
+them to a binary format. A subset of this format (without functions or
+modules) can be used as binary JSON. The example @file{test_bjson.js}
+shows how to use it.
+
+Warning: the binary JSON format may change without notice, so it
+should not be used to store persistent data. The @file{test_bjson.js}
+example is only used to test the binary object format functions.
+
+@section Runtime
+
+@subsection Strings
+
+Strings are stored either as an 8 bit or a 16 bit array of
+characters. Hence random access to characters is always fast.
+
+The C API provides functions to convert Javascript Strings to C UTF-8 encoded
+strings. The most common case where the Javascript string contains
+only ASCII characters involves no copying.
+
+@subsection Objects
+
+The object shapes (object prototype, property names and flags) are shared
+between objects to save memory.
+
+Arrays with no holes (except at the end of the array) are optimized.
+
+TypedArray accesses are optimized.
+
+@subsection Atoms
+
+Object property names and some strings are stored as Atoms (unique
+strings) to save memory and allow fast comparison. Atoms are
+represented as a 32 bit integer. Half of the atom range is reserved for
+immediate integer literals from @math{0} to @math{2^{31}-1}.
+
+@subsection Numbers
+
+Numbers are represented either as 32-bit signed integers or 64-bit IEEE-754
+floating point values. Most operations have fast paths for the 32-bit
+integer case.
+
+@subsection Garbage collection
+
+Reference counting is used to free objects automatically and
+deterministically. A separate cycle removal pass is done when the allocated
+memory becomes too large. The cycle removal algorithm only uses the
+reference counts and the object content, so no explicit garbage
+collection roots need to be manipulated in the C code.
+
+@subsection JSValue
+
+It is a Javascript value which can be a primitive type (such as
+Number, String, ...) or an Object. NaN boxing is used in the 32-bit version
+to store 64-bit floating point numbers. The representation is
+optimized so that 32-bit integers and reference counted values can be
+efficiently tested.
+
+In 64-bit code, JSValue are 128-bit large and no NaN boxing is used. The
+rationale is that in 64-bit code memory usage is less critical.
+
+In both cases (32 or 64 bits), JSValue exactly fits two CPU registers,
+so it can be efficiently returned by C functions.
+
+@subsection Function call
+
+The engine is optimized so that function calls are fast. The system
+stack holds the Javascript parameters and local variables.
+
+@section RegExp
+
+A specific regular expression engine was developped. It is both small
+and efficient and supports all the ES2020 features including the
+Unicode properties. As the Javascript compiler, it directly generates
+bytecode without a parse tree.
+
+Backtracking with an explicit stack is used so that there is no
+recursion on the system stack. Simple quantizers are specifically
+optimized to avoid recursions.
+
+Infinite recursions coming from quantizers with empty terms are
+avoided.
+
+The full regexp library weights about 15 KiB (x86 code), excluding the
+Unicode library.
+
+@section Unicode
+
+A specific Unicode library was developped so that there is no
+dependency on an external large Unicode library such as ICU. All the
+Unicode tables are compressed while keeping a reasonnable access
+speed.
+
+The library supports case conversion, Unicode normalization, Unicode
+script queries, Unicode general category queries and all Unicode
+binary properties.
+
+The full Unicode library weights about 45 KiB (x86 code).
+
+@section BigInt and BigFloat
+
+BigInt and BigFloat are implemented with the @code{libbf}
+library@footnote{@url{https://bellard.org/libbf}}. It weights about 60
+KiB (x86 code) and provides arbitrary precision IEEE 754 floating
+point operations and transcendental functions with exact rounding.
+
+@chapter License
+
+QuickJS is released under the MIT license.
+
+Unless otherwise specified, the QuickJS sources are copyright Fabrice
+Bellard and Charlie Gordon.
+
+@bye
diff --git a/examples/c_module.js b/examples/c_module.js
new file mode 100644
index 0000000..70d26bd
--- /dev/null
+++ b/examples/c_module.js
@@ -0,0 +1,6 @@
+/* example of JS module importing a C module */
+
+import { fib } from "./fib.so";
+
+console.log("Hello World");
+console.log("fib(10)=", fib(10));
diff --git a/examples/fib.c b/examples/fib.c
new file mode 100644
index 0000000..c77b705
--- /dev/null
+++ b/examples/fib.c
@@ -0,0 +1,72 @@
+/*
+ * QuickJS: Example of C module
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "../quickjs.h"
+
+#define countof(x) (sizeof(x) / sizeof((x)[0]))
+
+static int fib(int n)
+{
+    if (n <= 0)
+        return 0;
+    else if (n == 1)
+        return 1;
+    else
+        return fib(n - 1) + fib(n - 2);
+}
+
+static JSValue js_fib(JSContext *ctx, JSValueConst this_val,
+                      int argc, JSValueConst *argv)
+{
+    int n, res;
+    if (JS_ToInt32(ctx, &n, argv[0]))
+        return JS_EXCEPTION;
+    res = fib(n);
+    return JS_NewInt32(ctx, res);
+}
+
+static const JSCFunctionListEntry js_fib_funcs[] = {
+    JS_CFUNC_DEF("fib", 1, js_fib ),
+};
+
+static int js_fib_init(JSContext *ctx, JSModuleDef *m)
+{
+    return JS_SetModuleExportList(ctx, m, js_fib_funcs,
+                                  countof(js_fib_funcs));
+}
+
+#ifdef JS_SHARED_LIBRARY
+#define JS_INIT_MODULE js_init_module
+#else
+#define JS_INIT_MODULE js_init_module_fib
+#endif
+
+JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
+{
+    JSModuleDef *m;
+    m = JS_NewCModule(ctx, module_name, js_fib_init);
+    if (!m)
+        return NULL;
+    JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs));
+    return m;
+}
diff --git a/examples/fib_module.js b/examples/fib_module.js
new file mode 100644
index 0000000..6a81071
--- /dev/null
+++ b/examples/fib_module.js
@@ -0,0 +1,10 @@
+/* fib module */
+export function fib(n)
+{
+    if (n <= 0)
+        return 0;
+    else if (n == 1)
+        return 1;
+    else
+        return fib(n - 1) + fib(n - 2);
+}
diff --git a/examples/hello.js b/examples/hello.js
new file mode 100644
index 0000000..accefce
--- /dev/null
+++ b/examples/hello.js
@@ -0,0 +1 @@
+console.log("Hello World");
diff --git a/examples/hello_module.js b/examples/hello_module.js
new file mode 100644
index 0000000..463660f
--- /dev/null
+++ b/examples/hello_module.js
@@ -0,0 +1,6 @@
+/* example of JS module */
+
+import { fib } from "./fib_module.js";
+
+console.log("Hello World");
+console.log("fib(10)=", fib(10));
diff --git a/examples/pi.js b/examples/pi.js
new file mode 100644
index 0000000..f2ba4cd
--- /dev/null
+++ b/examples/pi.js
@@ -0,0 +1,66 @@
+/*
+ * PI computation in Javascript using the QuickJS bignum extensions
+ */
+"use strict";
+"use bigint";
+
+/* compute PI with a precision of 'prec' bits */
+function calc_pi(prec) {
+    const CHUD_A = 13591409;
+    const CHUD_B = 545140134;
+    const CHUD_C = 640320;
+    const CHUD_C3 = 10939058860032000; /* C^3/24 */
+    const CHUD_BITS_PER_TERM = 47.11041313821584202247; /* log2(C/12)*3 */
+    
+    /* return [P, Q, G] */
+    function chud_bs(a, b, need_G) {
+        var c, P, Q, G, P1, Q1, G1, P2, Q2, G2;
+        if (a == (b - 1)) {
+            G = (2 * b - 1) * (6 * b - 1) * (6 * b - 5);
+            P = BigFloat(G * (CHUD_B * b + CHUD_A));
+            if (b & 1)
+                P = -P;
+            G = BigFloat(G);
+            Q = BigFloat(b * b * b * CHUD_C3);
+        } else {
+            c = (a + b) >> 1;
+            [P1, Q1, G1] = chud_bs(a, c, true);
+            [P2, Q2, G2] = chud_bs(c, b, need_G);
+            P = P1 * Q2 + P2 * G1;
+            Q = Q1 * Q2;
+            if (need_G)
+                G = G1 * G2;
+            else
+                G = 0;
+        }
+        return [P, Q, G];
+    }
+
+    var n, P, Q, G;
+    /* number of serie terms */
+    n = Math.ceil(BigFloatEnv.prec / CHUD_BITS_PER_TERM) + 10;
+    [P, Q, G] = chud_bs(0, n, false);
+    Q = Q / (P + Q * CHUD_A);
+    G = (CHUD_C / 12) * BigFloat.sqrt(CHUD_C);
+    return Q * G;
+}
+
+(function() {
+    var r, n_digits, n_bits;
+    if (typeof scriptArgs != "undefined") {
+        if (scriptArgs.length < 2) {
+            print("usage: pi n_digits");
+            return;
+        }
+        n_digits = scriptArgs[1];
+    } else {
+        n_digits = 1000;
+    }
+    n_bits = Math.ceil(n_digits * Math.log2(10));
+    /* we add more bits to reduce the probability of bad rounding for
+       the last digits */
+    BigFloatEnv.setPrec( () => {
+        r = calc_pi();
+        print(r.toFixed(n_digits, BigFloatEnv.RNDZ));
+    }, n_bits + 32);
+})();
diff --git a/jscompress.c b/jscompress.c
new file mode 100644
index 0000000..a68c0e8
--- /dev/null
+++ b/jscompress.c
@@ -0,0 +1,918 @@
+/*
+ * Javascript Compressor
+ * 
+ * Copyright (c) 2008-2018 Fabrice Bellard
+ * Copyright (c) 2017-2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include "cutils.h"
+
+typedef struct JSToken {
+    int tok;
+    char buf[20];
+    char *str;
+    int len;
+    int size;
+    int line_num;  /* line number for start of token */
+    int lines;     /* number of embedded linefeeds in token */
+} JSToken;
+
+enum {
+    TOK_EOF = 256,
+    TOK_IDENT,
+    TOK_STR1,
+    TOK_STR2,
+    TOK_STR3,
+    TOK_NUM,
+    TOK_COM,
+    TOK_LCOM,
+};
+
+void tok_reset(JSToken *tt)
+{
+    if (tt->str != tt->buf) {
+        free(tt->str);
+        tt->str = tt->buf;
+        tt->size = sizeof(tt->buf);
+    }
+    tt->len = 0;
+}
+
+void tok_add_ch(JSToken *tt, int c)
+{
+    if (tt->len + 1 > tt->size) {
+        tt->size *= 2;
+        if (tt->str == tt->buf) {
+            tt->str = malloc(tt->size);
+            memcpy(tt->str, tt->buf, tt->len);
+        } else {
+            tt->str = realloc(tt->str, tt->size);
+        }
+    }
+    tt->str[tt->len++] = c;
+}
+
+FILE *infile;
+const char *filename;
+int output_line_num;
+int line_num;
+int ch;
+JSToken tokc;
+
+int skip_mask;
+#define DEFINE_MAX 20
+char *define_tab[DEFINE_MAX];
+int define_len;
+
+void error(const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    if (filename) {
+        fprintf(stderr, "%s:%d: ", filename, line_num);
+    } else {
+        fprintf(stderr, "jscompress: ");
+    }
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+    va_end(ap);
+    exit(1);
+}
+
+void define_symbol(const char *def)
+{
+    int i;
+    for (i = 0; i < define_len; i++) {
+        if (!strcmp(tokc.str, define_tab[i]))
+            return;
+    }
+    if (define_len >= DEFINE_MAX)
+        error("too many defines");
+    define_tab[define_len++] = strdup(def);
+}
+
+void undefine_symbol(const char *def)
+{
+    int i, j;
+    for (i = j = 0; i < define_len; i++) {
+        if (!strcmp(tokc.str, define_tab[i])) {
+            free(define_tab[i]);
+        } else {
+            define_tab[j++] = define_tab[i];
+        }
+    }
+    define_len = j;
+}
+
+const char *find_symbol(const char *def)
+{
+    int i;
+    for (i = 0; i < define_len; i++) {
+        if (!strcmp(tokc.str, define_tab[i]))
+            return "1";
+    }
+    return NULL;
+}
+
+void next(void);
+
+void nextch(void)
+{
+    ch = fgetc(infile);
+    if (ch == '\n')
+        line_num++;
+}
+
+int skip_blanks(void)
+{
+    for (;;) {
+        next();
+        if (tokc.tok != ' ' && tokc.tok != '\t' &&
+            tokc.tok != TOK_COM && tokc.tok != TOK_LCOM)
+            return tokc.tok;
+    }
+}
+
+void parse_directive(void)
+{
+    int ifdef, mask = skip_mask;
+    /* simplistic preprocessor:
+       #define / #undef / #ifdef / #ifndef / #else / #endif
+       no symbol substitution.
+     */
+    skip_mask = 0;  /* disable skipping to parse preprocessor line */
+    nextch();
+    if (skip_blanks() != TOK_IDENT)
+        error("expected preprocessing directive after #");
+
+    if (!strcmp(tokc.str, "define")) {
+        if (skip_blanks() != TOK_IDENT)
+            error("expected identifier after #define");
+        define_symbol(tokc.str);
+    } else if (!strcmp(tokc.str, "undef")) {
+        if (skip_blanks() != TOK_IDENT)
+            error("expected identifier after #undef");
+        undefine_symbol(tokc.str);
+    } else if ((ifdef = 1, !strcmp(tokc.str, "ifdef")) ||
+               (ifdef = 0, !strcmp(tokc.str, "ifndef"))) {
+        if (skip_blanks() != TOK_IDENT)
+            error("expected identifier after #ifdef/#ifndef");
+        mask = (mask << 2) | 2 | ifdef;
+        if (find_symbol(tokc.str))
+            mask ^= 1;
+    } else if (!strcmp(tokc.str, "else")) {
+        if (!(mask & 2))
+            error("#else without a #if");
+        mask ^= 1;
+    } else if (!strcmp(tokc.str, "endif")) {
+        if (!(mask & 2))
+            error("#endif without a #if");
+        mask >>= 2;
+    } else {
+        error("unsupported preprocessing directive");
+    }
+    if (skip_blanks() != '\n')
+        error("extra characters on preprocessing line");
+    skip_mask = mask;
+}
+
+/* return -1 if invalid char */
+static int hex_to_num(int ch)
+{
+    if (ch >= 'a' && ch <= 'f')
+        return ch - 'a' + 10;
+    else if (ch >= 'A' && ch <= 'F')
+        return ch - 'A' + 10;
+    else if (ch >= '0' && ch <= '9')
+        return ch - '0';
+    else
+        return -1;
+}
+
+void next(void)
+{
+again:    
+    tok_reset(&tokc);
+    tokc.line_num = line_num;
+    tokc.lines = 0;
+    switch(ch) {
+    case EOF:
+        tokc.tok = TOK_EOF;
+        if (skip_mask)
+            error("missing #endif");
+        break;
+    case 'a' ... 'z':
+    case 'A' ... 'Z':
+    case '_':
+    case '$':
+        tok_add_ch(&tokc, ch);
+        nextch();
+        while ((ch >= 'a' && ch <= 'z') ||
+               (ch >= 'A' && ch <= 'Z') ||
+               (ch >= '0' && ch <= '9') ||
+               (ch == '_' || ch == '$')) {
+            tok_add_ch(&tokc, ch);
+            nextch();
+        }
+        tok_add_ch(&tokc, '\0');
+        tokc.tok = TOK_IDENT;
+        break;
+    case '.':
+        nextch();
+        if (ch >= '0' && ch <= '9') {
+            tok_add_ch(&tokc, '.');
+            goto has_dot;
+        }
+        tokc.tok = '.';
+        break;
+    case '0':
+        tok_add_ch(&tokc, ch);
+        nextch();
+        if (ch == 'x' || ch == 'X') {
+            /* hexa */
+            tok_add_ch(&tokc, ch);
+            nextch();
+            while ((ch >= 'a' && ch <= 'f') ||
+                   (ch >= 'A' && ch <= 'F') ||
+                   (ch >= '0' && ch <= '9')) {
+                tok_add_ch(&tokc, ch);
+                nextch();
+            }
+            tok_add_ch(&tokc, '\0');
+            tokc.tok = TOK_NUM;
+            break;
+        }
+        goto has_digit;
+
+    case '1' ... '9':
+        tok_add_ch(&tokc, ch);
+        nextch();
+    has_digit:
+        /* decimal */
+        while (ch >= '0' && ch <= '9') {
+            tok_add_ch(&tokc, ch);
+            nextch();
+        }
+        if (ch == '.') {
+            tok_add_ch(&tokc, ch);
+            nextch();
+        has_dot:
+            while (ch >= '0' && ch <= '9') {
+                tok_add_ch(&tokc, ch);
+                nextch();
+            }
+        }
+        if (ch == 'e' || ch == 'E') {
+            tok_add_ch(&tokc, ch);
+            nextch();
+            if (ch == '+' || ch == '-') {
+                tok_add_ch(&tokc, ch);
+                nextch();
+            }
+            while (ch >= '0' && ch <= '9') {
+                tok_add_ch(&tokc, ch);
+                nextch();
+            }
+        }
+        tok_add_ch(&tokc, '\0');
+        tokc.tok = TOK_NUM;
+        break;
+    case '`':
+        {
+            nextch();
+            while (ch != '`' && ch != EOF) {
+                if (ch == '\\') {
+                    tok_add_ch(&tokc, ch);
+                    nextch();
+                    if (ch == EOF) {
+                        error("unexpected char after '\\'");
+                    }
+                    tok_add_ch(&tokc, ch);
+                } else {
+                    tok_add_ch(&tokc, ch);
+                    nextch();
+                }
+            }
+            nextch();
+            tok_add_ch(&tokc, 0);
+            tokc.tok = TOK_STR3;
+        }
+        break;
+    case '\"':
+    case '\'':
+        {
+            int n, i, c, hex_digit_count;
+            int quote_ch;
+            quote_ch = ch;
+            nextch();
+            while (ch != quote_ch && ch != EOF) {
+                if (ch == '\\') {
+                    nextch();
+                    switch(ch) {
+                    case 'n':
+                        tok_add_ch(&tokc, '\n');
+                        nextch();
+                        break;
+                    case 'r':
+                        tok_add_ch(&tokc, '\r');
+                        nextch();
+                        break;
+                    case 't':
+                        tok_add_ch(&tokc, '\t');
+                        nextch();
+                        break;
+                    case 'v':
+                        tok_add_ch(&tokc, '\v');
+                        nextch();
+                        break;
+                    case '\"':
+                    case '\'':
+                    case '\\':
+                        tok_add_ch(&tokc, ch);
+                        nextch();
+                        break;
+                    case '0' ... '7':
+                        n = 0;
+                        while (ch >= '0' && ch <= '7') {
+                            n = n * 8 + (ch - '0');
+                            nextch();
+                        }
+                        tok_add_ch(&tokc, n);
+                        break;
+                    case 'x':
+                    case 'u':
+                        if (ch == 'x')
+                            hex_digit_count = 2;
+                        else
+                            hex_digit_count = 4;
+                        nextch();
+                        n = 0;
+                        for(i = 0; i < hex_digit_count; i++) {
+                            c = hex_to_num(ch);
+                            if (c < 0) 
+                                error("unexpected char after '\\x'");
+                            n = n * 16 + c;
+                            nextch();
+                        }
+                        if (n >= 256)
+                            error("unicode is currently unsupported");
+                        tok_add_ch(&tokc, n);
+                        break;
+
+                    default:
+                        error("unexpected char after '\\'");
+                    }
+                } else {
+                    /* XXX: should refuse embedded newlines */
+                    tok_add_ch(&tokc, ch);
+                    nextch();
+                }
+            }
+            nextch();
+            tok_add_ch(&tokc, 0);
+            if (quote_ch == '\'')
+                tokc.tok = TOK_STR1;
+            else
+                tokc.tok = TOK_STR2;
+        }
+        break;
+    case '/':
+        nextch();
+        if (ch == '/') {
+            tok_add_ch(&tokc, '/');
+            tok_add_ch(&tokc, ch);
+            nextch();
+            while (ch != '\n' && ch != EOF) {
+                tok_add_ch(&tokc, ch);
+                nextch();
+            }
+            tok_add_ch(&tokc, '\0');
+            tokc.tok = TOK_LCOM;
+        } else if (ch == '*') {
+            int last;
+            tok_add_ch(&tokc, '/');
+            tok_add_ch(&tokc, ch);
+            last = 0;
+            for(;;) {
+                nextch();
+                if (ch == EOF)
+                    error("unterminated comment");
+                if (ch == '\n')
+                    tokc.lines++;
+                tok_add_ch(&tokc, ch);
+                if (last == '*' && ch == '/')
+                    break;
+                last = ch;
+            }
+            nextch();
+            tok_add_ch(&tokc, '\0');
+            tokc.tok = TOK_COM;
+        } else {
+            tokc.tok = '/';
+        }
+        break;
+    case '#':
+        parse_directive();
+        goto again;
+    case '\n':
+        /* adjust line number */
+        tokc.line_num--;
+        tokc.lines++;
+        /* fall thru */
+    default:
+        tokc.tok = ch;
+        nextch();
+        break;
+    }
+    if (skip_mask & 1)
+        goto again;
+}
+
+void print_tok(FILE *f, JSToken *tt)
+{
+    /* keep output lines in sync with input lines */
+    while (output_line_num < tt->line_num) {
+        putc('\n', f);
+        output_line_num++;
+    }
+
+    switch(tt->tok) {
+    case TOK_IDENT:
+    case TOK_COM:
+    case TOK_LCOM:
+        fprintf(f, "%s", tt->str);
+        break;
+    case TOK_NUM:
+        {
+            unsigned long a;
+            char *p;
+            a = strtoul(tt->str, &p, 0);
+            if (*p == '\0' && a <= 0x7fffffff) {
+                /* must be an integer */
+                fprintf(f, "%d", (int)a);
+            } else {
+                fprintf(f, "%s", tt->str);
+            }
+        }
+        break;
+    case TOK_STR3:
+        fprintf(f, "`%s`", tt->str);
+        break;
+    case TOK_STR1:
+    case TOK_STR2:
+        {
+            int i, c, quote_ch;
+            if (tt->tok == TOK_STR1)
+                quote_ch = '\'';
+            else
+                quote_ch = '\"';
+            fprintf(f, "%c", quote_ch);
+            for(i = 0; i < tt->len - 1; i++) {
+                c = (uint8_t)tt->str[i];
+                switch(c) {
+                case '\r':
+                    fprintf(f, "\\r");
+                    break;
+                case '\n':
+                    fprintf(f, "\\n");
+                    break;
+                case '\t':
+                    fprintf(f, "\\t");
+                    break;
+                case '\v':
+                    fprintf(f, "\\v");
+                    break;
+                case '\"':
+                case '\'':
+                    if (c == quote_ch)
+                        fprintf(f, "\\%c", c);
+                    else
+                        fprintf(f, "%c", c);
+                    break;
+                case '\\':
+                    fprintf(f, "\\\\");
+                    break;
+                default:
+                    /* XXX: no utf-8 support! */
+                    if (c >= 32 && c <= 255) {
+                        fprintf(f, "%c", c);
+                    } else if (c <= 255) 
+                        fprintf(f, "\\x%02x", c);
+                    else
+                        fprintf(f, "\\u%04x", c);
+                    break;
+                }
+            }
+            fprintf(f, "%c", quote_ch);
+        }
+        break;
+    default:
+        if (tokc.tok >= 256)
+            error("unsupported token in print_tok: %d", tt->tok);
+        fprintf(f, "%c", tt->tok);
+        break;
+    }
+    output_line_num += tt->lines;
+}
+
+/* check if token pasting could occur */                    
+static BOOL compat_token(int c1, int c2)
+{
+    if ((c1 == TOK_IDENT || c1 == TOK_NUM) &&
+        (c2 == TOK_IDENT || c2 == TOK_NUM))
+        return FALSE;
+
+    if ((c1 == c2 && strchr("+-<>&|=*/.", c1))
+    ||  (c2 == '=' && strchr("+-<>&|!*/^%", c1))
+    ||  (c1 == '=' && c2 == '>')
+    ||  (c1 == '/' && c2 == '*')
+    ||  (c1 == '.' && c2 == TOK_NUM)
+    ||  (c1 == TOK_NUM && c2 == '.'))
+        return FALSE;
+
+    return TRUE;
+}
+
+void js_compress(const char *filename, const char *outfilename,
+                 BOOL do_strip, BOOL keep_header)
+{
+    FILE *outfile;
+    int ltok, seen_space;
+    
+    line_num = 1;
+    infile = fopen(filename, "rb");
+    if (!infile) {
+        perror(filename);
+        exit(1);
+    }
+    
+    output_line_num = 1;
+    outfile = fopen(outfilename, "wb");
+    if (!outfile) {
+        perror(outfilename);
+        exit(1);
+    }
+        
+    nextch();
+    next();
+    ltok = 0;
+    seen_space = 0;
+    if (do_strip) {
+        if (keep_header) {
+            while (tokc.tok == ' ' ||
+                   tokc.tok == '\n' ||
+                   tokc.tok == '\t' ||
+                   tokc.tok == '\v' ||
+                   tokc.tok == '\b' ||
+                   tokc.tok == '\f') {
+                seen_space = 1;
+                next();
+            }
+            if (tokc.tok == TOK_COM) {
+                print_tok(outfile, &tokc);
+                //fprintf(outfile, "\n");
+                ltok = tokc.tok;
+                seen_space = 0;
+                next();
+            }
+        }
+
+        for(;;) {
+            if (tokc.tok == TOK_EOF)
+                break;
+            if (tokc.tok == ' ' ||
+                tokc.tok == '\r' ||
+                tokc.tok == '\t' ||
+                tokc.tok == '\v' ||
+                tokc.tok == '\b' ||
+                tokc.tok == '\f' ||
+                tokc.tok == TOK_LCOM ||
+                tokc.tok == TOK_COM) {
+                /* don't print spaces or comments */
+                seen_space = 1;
+            } else if (tokc.tok == TOK_STR3) {
+                print_tok(outfile, &tokc);
+                ltok = tokc.tok;
+                seen_space = 0;
+            } else if (tokc.tok == TOK_STR1 || tokc.tok == TOK_STR2) {
+                int count, i;
+                /* find the optimal quote char */
+                count = 0;
+                for(i = 0; i < tokc.len; i++) {
+                    if (tokc.str[i] == '\'')
+                        count++;
+                    else if (tokc.str[i] == '\"')
+                        count--;
+                }
+                if (count > 0)
+                    tokc.tok = TOK_STR2;
+                else if (count < 0)
+                    tokc.tok = TOK_STR1;
+                print_tok(outfile, &tokc);
+                ltok = tokc.tok;
+                seen_space = 0;
+            } else {
+                if (seen_space && !compat_token(ltok, tokc.tok)) {
+                    fprintf(outfile, " ");
+                }
+                print_tok(outfile, &tokc);
+                ltok = tokc.tok;
+                seen_space = 0;
+            }
+            next();
+        }
+    } else {
+        /* just handle preprocessing */
+        while (tokc.tok != TOK_EOF) {
+            print_tok(outfile, &tokc);
+            next();
+        }
+    }
+
+    fclose(outfile);
+    fclose(infile);
+}
+
+#define HASH_SIZE 30011
+#define MATCH_LEN_MIN 3
+#define MATCH_LEN_MAX (4 + 63)
+#define DIST_MAX 65535
+
+static int find_longest_match(int *pdist, const uint8_t *src, int src_len,
+                              const int *hash_next, int cur_pos)
+{
+    int pos, i, match_len, match_pos, pos_min, len_max;
+
+    len_max = min_int(src_len - cur_pos, MATCH_LEN_MAX);
+    match_len = 0;
+    match_pos = 0;
+    pos_min = max_int(cur_pos - DIST_MAX - 1, 0);
+    pos = hash_next[cur_pos];
+    while (pos >= pos_min) {
+        for(i = 0; i < len_max; i++) {
+            if (src[cur_pos + i] != src[pos + i])
+                break;
+        }
+        if (i > match_len) {
+            match_len = i;
+            match_pos = pos;
+        }
+        pos = hash_next[pos];
+    }
+    *pdist = cur_pos - match_pos - 1;
+    return match_len;
+}
+
+int lz_compress(uint8_t **pdst, const uint8_t *src, int src_len)
+{
+    int *hash_table, *hash_next;
+    uint32_t h, v;
+    int i, dist, len, len1, dist1;
+    uint8_t *dst, *q;
+    
+    /* build the hash table */
+    
+    hash_table = malloc(sizeof(hash_table[0]) * HASH_SIZE);
+    for(i = 0; i < HASH_SIZE; i++)
+        hash_table[i] = -1;
+    hash_next = malloc(sizeof(hash_next[0]) * src_len);
+    for(i = 0; i < src_len; i++)
+        hash_next[i] = -1;
+
+    for(i = 0; i < src_len - MATCH_LEN_MIN + 1; i++) {
+        h = ((src[i] << 16) | (src[i + 1] << 8) | src[i + 2]) % HASH_SIZE;
+        hash_next[i] = hash_table[h];
+        hash_table[h] = i;
+    }
+    for(;i < src_len; i++) {
+        hash_next[i] = -1;
+    }
+    free(hash_table);
+
+    dst = malloc(src_len + 4); /* never larger than the source */
+    q = dst;
+    *q++ = src_len >> 24;
+    *q++ = src_len >> 16;
+    *q++ = src_len >> 8;
+    *q++ = src_len >> 0;
+    /* compress */
+    i = 0;
+    while (i < src_len) {
+        if (src[i] >= 128)
+            return -1;
+        len = find_longest_match(&dist, src, src_len, hash_next, i);
+        if (len >= MATCH_LEN_MIN) {
+            /* heuristic: see if better length just after */
+            len1 = find_longest_match(&dist1, src, src_len, hash_next, i + 1);
+            if (len1 > len)
+                goto no_match;
+        }
+        if (len < MATCH_LEN_MIN) {
+        no_match:
+            *q++ = src[i];
+            i++;
+        } else if (len <= (3 + 15) && dist < (1 << 10)) {
+            v = 0x8000 | ((len - 3) << 10) | dist;
+            *q++ = v >> 8;
+            *q++ = v;
+            i += len;
+        } else if (len >= 4 && len <= (4 + 63) && dist < (1 << 16)) {
+            v = 0xc00000 | ((len - 4) << 16) | dist;
+            *q++ = v >> 16;
+            *q++ = v >> 8;
+            *q++ = v;
+            i += len;
+        } else {
+            goto no_match;
+        }
+    }
+    free(hash_next);
+    *pdst = dst;
+    return q - dst;
+}
+
+static int load_file(uint8_t **pbuf, const char *filename)
+{
+    FILE *f;
+    uint8_t *buf;
+    int buf_len;
+
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+    fseek(f, 0, SEEK_END);
+    buf_len = ftell(f);
+    fseek(f, 0, SEEK_SET);
+    buf = malloc(buf_len + 1);
+    fread(buf, 1, buf_len, f);
+    buf[buf_len] = '\0';
+    fclose(f);
+    *pbuf = buf;
+    return buf_len;
+}
+
+static void save_file(const char *filename, const uint8_t *buf, int buf_len)
+{
+    FILE *f;
+    
+    f = fopen(filename, "wb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+    fwrite(buf, 1, buf_len, f);
+    fclose(f);
+}
+
+static void save_c_source(const char *filename, const uint8_t *buf, int buf_len,
+                          const char *var_name)
+{
+    FILE *f;
+    int i;
+    
+    f = fopen(filename, "wb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+    fprintf(f, "/* This file is automatically generated - do not edit */\n\n");
+    fprintf(f, "const uint8_t %s[] = {\n", var_name);
+    for(i = 0; i < buf_len; i++) {
+        fprintf(f, " 0x%02x,", buf[i]);
+        if ((i % 8) == 7 || (i == buf_len - 1))
+            fprintf(f, "\n");
+    }
+    fprintf(f, "};\n");
+    fclose(f);
+}
+
+#define DEFAULT_OUTPUT_FILENAME "out.js"
+
+void help(void)
+{
+    printf("jscompress version 1.0 Copyright (c) 2008-2018 Fabrice Bellard\n"
+           "usage: jscompress [options] filename\n"
+           "Javascript compressor\n"
+           "\n"
+           "-h          print this help\n"
+           "-n          do not compress spaces\n"
+           "-H          keep the first comment\n"
+           "-c          compress to file\n"
+           "-C name     compress to C source ('name' is the variable name)\n"
+           "-D symbol   define preprocessor symbol\n"
+           "-U symbol   undefine preprocessor symbol\n"
+           "-o outfile  set the output filename (default=%s)\n",
+           DEFAULT_OUTPUT_FILENAME);
+    exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    int c, do_strip, keep_header, compress;
+    const char *out_filename, *c_var, *fname;
+    char tmpfilename[1024];
+
+    do_strip = 1;
+    keep_header = 0;
+    out_filename = DEFAULT_OUTPUT_FILENAME;
+    compress = 0;
+    c_var = NULL;
+    for(;;) {
+        c = getopt(argc, argv, "hno:HcC:D:U:");
+        if (c == -1)
+            break;
+        switch(c) {
+        case 'h':
+            help();
+            break;
+        case 'n':
+            do_strip = 0;
+            break;
+        case 'o':
+            out_filename = optarg;
+            break;
+        case 'H':
+            keep_header = 1;
+            break;
+        case 'c':
+            compress = 1;
+            break;
+        case 'C':
+            c_var = optarg;
+            compress = 1;
+            break;
+        case 'D':
+            define_symbol(optarg);
+            break;
+        case 'U':
+            undefine_symbol(optarg);
+            break;
+        }
+    }
+    if (optind >= argc)
+        help();
+
+    filename = argv[optind++];
+
+    if (compress) {
+#if defined(__ANDROID__)
+        /* XXX: use another directory ? */
+        snprintf(tmpfilename, sizeof(tmpfilename), "out.%d", getpid());
+#else
+        snprintf(tmpfilename, sizeof(tmpfilename), "/tmp/out.%d", getpid());
+#endif
+        fname = tmpfilename;
+    } else {
+        fname = out_filename;
+    }
+    js_compress(filename, fname, do_strip, keep_header);
+    
+    if (compress) {
+        uint8_t *buf1, *buf2; 
+        int buf1_len, buf2_len;
+        
+        buf1_len = load_file(&buf1, fname);
+        unlink(fname);
+        buf2_len = lz_compress(&buf2, buf1, buf1_len);
+        if (buf2_len < 0) {
+            fprintf(stderr, "Could not compress file (UTF8 chars are forbidden)\n");
+            exit(1);
+        }
+        
+        if (c_var) {
+            save_c_source(out_filename, buf2, buf2_len, c_var);
+        } else {
+            save_file(out_filename, buf2, buf2_len);
+        }
+        free(buf1);
+        free(buf2);
+    }
+    return 0;
+}
diff --git a/libbf.c b/libbf.c
new file mode 100644
index 0000000..1741809
--- /dev/null
+++ b/libbf.c
@@ -0,0 +1,5807 @@
+/*
+ * Tiny arbitrary precision floating point library
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef __AVX2__
+#include <immintrin.h>
+#endif
+
+#include "cutils.h"
+#include "libbf.h"
+
+/* enable it to check the multiplication result */
+//#define USE_MUL_CHECK
+/* enable it to use FFT/NTT multiplication */
+#define USE_FFT_MUL
+
+//#define inline __attribute__((always_inline))
+
+#ifdef __AVX2__
+#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */
+#else
+#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */
+#endif
+
+/* XXX: adjust */
+#define BASECASE_DIV_THRESHOLD_B 300
+#define BASECASE_DIV_THRESHOLD_Q 300
+
+#if LIMB_BITS == 64
+#define FMT_LIMB1 "%" PRIx64 
+#define FMT_LIMB "%016" PRIx64 
+#define PRId_LIMB PRId64
+#define PRIu_LIMB PRIu64
+
+#else
+
+#define FMT_LIMB1 "%x"
+#define FMT_LIMB "%08x"
+#define PRId_LIMB "d"
+#define PRIu_LIMB "u"
+
+#endif
+
+typedef int bf_op2_func_t(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                          bf_flags_t flags);
+
+#ifdef USE_FFT_MUL
+
+#define FFT_MUL_R_OVERLAP_A (1 << 0)
+#define FFT_MUL_R_OVERLAP_B (1 << 1)
+
+static no_inline void fft_mul(bf_t *res, limb_t *a_tab, limb_t a_len,
+                              limb_t *b_tab, limb_t b_len, int mul_flags);
+static void fft_clear_cache(bf_context_t *s);
+#endif
+
+/* could leading zeros */
+static inline int clz(limb_t a)
+{
+    if (a == 0) {
+        return LIMB_BITS;
+    } else {
+#if LIMB_BITS == 64
+        return clz64(a);
+#else
+        return clz32(a);
+#endif
+    }
+}
+
+static inline int ctz(limb_t a)
+{
+    if (a == 0) {
+        return LIMB_BITS;
+    } else {
+#if LIMB_BITS == 64
+        return ctz64(a);
+#else
+        return ctz32(a);
+#endif
+    }
+}
+
+static inline int ceil_log2(limb_t a)
+{
+    if (a <= 1)
+        return 0;
+    else
+        return LIMB_BITS - clz(a - 1);
+}
+
+#if 0
+static inline slimb_t ceil_div(slimb_t a, limb_t b)
+{
+    if (a >= 0)
+        return (a + b - 1) / b;
+    else
+        return a / (slimb_t)b;
+}
+
+static inline slimb_t floor_div(slimb_t a, limb_t b)
+{
+    if (a >= 0) {
+        return a / b;
+    } else {
+        return (a - b + 1) / (slimb_t)b;
+    }
+}
+#endif
+
+#define malloc(s) malloc_is_forbidden(s)
+#define free(p) free_is_forbidden(p)
+#define realloc(p, s) realloc_is_forbidden(p, s)
+
+void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func,
+                     void *realloc_opaque)
+{
+    memset(s, 0, sizeof(*s));
+    s->realloc_func = realloc_func;
+    s->realloc_opaque = realloc_opaque;
+}
+
+void bf_context_end(bf_context_t *s)
+{
+    bf_clear_cache(s);
+}
+
+/* 'size' must be > 0 */
+static void *bf_malloc(bf_context_t *s, size_t size)
+{
+    return bf_realloc(s, NULL, size);
+}
+
+static void bf_free(bf_context_t *s, void *ptr)
+{
+    bf_realloc(s, ptr, 0);
+}
+
+void bf_init(bf_context_t *s, bf_t *r)
+{
+    r->ctx = s;
+    r->sign = 0;
+    r->expn = BF_EXP_ZERO;
+    r->len = 0;
+    r->tab = NULL;
+}
+
+void bf_resize(bf_t *r, limb_t len)
+{
+    if (len != r->len) {
+        r->tab = bf_realloc(r->ctx, r->tab, len * sizeof(limb_t));
+        r->len = len;
+    }
+}
+
+void bf_set_ui(bf_t *r, uint64_t a)
+{
+    r->sign = 0;
+    if (a == 0) {
+        r->expn = BF_EXP_ZERO;
+        bf_resize(r, 0);
+    } 
+#if LIMB_BITS == 32
+    else if (a <= 0xffffffff)
+#else
+    else
+#endif
+    {
+        int shift;
+        bf_resize(r, 1);
+        shift = clz(a);
+        r->tab[0] = a << shift;
+        r->expn = LIMB_BITS - shift;
+    }
+#if LIMB_BITS == 32
+    else {
+        uint32_t a1, a0;
+        int shift;
+        bf_resize(r, 2);
+        a0 = a;
+        a1 = a >> 32;
+        shift = clz(a1);
+        r->tab[0] = a0 << shift;
+        r->tab[1] = (a1 << shift) | (a0 >> (LIMB_BITS - shift));
+        r->expn = 2 * LIMB_BITS - shift;
+    }
+#endif
+}
+
+void bf_set_si(bf_t *r, int64_t a)
+{
+    if (a < 0) {
+        bf_set_ui(r, -a);
+        r->sign = 1;
+    } else {
+        bf_set_ui(r, a);
+    }
+}
+
+void bf_set_nan(bf_t *r)
+{
+    bf_resize(r, 0);
+    r->expn = BF_EXP_NAN;
+    r->sign = 0;
+}
+
+void bf_set_zero(bf_t *r, int is_neg)
+{
+    bf_resize(r, 0);
+    r->expn = BF_EXP_ZERO;
+    r->sign = is_neg;
+}
+
+void bf_set_inf(bf_t *r, int is_neg)
+{
+    bf_resize(r, 0);
+    r->expn = BF_EXP_INF;
+    r->sign = is_neg;
+}
+
+void bf_set(bf_t *r, const bf_t *a)
+{
+    if (r == a)
+        return;
+    r->sign = a->sign;
+    r->expn = a->expn;
+    bf_resize(r, a->len);
+    memcpy(r->tab, a->tab, a->len * sizeof(limb_t));
+}
+
+/* equivalent to bf_set(r, a); bf_delete(a) */ 
+void bf_move(bf_t *r, bf_t *a)
+{
+    bf_context_t *s = r->ctx;
+    if (r == a)
+        return;
+    bf_free(s, r->tab);
+    *r = *a;
+}
+
+static limb_t get_limbz(const bf_t *a, limb_t idx)
+{
+    if (idx >= a->len)
+        return 0;
+    else
+        return a->tab[idx];
+}
+
+/* get LIMB_BITS at bit position 'pos' in tab */
+static inline limb_t get_bits(const limb_t *tab, limb_t len, slimb_t pos)
+{
+    limb_t i, a0, a1;
+    int p;
+
+    i = pos >> LIMB_LOG2_BITS;
+    p = pos & (LIMB_BITS - 1);
+    if (i < len)
+        a0 = tab[i];
+    else
+        a0 = 0;
+    if (p == 0) {
+        return a0;
+    } else {
+        i++;
+        if (i < len)
+            a1 = tab[i];
+        else
+            a1 = 0;
+        return (a0 >> p) | (a1 << (LIMB_BITS - p));
+    }
+}
+
+static inline limb_t get_bit(const limb_t *tab, limb_t len, slimb_t pos)
+{
+    slimb_t i;
+    i = pos >> LIMB_LOG2_BITS;
+    if (i < 0 || i >= len)
+        return 0;
+    return (tab[i] >> (pos & (LIMB_BITS - 1))) & 1;
+}
+
+static inline limb_t limb_mask(int start, int last)
+{
+    limb_t v;
+    int n;
+    n = last - start + 1;
+    if (n == LIMB_BITS)
+        v = -1;
+    else
+        v = (((limb_t)1 << n) - 1) << start;
+    return v;
+}
+
+/* return != 0 if one bit between 0 and bit_pos inclusive is not zero. */
+static inline limb_t scan_bit_nz(const bf_t *r, slimb_t bit_pos)
+{
+    slimb_t pos;
+    limb_t v;
+    
+    pos = bit_pos >> LIMB_LOG2_BITS;
+    if (pos < 0)
+        return 0;
+    v = r->tab[pos] & limb_mask(0, bit_pos & (LIMB_BITS - 1));
+    if (v != 0)
+        return 1;
+    pos--;
+    while (pos >= 0) {
+        if (r->tab[pos] != 0)
+            return 1;
+        pos--;
+    }
+    return 0;
+}
+
+/* return the addend for rounding. Note that prec can be <= 0 for bf_rint() */
+static int bf_get_rnd_add(int *pret, const bf_t *r, limb_t l,
+                          slimb_t prec, int rnd_mode)
+{
+    int add_one, inexact;
+    limb_t bit1, bit0;
+    
+    if (rnd_mode == BF_RNDF) {
+        bit0 = 1; /* faithful rounding does not honor the INEXACT flag */
+    } else {
+        /* starting limb for bit 'prec + 1' */
+        bit0 = scan_bit_nz(r, l * LIMB_BITS - 1 - bf_max(0, prec + 1));
+    }
+
+    /* get the bit at 'prec' */
+    bit1 = get_bit(r->tab, l, l * LIMB_BITS - 1 - prec);
+    inexact = (bit1 | bit0) != 0;
+    
+    add_one = 0;
+    switch(rnd_mode) {
+    case BF_RNDZ:
+        break;
+    case BF_RNDN:
+        if (bit1) {
+            if (bit0) {
+                add_one = 1;
+            } else {
+                /* round to even */
+                add_one =
+                    get_bit(r->tab, l, l * LIMB_BITS - 1 - (prec - 1));
+            }
+        }
+        break;
+    case BF_RNDD:
+    case BF_RNDU:
+        if (r->sign == (rnd_mode == BF_RNDD))
+            add_one = inexact;
+        break;
+    case BF_RNDNA:
+    case BF_RNDF:
+        add_one = bit1;
+        break;
+    case BF_RNDNU:
+        if (bit1) {
+            if (r->sign)
+                add_one = bit0;
+            else
+                add_one = 1;
+        }
+        break;
+    default:
+        abort();
+    }
+    
+    if (inexact)
+        *pret |= BF_ST_INEXACT;
+    return add_one;
+}
+
+static int bf_set_overflow(bf_t *r, int sign, limb_t prec, bf_flags_t flags)
+{
+    slimb_t i, l, e_max;
+    int rnd_mode;
+    
+    rnd_mode = flags & BF_RND_MASK;
+    if (prec == BF_PREC_INF ||
+        rnd_mode == BF_RNDN ||
+        rnd_mode == BF_RNDNA ||
+        rnd_mode == BF_RNDNU ||
+        (rnd_mode == BF_RNDD && sign == 1) ||
+        (rnd_mode == BF_RNDU && sign == 0)) {
+        bf_set_inf(r, sign);
+    } else {
+        /* set to maximum finite number */
+        l = (prec + LIMB_BITS - 1) / LIMB_BITS;
+        bf_resize(r, l);
+        r->tab[0] = limb_mask((-prec) & (LIMB_BITS - 1),
+                              LIMB_BITS - 1);
+        for(i = 1; i < l; i++)
+            r->tab[i] = (limb_t)-1;
+        e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1);
+        r->expn = e_max;
+        r->sign = sign;
+    }
+    return BF_ST_OVERFLOW | BF_ST_INEXACT;
+}
+
+/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is
+   assumed to have length 'l'. Note: 'prec1' can be negative or
+   infinite (BF_PREC_INF). */
+static int __bf_round(bf_t *r, limb_t prec1, bf_flags_t flags, limb_t l)
+{
+    limb_t v, a;
+    int shift, add_one, ret, rnd_mode;
+    slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec;
+
+    /* e_min and e_max are computed to match the IEEE 754 conventions */
+    e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1);
+    e_min = -e_range + 3;
+    e_max = e_range;
+    
+    if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) {
+        /* restrict the precision in case of potentially subnormal
+           result */
+        prec = prec1 - (e_min - r->expn);
+    } else {
+        prec = prec1;
+    }
+    
+    /* round to prec bits */
+    rnd_mode = flags & BF_RND_MASK;
+    ret = 0;
+    add_one = bf_get_rnd_add(&ret, r, l, prec, rnd_mode);
+    
+    if (prec <= 0) {
+        if (add_one) {
+            bf_resize(r, 1);
+            r->tab[0] = (limb_t)1 << (LIMB_BITS - 1);
+            r->expn += 1 - prec;
+            ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT;
+            return ret;
+        } else {
+            goto underflow;
+        }
+    } else if (add_one) {
+        limb_t carry;
+        
+        /* add one starting at digit 'prec - 1' */
+        bit_pos = l * LIMB_BITS - 1 - (prec - 1);
+        pos = bit_pos >> LIMB_LOG2_BITS;
+        carry = (limb_t)1 << (bit_pos & (LIMB_BITS - 1));
+        
+        for(i = pos; i < l; i++) {
+            v = r->tab[i] + carry;
+            carry = (v < carry);
+            r->tab[i] = v;
+            if (carry == 0)
+                break;
+        }
+        if (carry) {
+            /* shift right by one digit */
+            v = 1;
+            for(i = l - 1; i >= pos; i--) {
+                a = r->tab[i];
+                r->tab[i] = (a >> 1) | (v << (LIMB_BITS - 1));
+                v = a;
+            }
+            r->expn++;
+        }
+    }
+    
+    /* check underflow */
+    if (unlikely(r->expn < e_min)) {
+        if (flags & BF_FLAG_SUBNORMAL) {
+            /* if inexact, also set the underflow flag */
+            if (ret & BF_ST_INEXACT)
+                ret |= BF_ST_UNDERFLOW;
+        } else {
+        underflow:
+            ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT;
+            bf_set_zero(r, r->sign);
+            return ret;
+        }
+    }
+    
+    /* check overflow */
+    if (unlikely(r->expn > e_max))
+        return bf_set_overflow(r, r->sign, prec1, flags);
+    
+    /* keep the bits starting at 'prec - 1' */
+    bit_pos = l * LIMB_BITS - 1 - (prec - 1);
+    i = bit_pos >> LIMB_LOG2_BITS;
+    if (i >= 0) {
+        shift = bit_pos & (LIMB_BITS - 1);
+        if (shift != 0)
+            r->tab[i] &= limb_mask(shift, LIMB_BITS - 1);
+    } else {
+        i = 0;
+    }
+    /* remove trailing zeros */
+    while (r->tab[i] == 0)
+        i++;
+    if (i > 0) {
+        l -= i;
+        memmove(r->tab, r->tab + i, l * sizeof(limb_t));
+    }
+    bf_resize(r, l);
+    return ret;
+}
+
+/* 'r' must be a finite number */
+int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags)
+{
+    limb_t l, v, a;
+    int shift, ret;
+    slimb_t i;
+    
+    //    bf_print_str("bf_renorm", r);
+    l = r->len;
+    while (l > 0 && r->tab[l - 1] == 0)
+        l--;
+    if (l == 0) {
+        /* zero */
+        r->expn = BF_EXP_ZERO;
+        bf_resize(r, 0);
+        ret = 0;
+    } else {
+        r->expn -= (r->len - l) * LIMB_BITS;
+        /* shift to have the MSB set to '1' */
+        v = r->tab[l - 1];
+        shift = clz(v);
+        if (shift != 0) {
+            v = 0;
+            for(i = 0; i < l; i++) {
+                a = r->tab[i];
+                r->tab[i] = (a << shift) | (v >> (LIMB_BITS - shift));
+                v = a;
+            }
+            r->expn -= shift;
+        }
+        ret = __bf_round(r, prec1, flags, l);
+    }
+    //    bf_print_str("r_final", r);
+    return ret;
+}
+
+/* return true if rounding can be done at precision 'prec' assuming
+   the exact result r is such that |r-a| <= 2^(EXP(a)-k). */
+/* XXX: check the case where the exponent would be incremented by the
+   rounding */
+int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k)
+{
+    BOOL is_rndn;
+    slimb_t bit_pos, n;
+    limb_t bit;
+    
+    if (a->expn == BF_EXP_INF || a->expn == BF_EXP_NAN)
+        return FALSE;
+    if (rnd_mode == BF_RNDF) {
+        return (k >= (prec + 1));
+    }
+    if (a->expn == BF_EXP_ZERO)
+        return FALSE;
+    is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA ||
+               rnd_mode == BF_RNDNU);
+    if (k < (prec + 2))
+        return FALSE;
+    bit_pos = a->len * LIMB_BITS - 1 - prec;
+    n = k - prec;
+    /* bit pattern for RNDN or RNDNA: 0111.. or 1000...
+       for other rounding modes: 000... or 111... 
+    */
+    bit = get_bit(a->tab, a->len, bit_pos);
+    bit_pos--;
+    n--;
+    bit ^= is_rndn;
+    /* XXX: slow, but a few iterations on average */
+    while (n != 0) {
+        if (get_bit(a->tab, a->len, bit_pos) != bit)
+            return TRUE;
+        bit_pos--;
+        n--;
+    }
+    return FALSE;
+}
+
+int bf_round(bf_t *r, limb_t prec, bf_flags_t flags)
+{
+    if (r->len == 0)
+        return 0;
+    return __bf_round(r, prec, flags, r->len);
+}
+
+/* for debugging */
+static __maybe_unused void dump_limbs(const char *str, const limb_t *tab, limb_t n)
+{
+    limb_t i;
+    printf("%s: len=%" PRId_LIMB "\n", str, n);
+    for(i = 0; i < n; i++) {
+        printf("%" PRId_LIMB ": " FMT_LIMB "\n",
+               i, tab[i]);
+    }
+}
+
+/* for debugging */
+void bf_print_str(const char *str, const bf_t *a)
+{
+    slimb_t i;
+    printf("%s=", str);
+
+    if (a->expn == BF_EXP_NAN) {
+        printf("NaN");
+    } else {
+        if (a->sign)
+            putchar('-');
+        if (a->expn == BF_EXP_ZERO) {
+            putchar('0');
+        } else if (a->expn == BF_EXP_INF) {
+            printf("Inf");
+        } else {
+            printf("0x0.");
+            for(i = a->len - 1; i >= 0; i--)
+                printf(FMT_LIMB, a->tab[i]);
+            printf("p%" PRId_LIMB, a->expn);
+        }
+    }
+    printf("\n");
+}
+
+/* compare the absolute value of 'a' and 'b'. Return < 0 if a < b, 0
+   if a = b and > 0 otherwise. */
+int bf_cmpu(const bf_t *a, const bf_t *b)
+{
+    slimb_t i;
+    limb_t len, v1, v2;
+    
+    if (a->expn != b->expn) {
+        if (a->expn < b->expn)
+            return -1;
+        else
+            return 1;
+    }
+    len = bf_max(a->len, b->len);
+    for(i = len - 1; i >= 0; i--) {
+        v1 = get_limbz(a, a->len - len + i);
+        v2 = get_limbz(b, b->len - len + i);
+        if (v1 != v2) {
+            if (v1 < v2)
+                return -1;
+            else
+                return 1;
+        }
+    }
+    return 0;
+}
+
+/* Full order: -0 < 0, NaN == NaN and NaN is larger than all other numbers */
+int bf_cmp_full(const bf_t *a, const bf_t *b)
+{
+    int res;
+    
+    if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) {
+        if (a->expn == b->expn)
+            res = 0;
+        else if (a->expn == BF_EXP_NAN)
+            res = 1;
+        else
+            res = -1;
+    } else if (a->sign != b->sign) {
+        res = 1 - 2 * a->sign;
+    } else {
+        res = bf_cmpu(a, b);
+        if (a->sign)
+            res = -res;
+    }
+    return res;
+}
+
+#define BF_CMP_EQ 1
+#define BF_CMP_LT 2
+#define BF_CMP_LE 3
+
+static int bf_cmp(const bf_t *a, const bf_t *b, int op)
+{
+    BOOL is_both_zero;
+    int res;
+    
+    if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN)
+        return 0;
+    if (a->sign != b->sign) {
+        is_both_zero = (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO);
+        if (is_both_zero) {
+            return op & BF_CMP_EQ;
+        } else if (op & BF_CMP_LT) {
+            return a->sign;
+        } else {
+            return FALSE;
+        }
+    } else {
+        res = bf_cmpu(a, b);
+        if (res == 0) {
+            return op & BF_CMP_EQ;
+        } else if (op & BF_CMP_LT) {
+            return (res < 0) ^ a->sign;
+        } else {
+            return FALSE;
+        }
+    }
+}
+
+int bf_cmp_eq(const bf_t *a, const bf_t *b)
+{
+    return bf_cmp(a, b, BF_CMP_EQ);
+}
+
+int bf_cmp_le(const bf_t *a, const bf_t *b)
+{
+    return bf_cmp(a, b, BF_CMP_LE);
+}
+
+int bf_cmp_lt(const bf_t *a, const bf_t *b)
+{
+    return bf_cmp(a, b, BF_CMP_LT);
+}
+
+/* Compute the number of bits 'n' matching the pattern:
+   a= X1000..0
+   b= X0111..1
+              
+   When computing a-b, the result will have at least n leading zero
+   bits.
+
+   Precondition: a > b and a.expn - b.expn = 0 or 1
+*/
+static limb_t count_cancelled_bits(const bf_t *a, const bf_t *b)
+{
+    slimb_t bit_offset, b_offset, n;
+    int p, p1;
+    limb_t v1, v2, mask;
+
+    bit_offset = a->len * LIMB_BITS - 1;
+    b_offset = (b->len - a->len) * LIMB_BITS - (LIMB_BITS - 1) +
+        a->expn - b->expn;
+    n = 0;
+
+    /* first search the equals bits */
+    for(;;) {
+        v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS);
+        v2 = get_bits(b->tab, b->len, bit_offset + b_offset);
+        //        printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2);
+        if (v1 != v2)
+            break;
+        n += LIMB_BITS;
+        bit_offset -= LIMB_BITS;
+    }
+    /* find the position of the first different bit */
+    p = clz(v1 ^ v2) + 1;
+    n += p;
+    /* then search for '0' in a and '1' in b */
+    p = LIMB_BITS - p;
+    if (p > 0) {
+        /* search in the trailing p bits of v1 and v2 */
+        mask = limb_mask(0, p - 1);
+        p1 = bf_min(clz(v1 & mask), clz((~v2) & mask)) - (LIMB_BITS - p);
+        n += p1;
+        if (p1 != p)
+            goto done;
+    }
+    bit_offset -= LIMB_BITS;
+    for(;;) {
+        v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS);
+        v2 = get_bits(b->tab, b->len, bit_offset + b_offset);
+        //        printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2);
+        if (v1 != 0 || v2 != -1) {
+            /* different: count the matching bits */
+            p1 = bf_min(clz(v1), clz(~v2));
+            n += p1;
+            break;
+        }
+        n += LIMB_BITS;
+        bit_offset -= LIMB_BITS;
+    }
+ done:
+    return n;
+}
+
+static int bf_add_internal(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                           bf_flags_t flags, int b_neg)
+{
+    const bf_t *tmp;
+    int is_sub, ret, cmp_res, a_sign, b_sign;
+
+    a_sign = a->sign;
+    b_sign = b->sign ^ b_neg;
+    is_sub = a_sign ^ b_sign;
+    cmp_res = bf_cmpu(a, b);
+    if (cmp_res < 0) {
+        tmp = a;
+        a = b;
+        b = tmp;
+        a_sign = b_sign; /* b_sign is never used later */
+    }
+    /* abs(a) >= abs(b) */
+    if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) {
+        /* zero result */
+        bf_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD);
+        ret = 0;
+    } else if (a->len == 0 || b->len == 0) {
+        ret = 0;
+        if (a->expn >= BF_EXP_INF) {
+            if (a->expn == BF_EXP_NAN) {
+                /* at least one operand is NaN */
+                bf_set_nan(r);
+            } else if (b->expn == BF_EXP_INF && is_sub) {
+                /* infinities with different signs */
+                bf_set_nan(r);
+                ret = BF_ST_INVALID_OP;
+            } else {
+                bf_set_inf(r, a_sign);
+            }
+        } else {
+            /* at least one zero and not subtract */
+            bf_set(r, a);
+            r->sign = a_sign;
+            goto renorm;
+        }
+    } else {
+        slimb_t d, a_offset, b_bit_offset, i, cancelled_bits;
+        limb_t carry, v1, v2, u, r_len, carry1, precl, tot_len, z, sub_mask;
+
+        r->sign = a_sign;
+        r->expn = a->expn;
+        d = a->expn - b->expn;
+        /* must add more precision for the leading cancelled bits in
+           subtraction */
+        if (is_sub) {
+            if (d <= 1)
+                cancelled_bits = count_cancelled_bits(a, b);
+            else
+                cancelled_bits = 1;
+        } else {
+            cancelled_bits = 0;
+        }
+        
+        /* add two extra bits for rounding */
+        precl = (cancelled_bits + prec + 2 + LIMB_BITS - 1) / LIMB_BITS;
+        tot_len = bf_max(a->len, b->len + (d + LIMB_BITS - 1) / LIMB_BITS);
+        r_len = bf_min(precl, tot_len);
+        bf_resize(r, r_len);
+        a_offset = a->len - r_len;
+        b_bit_offset = (b->len - r_len) * LIMB_BITS + d;
+
+        /* compute the bits before for the rounding */
+        carry = is_sub;
+        z = 0;
+        sub_mask = -is_sub;
+        i = r_len - tot_len;
+        while (i < 0) {
+            slimb_t ap, bp;
+            BOOL inflag;
+            
+            ap = a_offset + i;
+            bp = b_bit_offset + i * LIMB_BITS;
+            inflag = FALSE;
+            if (ap >= 0 && ap < a->len) {
+                v1 = a->tab[ap];
+                inflag = TRUE;
+            } else {
+                v1 = 0;
+            }
+            if (bp + LIMB_BITS > 0 && bp < (slimb_t)(b->len * LIMB_BITS)) {
+                v2 = get_bits(b->tab, b->len, bp);
+                inflag = TRUE;
+            } else {
+                v2 = 0;
+            }
+            if (!inflag) {
+                /* outside 'a' and 'b': go directly to the next value
+                   inside a or b so that the running time does not
+                   depend on the exponent difference */
+                i = 0;
+                if (ap < 0)
+                    i = bf_min(i, -a_offset);
+                /* b_bit_offset + i * LIMB_BITS + LIMB_BITS >= 1
+                   equivalent to 
+                   i >= ceil(-b_bit_offset + 1 - LIMB_BITS) / LIMB_BITS)
+                */
+                if (bp + LIMB_BITS <= 0)
+                    i = bf_min(i, (-b_bit_offset) >> LIMB_LOG2_BITS);
+            } else {
+                i++;
+            }
+            v2 ^= sub_mask;
+            u = v1 + v2;
+            carry1 = u < v1;
+            u += carry;
+            carry = (u < carry) | carry1;
+            z |= u;
+        }
+        /* and the result */
+        for(i = 0; i < r_len; i++) {
+            v1 = get_limbz(a, a_offset + i);
+            v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS);
+            v2 ^= sub_mask;
+            u = v1 + v2;
+            carry1 = u < v1;
+            u += carry;
+            carry = (u < carry) | carry1;
+            r->tab[i] = u;
+        }
+        /* set the extra bits for the rounding */
+        r->tab[0] |= (z != 0);
+
+        /* carry is only possible in add case */
+        if (!is_sub && carry) {
+            bf_resize(r, r_len + 1);
+            r->tab[r_len] = 1;
+            r->expn += LIMB_BITS;
+        }
+    renorm:
+        ret = bf_normalize_and_round(r, prec, flags);
+    }
+    return ret;
+}
+
+static int __bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                     bf_flags_t flags)
+{
+    return bf_add_internal(r, a, b, prec, flags, 0);
+}
+
+static int __bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                     bf_flags_t flags)
+{
+    return bf_add_internal(r, a, b, prec, flags, 1);
+}
+
+static limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, 
+                     limb_t n, limb_t carry)
+{
+    slimb_t i;
+    limb_t k, a, v, k1;
+    
+    k = carry;
+    for(i=0;i<n;i++) {
+        v = op1[i];
+        a = v + op2[i];
+        k1 = a < v;
+        a = a + k;
+        k = (a < k) | k1;
+        res[i] = a;
+    }
+    return k;
+}
+
+/* tabr[] += taba[] * b, return the high word. */
+static limb_t mp_add_mul1(limb_t *tabr, const limb_t *taba, limb_t n,
+                              limb_t b)
+{
+    limb_t i, l;
+    dlimb_t t;
+    
+    l = 0;
+    for(i = 0; i < n; i++) {
+        t = (dlimb_t)taba[i] * (dlimb_t)b + l + tabr[i];
+        tabr[i] = t;
+        l = t >> LIMB_BITS;
+    }
+    return l;
+}
+
+/* tabr[] -= taba[] * b. Return the value to substract to the high
+   word. */
+static limb_t mp_sub_mul1(limb_t *tabr, const limb_t *taba, limb_t n,
+                          limb_t b)
+{
+    limb_t i, l;
+    dlimb_t t;
+    
+    l = 0;
+    for(i = 0; i < n; i++) {
+        t = tabr[i] - (dlimb_t)taba[i] * (dlimb_t)b - l;
+        tabr[i] = t;
+        l = -(t >> LIMB_BITS);
+    }
+    return l;
+}
+
+/* WARNING: d must be >= 2^(LIMB_BITS-1) */
+static inline limb_t udiv1norm_init(limb_t d)
+{
+    limb_t a0, a1;
+    a1 = -d - 1;
+    a0 = -1;
+    return (((dlimb_t)a1 << LIMB_BITS) | a0) / d;
+}
+
+/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0
+   / d' with 0 <= a1 < d. */
+static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0,
+                                limb_t d, limb_t d_inv)
+{
+    limb_t n1m, n_adj, q, r, ah;
+    dlimb_t a;
+    n1m = ((slimb_t)a0 >> (LIMB_BITS - 1));
+    n_adj = a0 + (n1m & d);
+    a = (dlimb_t)d_inv * (a1 - n1m) + n_adj;
+    q = (a >> LIMB_BITS) + a1;
+    /* compute a - q * r and update q so that the remainder is\
+       between 0 and d - 1 */
+    a = ((dlimb_t)a1 << LIMB_BITS) | a0;
+    a = a - (dlimb_t)q * d - d;
+    ah = a >> LIMB_BITS;
+    q += 1 + ah;
+    r = (limb_t)a + (ah & d);
+    *pr = r;
+    return q;
+}
+
+/* b must be >= 1 << (LIMB_BITS - 1) */
+static limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n,
+                          limb_t b, limb_t r)
+{
+    slimb_t i;
+
+    if (n >= 3) {
+        limb_t b_inv;
+        b_inv = udiv1norm_init(b);
+        for(i = n - 1; i >= 0; i--) {
+            tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv);
+        }
+    } else {
+        dlimb_t a1;
+        for(i = n - 1; i >= 0; i--) {
+            a1 = ((dlimb_t)r << LIMB_BITS) | taba[i];
+            tabr[i] = a1 / b;
+            r = a1 % b;
+        }
+    }
+    return r;
+}
+
+/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb
+   - 1] must be >= 1 << (LIMB_BITS - 1). na - nb must be >= 0. 'taba'
+   is modified and contains the remainder (nb limbs). tabq[0..na-nb]
+   contains the quotient. taba[na] is modified. */
+static void mp_divnorm(limb_t *tabq, 
+                       limb_t *taba, limb_t na, 
+                       const limb_t *tabb, limb_t nb)
+{
+    limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r;
+    slimb_t i;
+
+    b1 = tabb[nb - 1];
+    if (nb == 1) {
+        taba[0] = mp_div1norm(tabq, taba, na, b1, 0);
+        return;
+    }
+    taba[na] = 0;
+    n = na - nb;
+    if (n >= 3)
+        b1_inv = udiv1norm_init(b1);
+    else
+        b1_inv = 0;
+
+    /* XXX: could simplify the first iteration */
+    for(i = n; i >= 0; i--) {
+        if (unlikely(taba[i + nb] >= b1)) {
+            q = -1;
+        } else if (b1_inv) {
+            q = udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv);
+        } else {
+            dlimb_t al;
+            al = ((dlimb_t)taba[i + nb] << LIMB_BITS) | taba[i + nb - 1];
+            q = al / b1;
+            r = al % b1;
+        }
+        r = mp_sub_mul1(taba + i, tabb, nb, q);
+
+        v = taba[i + nb];
+        a = v - r;
+        c = (a > v);
+        taba[i + nb] = a;
+
+        if (c != 0) {
+            /* negative result */
+            for(;;) {
+                q--;
+                c = mp_add(taba + i, taba + i, tabb, nb, 0);
+                /* propagate carry and test if positive result */
+                if (c != 0) {
+                    if (++taba[i + nb] == 0) {
+                        break;
+                    }
+                }
+            }
+        }
+        tabq[i] = q;
+    }
+}
+
+int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+           bf_flags_t flags)
+{
+    limb_t i;
+    int ret, r_sign;
+
+    if (a->len < b->len) {
+        const bf_t *tmp = a;
+        a = b;
+        b = tmp;
+    }
+    r_sign = a->sign ^ b->sign;
+    /* here b->len <= a->len */
+    if (b->len == 0) {
+        if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            ret = 0;
+        } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) {
+            if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) ||
+                (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) {
+                bf_set_nan(r);
+                ret = BF_ST_INVALID_OP;
+            } else {
+                bf_set_inf(r, r_sign);
+                ret = 0;
+            }
+        } else {
+            bf_set_zero(r, r_sign);
+            ret = 0;
+        }
+    } else {
+        bf_t tmp, *r1 = NULL;
+        limb_t a_len, b_len, precl;
+        limb_t *a_tab, *b_tab;
+            
+        a_len = a->len;
+        b_len = b->len;
+        
+        if ((flags & BF_RND_MASK) == BF_RNDF) {
+            /* faithful rounding does not require using the full inputs */
+            precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS;
+            a_len = bf_min(a_len, precl);
+            b_len = bf_min(b_len, precl);
+        }
+        a_tab = a->tab + a->len - a_len;
+        b_tab = b->tab + b->len - b_len;
+        
+#ifdef USE_FFT_MUL
+        if (b_len >= FFT_MUL_THRESHOLD) {
+            int mul_flags = 0;
+            if (r == a)
+                mul_flags |= FFT_MUL_R_OVERLAP_A;
+            if (r == b)
+                mul_flags |= FFT_MUL_R_OVERLAP_B;
+            fft_mul(r, a_tab, a_len, b_tab, b_len, mul_flags);
+        } else
+#endif
+        {
+            if (r == a || r == b) {
+                bf_init(r->ctx, &tmp);
+                r1 = r;
+                r = &tmp;
+            }
+            bf_resize(r, a_len + b_len);
+            memset(r->tab, 0, sizeof(limb_t) * a_len);
+            for(i = 0; i < b_len; i++) {
+                r->tab[i + a_len] = mp_add_mul1(r->tab + i, a_tab, a_len,
+                                                b_tab[i]);
+            }
+        }
+        r->sign = r_sign;
+        r->expn = a->expn + b->expn;
+        ret = bf_normalize_and_round(r, prec, flags);
+        if (r == &tmp)
+            bf_move(r1, &tmp);
+    }
+    return ret;
+}
+
+/* multiply 'r' by 2^e */
+int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags)
+{
+    slimb_t e_max;
+    if (r->len == 0)
+        return 0;
+    e_max = ((limb_t)1 << BF_EXP_BITS_MAX) - 1;
+    e = bf_max(e, -e_max);
+    e = bf_min(e, e_max);
+    r->expn += e;
+    return __bf_round(r, prec, flags, r->len);
+}
+
+static void bf_recip_rec(bf_t *a, const bf_t *x, limb_t prec1)
+{
+    bf_t t0;
+    limb_t prec;
+
+    bf_init(a->ctx, &t0);
+
+    if (prec1 <= LIMB_BITS - 3) {
+        limb_t v;
+        /* initial approximation */
+        v = x->tab[x->len - 1];
+        /* 2^(L-1) <= v <= 2^L-1 (L=LIMB_BITS) */
+        v = ((dlimb_t)1 << (2 * LIMB_BITS - 2)) / v;
+        /* 2^(L-2) <= v <= 2^(L-1) */
+        bf_resize(a, 1);
+        a->sign = x->sign;
+        a->expn = 2 - x->expn;
+        if (v == ((limb_t)1 << (LIMB_BITS - 1))) {
+            a->tab[0] = v;
+        } else {
+            a->tab[0] = v << 1;
+            a->expn--;
+        }
+        a->tab[0] &= limb_mask(LIMB_BITS - prec1, LIMB_BITS - 1);
+    } else {
+        /* XXX: prove the added precision */
+        bf_recip_rec(a, x, (prec1 / 2) + 8);
+        prec = prec1 + 8;
+
+        /* a = a + a * (1 - x * a) */
+        bf_mul(&t0, x, a, prec, BF_RNDF);
+        t0.sign ^= 1;
+        bf_add_si(&t0, &t0, 1, prec, BF_RNDF);
+        bf_mul(&t0, &t0, a, prec, BF_RNDF);
+        bf_add(a, a, &t0, prec1, BF_RNDF);
+    }
+    //    bf_print_str("r", a);
+    bf_delete(&t0);
+}
+
+/* Note: only faithful rounding is supported */
+void bf_recip(bf_t *r, const bf_t *a, limb_t prec)
+{
+    assert(r != a);
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+        } else if (a->expn == BF_EXP_INF) {
+            bf_set_zero(r, a->sign);
+        } else {
+            bf_set_inf(r, a->sign);
+        }
+    } else {
+        //        bf_print_str("a", a);
+        bf_recip_rec(r, a, prec);
+    }
+}
+
+/* add zero limbs if necessary to have at least precl limbs */
+static void bf_add_zero_limbs(bf_t *r, limb_t precl)
+{
+    limb_t l = r->len;
+    if (l < precl) {
+        bf_resize(r, precl);
+        memmove(r->tab + precl - l, r->tab,
+                l * sizeof(limb_t));
+        memset(r->tab, 0, (precl - l) * sizeof(limb_t));
+    }
+}
+
+/* set a bit to 1 at bit position >= (precl * LIMB_BITS - 1) */
+static void bf_or_one(bf_t *r, limb_t precl)
+{
+    bf_add_zero_limbs(r, precl);
+    r->tab[0] |= 1;
+}
+
+/* Return e such as a=m*2^e with m odd integer. return 0 if a is zero,
+   Infinite or Nan. */
+slimb_t bf_get_exp_min(const bf_t *a)
+{
+    slimb_t i;
+    limb_t v;
+    int k;
+    
+    for(i = 0; i < a->len; i++) {
+        v = a->tab[i];
+        if (v != 0) {
+            k = ctz(v);
+            return a->expn - (a->len - i) * LIMB_BITS + k;
+        }
+    }
+    return 0;
+}
+
+/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the
+   integer defined as floor(a/b) and r = a - q * b. */
+static void bf_tdivremu(bf_t *q, bf_t *r,
+                        const bf_t *a, const bf_t *b)
+{
+    if (a->expn < b->expn) {
+        bf_set_ui(q, 0);
+        bf_set(r, a);
+    } else {
+        /* for large numbers, use the floating point division in
+           faithful mode */
+        bf_div(q, a, b, bf_max(a->expn - b->expn + 1, 2), BF_RNDF);
+        bf_rint(q, BF_PREC_INF, BF_RNDZ);
+        bf_mul(r, q, b, BF_PREC_INF, BF_RNDN);
+        bf_sub(r, a, r, BF_PREC_INF, BF_RNDN);
+        if (r->len != 0 && r->sign) {
+            bf_add_si(q, q, -1, BF_PREC_INF, BF_RNDZ);
+            bf_add(r, r, b, BF_PREC_INF, BF_RNDZ);
+        }
+    }
+}
+
+static int __bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                    bf_flags_t flags)
+{
+    int ret, r_sign;
+    limb_t n, nb, precl;
+    
+    r_sign = a->sign ^ b->sign;
+    if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) {
+        if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            return 0;
+        } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) {
+            bf_set_nan(r);
+            return BF_ST_INVALID_OP;
+        } else if (a->expn == BF_EXP_INF) {
+            bf_set_inf(r, r_sign);
+            return 0;
+        } else {
+            bf_set_zero(r, r_sign);
+            return 0;
+        }
+    } else if (a->expn == BF_EXP_ZERO) {
+        if (b->expn == BF_EXP_ZERO) {
+            bf_set_nan(r);
+            return BF_ST_INVALID_OP;
+        } else {
+            bf_set_zero(r, r_sign);
+            return 0;
+        }
+    } else if (b->expn == BF_EXP_ZERO) {
+        bf_set_inf(r, r_sign);
+        return BF_ST_DIVIDE_ZERO;
+    }
+
+    /* number of limbs of the quotient (2 extra bits for rounding) */
+    precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS;
+    nb = b->len;
+    n = bf_max(a->len, precl);
+    
+    if (nb <= BASECASE_DIV_THRESHOLD_B ||
+        (slimb_t)n <= (BASECASE_DIV_THRESHOLD_Q - 1)) {
+        limb_t *taba, na, i;
+        slimb_t d;
+        
+        na = n + nb;
+        taba = bf_malloc(r->ctx, (na + 1) * sizeof(limb_t));
+        d = na - a->len;
+        memset(taba, 0, d * sizeof(limb_t));
+        memcpy(taba + d, a->tab, a->len * sizeof(limb_t));
+        bf_resize(r, n + 1);
+        mp_divnorm(r->tab, taba, na, b->tab, nb);
+        
+        /* see if non zero remainder */
+        for(i = 0; i < nb; i++) {
+            if (taba[i] != 0) {
+                r->tab[0] |= 1;
+                break;
+            }
+        }
+        bf_free(r->ctx, taba);
+        r->expn = a->expn - b->expn + LIMB_BITS;
+        r->sign = r_sign;
+        ret = bf_normalize_and_round(r, prec, flags);
+    } else if ((flags & BF_RND_MASK) == BF_RNDF) {
+        bf_t b_inv;
+        bf_init(r->ctx, &b_inv);
+        bf_recip(&b_inv, b, prec + 3);
+        ret = bf_mul(r, a, &b_inv, prec, flags);
+        bf_delete(&b_inv);
+    } else {
+        bf_t a1_s, *a1 = &a1_s;
+        bf_t b1_s, *b1 = &b1_s;
+        bf_t rem_s, *rem = &rem_s;
+        
+        /* convert the mantissa of 'a' and 'b' to integers and generate
+           a quotient with at least prec + 2 bits */
+        a1->expn = (n + nb) * LIMB_BITS;
+        a1->tab = a->tab;
+        a1->len = a->len;
+        a1->sign = 0;
+
+        b1->expn = nb * LIMB_BITS;
+        b1->tab = b->tab;
+        b1->len = nb;
+        b1->sign = 0;
+
+        bf_init(r->ctx, rem);
+        bf_tdivremu(r, rem, a1, b1);
+        /* the remainder is not zero: put it in the rounding bits */
+        if (rem->len != 0) {
+            bf_or_one(r, precl);
+        }
+        bf_delete(rem);
+        r->expn += a->expn - b->expn - n * LIMB_BITS;
+        r->sign = r_sign;
+        ret = bf_round(r, prec, flags);
+    }
+    return ret;
+}
+
+/* division and remainder. 
+   
+   rnd_mode is the rounding mode for the quotient. The additional
+   rounding mode BF_RND_EUCLIDIAN is supported.
+
+   'q' is an integer. 'r' is rounded with prec and flags (prec can be
+   BF_PREC_INF).
+*/
+int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b,
+              limb_t prec, bf_flags_t flags, int rnd_mode)
+{
+    bf_t a1_s, *a1 = &a1_s;
+    bf_t b1_s, *b1 = &b1_s;
+    int q_sign;
+    BOOL is_ceil, is_rndn;
+    
+    assert(q != a && q != b);
+    assert(r != a && r != b);
+    assert(q != r);
+    
+    if (a->len == 0 || b->len == 0) {
+        bf_set_zero(q, 0);
+        if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            return 0;
+        } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) {
+            bf_set_nan(r);
+            return BF_ST_INVALID_OP;
+        } else {
+            bf_set(r, a);
+            return bf_round(r, prec, flags);
+        }
+    }
+
+    q_sign = a->sign ^ b->sign;
+    is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA ||
+               rnd_mode == BF_RNDNU);
+    switch(rnd_mode) {
+    default:
+    case BF_RNDZ:
+    case BF_RNDN:
+    case BF_RNDNA:
+        is_ceil = FALSE;
+        break;
+    case BF_RNDD:
+        is_ceil = q_sign;
+        break;
+    case BF_RNDU:
+        is_ceil = q_sign ^ 1;
+        break;
+    case BF_DIVREM_EUCLIDIAN:
+        is_ceil = a->sign;
+        break;
+    case BF_RNDNU:
+        /* XXX: unsupported yet */
+        abort();
+    }
+
+    a1->expn = a->expn;
+    a1->tab = a->tab;
+    a1->len = a->len;
+    a1->sign = 0;
+    
+    b1->expn = b->expn;
+    b1->tab = b->tab;
+    b1->len = b->len;
+    b1->sign = 0;
+
+    /* XXX: could improve to avoid having a large 'q' */
+    bf_tdivremu(q, r, a1, b1);
+
+    if (r->len != 0) {
+        if (is_rndn) {
+            int res;
+            b1->expn--;
+            res = bf_cmpu(r, b1);
+            b1->expn++;
+            if (res > 0 ||
+                (res == 0 &&
+                 (rnd_mode == BF_RNDNA ||
+                  get_bit(q->tab, q->len, q->len * LIMB_BITS - q->expn)))) {
+                goto do_sub_r;
+            }
+        } else if (is_ceil) {
+        do_sub_r:
+            bf_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ);
+            bf_sub(r, r, b1, BF_PREC_INF, BF_RNDZ);
+        }
+    }
+
+    r->sign ^= a->sign;
+    q->sign = q_sign;
+    return bf_round(r, prec, flags);
+}
+
+int bf_fmod(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+            bf_flags_t flags)
+{
+    bf_t q_s, *q = &q_s;
+    int ret;
+    
+    bf_init(r->ctx, q);
+    ret = bf_divrem(q, r, a, b, prec, flags, BF_RNDZ);
+    bf_delete(q);
+    return ret;
+}
+
+int bf_remainder(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                 bf_flags_t flags)
+{
+    bf_t q_s, *q = &q_s;
+    int ret;
+    
+    bf_init(r->ctx, q);
+    ret = bf_divrem(q, r, a, b, prec, flags, BF_RNDN);
+    bf_delete(q);
+    return ret;
+}
+
+static inline int bf_get_limb(slimb_t *pres, const bf_t *a, int flags)
+{
+#if LIMB_BITS == 32
+    return bf_get_int32(pres, a, flags);
+#else
+    return bf_get_int64(pres, a, flags);
+#endif
+}
+
+int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+              bf_flags_t flags)
+{
+    bf_t q_s, *q = &q_s;
+    int ret;
+    
+    bf_init(r->ctx, q);
+    ret = bf_divrem(q, r, a, b, prec, flags, BF_RNDN);
+    bf_get_limb(pq, q, BF_GET_INT_MOD);
+    bf_delete(q);
+    return ret;
+}
+
+static __maybe_unused inline limb_t mul_mod(limb_t a, limb_t b, limb_t m)
+{
+    dlimb_t t;
+    t = (dlimb_t)a * (dlimb_t)b;
+    return t % m;
+}
+
+#if defined(USE_MUL_CHECK)
+static limb_t mp_mod1(const limb_t *tab, limb_t n, limb_t m, limb_t r)
+{
+    slimb_t i;
+    dlimb_t t;
+
+    for(i = n - 1; i >= 0; i--) {
+        t = ((dlimb_t)r << LIMB_BITS) | tab[i];
+        r = t % m;
+    }
+    return r;
+}
+#endif
+
+/* (128.0 / sqrt((i + 64) / 256)) & 0xff */
+static const uint8_t rsqrt_table[192] = {
+  0,254,252,250,248,247,245,243,241,240,238,236,235,233,232,230,
+229,228,226,225,223,222,221,220,218,217,216,215,214,212,211,210,
+209,208,207,206,205,204,203,202,201,200,199,198,197,196,195,194,
+194,193,192,191,190,189,189,188,187,186,185,185,184,183,182,182,
+181,180,180,179,178,178,177,176,176,175,174,174,173,172,172,171,
+171,170,169,169,168,168,167,167,166,166,165,164,164,163,163,162,
+162,161,161,160,160,159,159,158,158,158,157,157,156,156,155,155,
+154,154,154,153,153,152,152,151,151,151,150,150,149,149,149,148,
+148,147,147,147,146,146,146,145,145,144,144,144,143,143,143,142,
+142,142,141,141,141,140,140,140,139,139,139,138,138,138,137,137,
+137,137,136,136,136,135,135,135,134,134,134,134,133,133,133,132,
+132,132,132,131,131,131,131,130,130,130,130,129,129,129,129,128,
+};
+
+static void __bf_rsqrt(bf_t *a, const bf_t *x, limb_t prec1)
+{
+    bf_t t0;
+    limb_t prec;
+
+    if (prec1 <= 7) {
+        slimb_t e;
+        limb_t v;
+        /* initial approximation using 8 mantissa bits */
+        v = x->tab[x->len - 1];
+        e = x->expn;
+        if (e & 1) {
+            v >>= 1;
+            e++;
+        }
+        v = rsqrt_table[(v >> (LIMB_BITS - 8)) - 64];
+        e = 1 - (e / 2);
+        if (v == 0) {
+            v = 128; /* real table value is 256 */
+            e++;
+        }
+        bf_resize(a, 1);
+        a->tab[0] = (v << (LIMB_BITS - 8)) &
+            limb_mask(LIMB_BITS - prec1, LIMB_BITS - 1);
+        a->expn = e;
+        a->sign = 0;
+    } else {
+        /* XXX: prove rounding */
+        __bf_rsqrt(a, x, (prec1 / 2) + 2);
+
+        prec = prec1 + 3;
+
+        /* x' = x + (x/2) * (1 - a * x^2) */
+        bf_init(a->ctx, &t0);
+
+        bf_mul(&t0, a, a, prec, BF_RNDF);
+        bf_mul(&t0, &t0, x, prec, BF_RNDF);
+        t0.sign ^= 1;
+        bf_add_si(&t0, &t0, 1, prec, BF_RNDF);
+        bf_mul(&t0, &t0, a, prec, BF_RNDF);
+        if (t0.len != 0)
+            t0.expn--;
+        bf_add(a, a, &t0, prec1, BF_RNDF);
+
+        bf_delete(&t0);
+    }
+}
+
+static int __bf_sqrt(bf_t *x, const bf_t *a, limb_t prec1, bf_flags_t flags)
+{
+    bf_t t0, t1;
+    limb_t prec;
+    int ret;
+    
+    /* XXX: prove rounding */
+    __bf_rsqrt(x, a, (prec1 / 2) + 2);
+    prec = prec1 + 3;
+
+    /* x' = a * x + (x/2) * (a - (a * x)^2) */
+    
+    bf_init(x->ctx, &t0);
+    bf_init(x->ctx, &t1);
+    bf_mul(&t1, x, a, prec, BF_RNDF);
+    bf_mul(&t0, &t1, &t1, prec, BF_RNDF);
+    t0.sign ^= 1;
+    bf_add(&t0, &t0, a, prec, BF_RNDF);
+    bf_mul(&t0, &t0, x, prec, BF_RNDF);
+    if (t0.len != 0)
+        t0.expn--;
+    ret = bf_add(x, &t1, &t0, prec1, flags);
+    
+    bf_delete(&t0);
+    bf_delete(&t1);
+    return ret;
+}
+
+/* Note: only faithful rounding is supported */
+void bf_rsqrt(bf_t *r, const bf_t *a, limb_t prec)
+{
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN ||
+            (a->sign && a->expn != BF_EXP_ZERO)) {
+            bf_set_nan(r);
+        } else if (a->expn == BF_EXP_INF) {
+            bf_set_zero(r, a->sign);
+        } else {
+            bf_set_inf(r, 0);
+        }
+    } else if (a->sign) {
+        bf_set_nan(r);
+    } else {
+        //        bf_print_str("a", a);
+        __bf_rsqrt(r, a, prec);
+    }
+}
+
+/* return floor(sqrt(a)) */
+static limb_t bf_isqrt(limb_t a)
+{
+    unsigned int l;
+    limb_t u, s;
+    
+    if (a == 0)
+        return 0;
+    l = ceil_log2(a);
+    u = (limb_t)1 << ((l + 1) / 2);
+    /* u >= floor(sqrt(a)) */
+    for(;;) {
+        s = u;
+        u = ((a / s) + s) / 2;
+        if (u >= s)
+            break;
+    }
+    return s;
+}
+
+/* Integer square root with remainder. 'a' must be an integer. r =
+   floor(sqrt(a)) and rem = a - r^2.  BF_ST_INEXACT is set if the result
+   is inexact. 'rem' can be NULL if the remainder is not needed. */
+int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a)
+{
+    int ret;
+    
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+        } else if (a->expn == BF_EXP_INF && a->sign) {
+            goto invalid_op;
+        } else {
+            bf_set(r, a);
+        }
+        if (rem1)
+            bf_set_ui(rem1, 0);
+        ret = 0;
+    } else if (a->sign) {
+ invalid_op:
+        bf_set_nan(r);
+        if (rem1)
+            bf_set_ui(rem1, 0);
+        ret = BF_ST_INVALID_OP;
+    } else {
+        bf_t rem_s, *rem;
+        int res;
+        
+        bf_sqrt(r, a, (a->expn + 1) / 2, BF_RNDF);
+        bf_rint(r, BF_PREC_INF, BF_RNDZ);
+        /* see if the result is exact by computing the remainder */
+        if (rem1) {
+            rem = rem1;
+        } else {
+            rem = &rem_s;
+            bf_init(r->ctx, rem);
+        }
+        bf_mul(rem, r, r, BF_PREC_INF, BF_RNDZ);
+        ret = 0;
+        if (rem1) {
+            bf_neg(rem);
+            bf_add(rem, rem, a, BF_PREC_INF, BF_RNDZ);
+            if (rem->len != 0) {
+                ret = BF_ST_INEXACT;
+                if (rem->sign) {
+                    bf_t a1_s, *a1 = &a1_s;
+                    bf_add_si(r, r, -1, BF_PREC_INF, BF_RNDZ);
+                    a1->tab = a->tab;
+                    a1->len = a->len;
+                    a1->sign = a->sign;
+                    a1->expn = a->expn + 1;
+                    bf_add(rem, rem, r, BF_PREC_INF, BF_RNDZ);
+                    bf_add_si(rem, rem, 1, BF_PREC_INF, BF_RNDZ);
+                }
+            } else {
+                ret = 0;
+            }
+        } else {
+            res = bf_cmpu(rem, a);
+            bf_delete(rem);
+            //            printf("res2=%d\n", res2);
+            if (res > 0) {
+                /* need to correct the result */
+                bf_add_si(r, r, -1, BF_PREC_INF, BF_RNDZ);
+            }
+            ret = (res != 0 ? BF_ST_INEXACT : 0);
+        }
+    }
+    return ret;
+}
+
+int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    int rnd_mode, ret;
+
+    assert(r != a);
+
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+        } else if (a->expn == BF_EXP_INF && a->sign) {
+            goto invalid_op;
+        } else {
+            bf_set(r, a);
+        }
+        ret = 0;
+    } else if (a->sign) {
+ invalid_op:
+        bf_set_nan(r);
+        ret = BF_ST_INVALID_OP;
+    } else {
+        rnd_mode = flags & BF_RND_MASK;
+        if (rnd_mode == BF_RNDF) {
+            ret = __bf_sqrt(r, a, prec, flags);
+        } else {
+            bf_t a1_s, *a1 = &a1_s;
+            slimb_t d, prec2;
+            int res1, res2;
+
+            bf_init(r->ctx, a1);
+            bf_set(a1, a);
+            /* convert the mantissa to an integer with at most 2 *
+               prec + 4 bits */
+            prec2 = prec + 2;
+            /* make '-a->expn + d' divisible by two */
+            d = prec2 * 2 - (a->expn & 1);
+            a1->expn = d;
+            res1 = bf_rint(a1, BF_PREC_INF, BF_RNDZ);
+            res2 = bf_sqrtrem(r, NULL, a1);
+            bf_delete(a1);
+            if ((res2 | res1) != 0) {
+                bf_or_one(r, (prec2 + LIMB_BITS - 1) / LIMB_BITS);
+            }
+            /* update the exponent */
+            r->expn -= (-a->expn + d) >> 1;
+            ret = bf_round(r, prec, flags);
+        }
+    }
+    return ret;
+}
+
+static no_inline int bf_op2(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                            bf_flags_t flags, bf_op2_func_t *func)
+{
+    bf_t tmp;
+    int ret;
+    
+    if (r == a || r == b) {
+        bf_init(r->ctx, &tmp);
+        ret = func(&tmp, a, b, prec, flags);
+        bf_move(r, &tmp);
+    } else {
+        ret = func(r, a, b, prec, flags);
+    }
+    return ret;
+}
+
+int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+            bf_flags_t flags)
+{
+    return bf_op2(r, a, b, prec, flags, __bf_add);
+}
+
+int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+            bf_flags_t flags)
+{
+    return bf_op2(r, a, b, prec, flags, __bf_sub);
+}
+
+int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+           bf_flags_t flags)
+{
+    return bf_op2(r, a, b, prec, flags, __bf_div);
+}
+
+int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec,
+               bf_flags_t flags)
+{
+    bf_t b;
+    int ret;
+    bf_init(r->ctx, &b);
+    bf_set_ui(&b, b1);
+    ret = bf_mul(r, a, &b, prec, flags);
+    bf_delete(&b);
+    return ret;
+}
+
+int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec,
+               bf_flags_t flags)
+{
+    bf_t b;
+    int ret;
+    bf_init(r->ctx, &b);
+    bf_set_si(&b, b1);
+    ret = bf_mul(r, a, &b, prec, flags);
+    bf_delete(&b);
+    return ret;
+}
+
+int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec,
+              bf_flags_t flags)
+{
+    bf_t b;
+    int ret;
+    
+    bf_init(r->ctx, &b);
+    bf_set_si(&b, b1);
+    ret = bf_add(r, a, &b, prec, flags);
+    bf_delete(&b);
+    return ret;
+}
+
+int bf_pow_ui(bf_t *r, const bf_t *a, limb_t b, limb_t prec,
+              bf_flags_t flags)
+{
+    int ret, n_bits, i;
+    
+    assert(r != a);
+    if (b == 0) {
+        bf_set_ui(r, 1);
+        return 0;
+    }
+    bf_set(r, a);
+    ret = 0;
+    n_bits = LIMB_BITS - clz(b);
+    for(i = n_bits - 2; i >= 0; i--) {
+        ret |= bf_mul(r, r, r, prec, flags);
+        if ((b >> i) & 1)
+            ret |= bf_mul(r, r, a, prec, flags);
+    }
+    return ret;
+}
+
+int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b, limb_t prec, bf_flags_t flags)
+{
+    bf_t a;
+    int ret;
+    
+    bf_init(r->ctx, &a);
+    bf_set_ui(&a, a1);
+    ret = bf_pow_ui(r, &a, b, prec, flags);
+    bf_delete(&a);
+    return ret;
+}
+
+/* convert to integer (single rounding) */
+int bf_rint(bf_t *r, limb_t prec, bf_flags_t flags)
+{
+    int ret;
+    if (r->len == 0)
+        return 0;
+    if (r->expn <= 0) {
+        ret = __bf_round(r, r->expn, flags & ~BF_FLAG_SUBNORMAL, r->len) &
+            ~BF_ST_UNDERFLOW;
+    } else {
+        ret = __bf_round(r, bf_min(r->expn, prec), flags, r->len);
+    }
+    return ret;
+}
+
+/* logical operations */
+#define BF_LOGIC_OR  0
+#define BF_LOGIC_XOR 1
+#define BF_LOGIC_AND 2
+
+static inline limb_t bf_logic_op1(limb_t a, limb_t b, int op)
+{
+    switch(op) {
+    case BF_LOGIC_OR:
+        return a | b;
+    case BF_LOGIC_XOR:
+        return a ^ b;
+    default:
+    case BF_LOGIC_AND:
+        return a & b;
+    }
+}
+
+static void bf_logic_op(bf_t *r, const bf_t *a1, const bf_t *b1, int op)
+{
+    bf_t b1_s, a1_s, *a, *b;
+    limb_t a_sign, b_sign, r_sign;
+    slimb_t l, i, a_bit_offset, b_bit_offset;
+    limb_t v1, v2, v1_mask, v2_mask, r_mask;
+    
+    assert(r != a1 && r != b1);
+
+    if (a1->expn <= 0)
+        a_sign = 0; /* minus zero is considered as positive */
+    else
+        a_sign = a1->sign;
+
+    if (b1->expn <= 0)
+        b_sign = 0; /* minus zero is considered as positive */
+    else
+        b_sign = b1->sign;
+    
+    if (a_sign) {
+        a = &a1_s;
+        bf_init(r->ctx, a);
+        bf_add_si(a, a1, 1, BF_PREC_INF, BF_RNDZ);
+    } else {
+        a = (bf_t *)a1;
+    }
+
+    if (b_sign) {
+        b = &b1_s;
+        bf_init(r->ctx, b);
+        bf_add_si(b, b1, 1, BF_PREC_INF, BF_RNDZ);
+    } else {
+        b = (bf_t *)b1;
+    }
+    
+    r_sign = bf_logic_op1(a_sign, b_sign, op);
+    if (op == BF_LOGIC_AND && r_sign == 0) {
+        /* no need to compute extra zeros for and */
+        if (a_sign == 0 && b_sign == 0)
+            l = bf_min(a->expn, b->expn);
+        else if (a_sign == 0)
+            l = a->expn;
+        else
+            l = b->expn;
+    } else {
+        l = bf_max(a->expn, b->expn);
+    }
+    /* Note: a or b can be zero */
+    l = (bf_max(l, 1) + LIMB_BITS - 1) / LIMB_BITS;
+    bf_resize(r, l);
+    a_bit_offset = a->len * LIMB_BITS - a->expn;
+    b_bit_offset = b->len * LIMB_BITS - b->expn;
+    v1_mask = -a_sign;
+    v2_mask = -b_sign;
+    r_mask = -r_sign;
+    for(i = 0; i < l; i++) {
+        v1 = get_bits(a->tab, a->len, a_bit_offset + i * LIMB_BITS) ^ v1_mask;
+        v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS) ^ v2_mask;
+        r->tab[i] = bf_logic_op1(v1, v2, op) ^ r_mask;
+    }
+    r->expn = l * LIMB_BITS;
+    r->sign = r_sign;
+    bf_normalize_and_round(r, BF_PREC_INF, BF_RNDZ);
+    if (r_sign)
+        bf_add_si(r, r, -1, BF_PREC_INF, BF_RNDZ);
+    if (a == &a1_s)
+        bf_delete(a);
+    if (b == &b1_s)
+        bf_delete(b);
+}
+
+/* 'a' and 'b' must be integers */
+void bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b)
+{
+    bf_logic_op(r, a, b, BF_LOGIC_OR);
+}
+
+/* 'a' and 'b' must be integers */
+void bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b)
+{
+    bf_logic_op(r, a, b, BF_LOGIC_XOR);
+}
+
+/* 'a' and 'b' must be integers */
+void bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b)
+{
+    bf_logic_op(r, a, b, BF_LOGIC_AND);
+}
+
+/* conversion between fixed size types */
+
+typedef union {
+    double d;
+    uint64_t u;
+} Float64Union;
+
+int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode)
+{
+    Float64Union u;
+    int e, ret;
+    uint64_t m;
+    
+    ret = 0;
+    if (a->expn == BF_EXP_NAN) {
+        u.u = 0x7ff8000000000000; /* quiet nan */
+    } else {
+        bf_t b_s, *b = &b_s;
+        
+        bf_init(a->ctx, b);
+        bf_set(b, a);
+        if (bf_is_finite(b)) {
+            ret = bf_round(b, 53, rnd_mode | BF_FLAG_SUBNORMAL | bf_set_exp_bits(11));
+        }
+        if (b->expn == BF_EXP_INF) {
+            e = (1 << 11) - 1;
+            m = 0;
+        } else if (b->expn == BF_EXP_ZERO) {
+            e = 0;
+            m = 0;
+        } else {
+            e = b->expn + 1023 - 1;
+#if LIMB_BITS == 32
+            if (b->len == 2) {
+                m = ((uint64_t)b->tab[1] << 32) | b->tab[0];
+            } else {
+                m = ((uint64_t)b->tab[0] << 32);
+            }
+#else
+            m = b->tab[0];
+#endif
+            if (e <= 0) {
+                /* subnormal */
+                m = m >> (12 - e);
+                e = 0;
+            } else {
+                m = (m << 1) >> 12;
+            }
+        }
+        u.u = m | ((uint64_t)e << 52) | ((uint64_t)b->sign << 63);
+        bf_delete(b);
+    }
+    *pres = u.d;
+    return ret;
+}
+
+void bf_set_float64(bf_t *a, double d)
+{
+    Float64Union u;
+    uint64_t m;
+    int shift, e, sgn;
+    
+    u.d = d;
+    sgn = u.u >> 63;
+    e = (u.u >> 52) & ((1 << 11) - 1);
+    m = u.u & (((uint64_t)1 << 52) - 1);
+    if (e == ((1 << 11) - 1)) {
+        if (m != 0) {
+            bf_set_nan(a);
+        } else {
+            bf_set_inf(a, sgn);
+        }
+    } else if (e == 0) {
+        if (m == 0) {
+            bf_set_zero(a, sgn);
+        } else {
+            /* subnormal number */
+            m <<= 12;
+            shift = clz64(m);
+            m <<= shift;
+            e = -shift;
+            goto norm;
+        }
+    } else {
+        m = (m << 11) | ((uint64_t)1 << 63);
+    norm:
+        a->expn = e - 1023 + 1;
+#if LIMB_BITS == 32
+        bf_resize(a, 2);
+        a->tab[0] = m;
+        a->tab[1] = m >> 32;
+#else
+        bf_resize(a, 1);
+        a->tab[0] = m;
+#endif
+        a->sign = sgn;
+    }
+}
+
+/* The rounding mode is always BF_RNDZ. Return BF_ST_OVERFLOW if there
+   is an overflow and 0 otherwise. */
+int bf_get_int32(int *pres, const bf_t *a, int flags)
+{
+    uint32_t v;
+    int ret;
+    if (a->expn >= BF_EXP_INF) {
+        ret = 0;
+        if (flags & BF_GET_INT_MOD) {
+            v = 0;
+        } else if (a->expn == BF_EXP_INF) {
+            v = (uint32_t)INT32_MAX + a->sign;
+        } else {
+            v = INT32_MAX;
+        }
+    } else if (a->expn <= 0) {
+        v = 0;
+        ret = 0;
+    } else if (a->expn <= 31) {
+        v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn);
+        if (a->sign)
+            v = -v;
+        ret = 0;
+    } else if (!(flags & BF_GET_INT_MOD)) {
+        ret = BF_ST_OVERFLOW;
+        if (a->sign) {
+            v = (uint32_t)INT32_MAX + 1;
+            if (a->expn == 32 && 
+                (a->tab[a->len - 1] >> (LIMB_BITS - 32)) == v) {
+                ret = 0;
+            }
+        } else {
+            v = INT32_MAX;
+        }
+    } else {
+        v = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); 
+        if (a->sign)
+            v = -v;
+        ret = 0;
+    }
+    *pres = v;
+    return ret;
+}
+
+/* The rounding mode is always BF_RNDZ. Return BF_ST_OVERFLOW if there
+   is an overflow and 0 otherwise. */
+int bf_get_int64(int64_t *pres, const bf_t *a, int flags)
+{
+    uint64_t v;
+    int ret;
+    if (a->expn >= BF_EXP_INF) {
+        ret = 0;
+        if (flags & BF_GET_INT_MOD) {
+            v = 0;
+        } else if (a->expn == BF_EXP_INF) {
+            v = (uint64_t)INT64_MAX + a->sign;
+        } else {
+            v = INT64_MAX;
+        }
+    } else if (a->expn <= 0) {
+        v = 0;
+        ret = 0;
+    } else if (a->expn <= 63) {
+#if LIMB_BITS == 32
+        if (a->expn <= 32)
+            v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn);
+        else
+            v = (((uint64_t)a->tab[a->len - 1] << 32) |
+                 get_limbz(a, a->len - 2)) >> (64 - a->expn);
+#else
+        v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn);
+#endif
+        if (a->sign)
+            v = -v;
+        ret = 0;
+    } else if (!(flags & BF_GET_INT_MOD)) {
+        ret = BF_ST_OVERFLOW;
+        if (a->sign) {
+            uint64_t v1;
+            v = (uint64_t)INT64_MAX + 1;
+            if (a->expn == 64) {
+                v1 = a->tab[a->len - 1];
+#if LIMB_BITS == 32
+                v1 |= (v1 << 32) | get_limbz(a, a->len - 2);
+#endif
+                if (v1 == v)
+                    ret = 0;
+            }
+        } else {
+            v = INT64_MAX;
+        }
+    } else {
+        slimb_t bit_pos = a->len * LIMB_BITS - a->expn;
+        v = get_bits(a->tab, a->len, bit_pos); 
+#if LIMB_BITS == 32
+        v |= (uint64_t)get_bits(a->tab, a->len, bit_pos + 32) << 32;
+#endif
+        if (a->sign)
+            v = -v;
+        ret = 0;
+    }
+    *pres = v;
+    return ret;
+}
+
+/* base conversion from radix */
+
+static const uint8_t digits_per_limb_table[BF_RADIX_MAX - 1] = {
+#if LIMB_BITS == 32
+32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+#else
+64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12,
+#endif
+};
+
+static limb_t get_limb_radix(int radix)
+{
+    int i, k;
+    limb_t radixl;
+    
+    k = digits_per_limb_table[radix - 2];
+    radixl = radix;
+    for(i = 1; i < k; i++)
+        radixl *= radix;
+    return radixl;
+}
+
+static void bf_integer_from_radix_rec(bf_t *r, const limb_t *tab,
+                                      limb_t n, int level, limb_t n0,
+                                      limb_t radix, bf_t *pow_tab)
+{
+    if (n == 1) {
+        bf_set_ui(r, tab[0]);
+    } else {
+        bf_t T_s, *T = &T_s, *B;
+        limb_t n1, n2;
+        
+        n2 = (((n0 * 2) >> (level + 1)) + 1) / 2;
+        n1 = n - n2;
+        //        printf("level=%d n0=%ld n1=%ld n2=%ld\n", level, n0, n1, n2);
+        B = &pow_tab[level];
+        if (B->len == 0) {
+            bf_pow_ui_ui(B, radix, n2, BF_PREC_INF, BF_RNDZ);
+        }
+        bf_integer_from_radix_rec(r, tab + n2, n1, level + 1, n0,
+                                  radix, pow_tab);
+        bf_mul(r, r, B, BF_PREC_INF, BF_RNDZ);
+        bf_init(r->ctx, T);
+        bf_integer_from_radix_rec(T, tab, n2, level + 1, n0,
+                                  radix, pow_tab);
+        bf_add(r, r, T, BF_PREC_INF, BF_RNDZ);
+        bf_delete(T);
+    }
+    //    bf_print_str("  r=", r);
+}
+
+static void bf_integer_from_radix(bf_t *r, const limb_t *tab,
+                                 limb_t n, limb_t radix)
+{
+    bf_context_t *s = r->ctx;
+    int pow_tab_len, i;
+    limb_t radixl;
+    bf_t *pow_tab;
+    
+    radixl = get_limb_radix(radix);
+    pow_tab_len = ceil_log2(n) + 2; /* XXX: check */
+    pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len);
+    for(i = 0; i < pow_tab_len; i++)
+        bf_init(r->ctx, &pow_tab[i]);
+    bf_integer_from_radix_rec(r, tab, n, 0, n, radixl, pow_tab);
+    for(i = 0; i < pow_tab_len; i++) {
+        bf_delete(&pow_tab[i]);
+    }
+    bf_free(s, pow_tab);
+}
+
+/* compute and round T * radix^expn. */
+int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix,
+                     slimb_t expn, limb_t prec, bf_flags_t flags)
+{
+    int ret, expn_sign, overflow;
+    slimb_t e, extra_bits, prec1, ziv_extra_bits;
+    bf_t B_s, *B = &B_s;
+
+    if (T->len == 0) {
+        bf_set(r, T);
+        return 0;
+    } else if (expn == 0) {
+        bf_set(r, T);
+        return bf_round(r, prec, flags);
+    }
+
+    e = expn;
+    expn_sign = 0;
+    if (e < 0) {
+        e = -e;
+        expn_sign = 1;
+    }
+    bf_init(r->ctx, B);
+    if (prec == BF_PREC_INF) {
+        /* infinite precision: only used if the result is known to be exact */
+        bf_pow_ui_ui(B, radix, e, BF_PREC_INF, BF_RNDN);
+        if (expn_sign) {
+            ret = bf_div(r, T, B, T->len * LIMB_BITS, BF_RNDN);
+        } else {
+            ret = bf_mul(r, T, B, BF_PREC_INF, BF_RNDN);
+        }
+    } else {
+        ziv_extra_bits = 16;
+        for(;;) {
+            prec1 = prec + ziv_extra_bits;
+            /* XXX: correct overflow/underflow handling */
+            /* XXX: rigorous error analysis needed */
+            extra_bits = ceil_log2(e) * 2 + 1;
+            ret = bf_pow_ui_ui(B, radix, e, prec1 + extra_bits, BF_RNDN);
+            overflow = !bf_is_finite(B);
+            /* XXX: if bf_pow_ui_ui returns an exact result, can stop
+               after the next operation */
+            if (expn_sign)
+                ret |= bf_div(r, T, B, prec1 + extra_bits, BF_RNDN);
+            else
+                ret |= bf_mul(r, T, B, prec1 + extra_bits, BF_RNDN);
+            if ((ret & BF_ST_INEXACT) &&
+                !bf_can_round(r, prec, flags & BF_RND_MASK, prec1) &&
+                !overflow) {
+                /* and more precision and retry */
+                ziv_extra_bits = ziv_extra_bits  + (ziv_extra_bits / 2);
+            } else {
+                ret = bf_round(r, prec, flags) | (ret & BF_ST_INEXACT);
+                break;
+            }
+        }
+    }
+    bf_delete(B);
+    return ret;
+}
+
+static inline int to_digit(int c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    else if (c >= 'A' && c <= 'Z')
+        return c - 'A' + 10;
+    else if (c >= 'a' && c <= 'z')
+        return c - 'a' + 10;
+    else
+        return 36;
+}
+
+/* add a limb at 'pos' and decrement pos. new space is created if needed */
+static void bf_add_limb(bf_t *a, slimb_t *ppos, limb_t v)
+{
+    slimb_t pos;
+    pos = *ppos;
+    if (unlikely(pos < 0)) {
+        limb_t new_size, d;
+        new_size = bf_max(a->len + 1, a->len * 3 / 2);
+        a->tab = bf_realloc(a->ctx, a->tab, sizeof(limb_t) * new_size);
+        d = new_size - a->len;
+        memmove(a->tab + d, a->tab, a->len * sizeof(limb_t));
+        a->len = new_size;
+        pos += d;
+    }
+    a->tab[pos--] = v;
+    *ppos = pos;
+}
+
+static int bf_tolower(int c)
+{
+    if (c >= 'A' && c <= 'Z')
+        c = c - 'A' + 'a';
+    return c;
+}
+
+static int strcasestart(const char *str, const char *val, const char **ptr)
+{
+    const char *p, *q;
+    p = str;
+    q = val;
+    while (*q != '\0') {
+        if (bf_tolower(*p) != *q)
+            return 0;
+        p++;
+        q++;
+    }
+    if (ptr)
+        *ptr = p;
+    return 1;
+}
+
+/* 
+   Return (status, n, exp). 'status' is the floating point status. 'n'
+   is the parsed number. 
+   If prec = BF_PREC_INF:
+      If the number is an integer or if the radix is a power of two,
+      *pexponent = 0. 
+      Otherwise, '*pexponent' is the exponent in radix 'radix'.
+   Otherwise
+      *pexponent = 0
+*/
+int bf_atof2(bf_t *r, slimb_t *pexponent,
+             const char *str, const char **pnext, int radix,
+             limb_t prec, bf_flags_t flags)
+{
+    const char *p, *p_start;
+    int is_neg, radix_bits, exp_is_neg, ret, digits_per_limb, shift, sep;
+    int ret_legacy_octal = 0;
+    limb_t cur_limb;
+    slimb_t pos, expn, int_len, digit_count;
+    BOOL has_decpt, is_bin_exp, is_float;
+    bf_t a_s, *a;
+    
+    /* optional separator between digits */
+    sep = (flags & BF_ATOF_UNDERSCORE_SEP) ? '_' : 256;
+    
+    *pexponent = 0;
+    p = str;
+    if (!(flags & (BF_ATOF_INT_ONLY | BF_ATOF_JS_QUIRKS)) &&
+        radix <= 16 &&
+        strcasestart(p, "nan", &p)) {
+        bf_set_nan(r);
+        ret = 0;
+        goto done;
+    }
+    is_neg = 0;
+    
+    if (p[0] == '+') {
+        p++;
+        p_start = p;
+        if (flags & BF_ATOF_NO_PREFIX_AFTER_SIGN)
+            goto no_radix_prefix;
+    } else if (p[0] == '-') {
+        is_neg = 1;
+        p++;
+        p_start = p;
+        if (flags & BF_ATOF_NO_PREFIX_AFTER_SIGN)
+            goto no_radix_prefix;
+    } else {
+        p_start = p;
+    }
+    if (p[0] == '0') {
+        if ((p[1] == 'x' || p[1] == 'X') &&
+            (radix == 0 || radix == 16) &&
+            !(flags & BF_ATOF_NO_HEX)) {
+            radix = 16;
+            p += 2;
+        } else if ((p[1] == 'o' || p[1] == 'O') &&
+                   radix == 0 && (flags & BF_ATOF_BIN_OCT)) {
+            p += 2;
+            radix = 8;
+        } else if ((p[1] == 'b' || p[1] == 'B') &&
+                   radix == 0 && (flags & BF_ATOF_BIN_OCT)) {
+            p += 2;
+            radix = 2;
+        } else if ((p[1] >= '0' && p[1] <= '9') &&
+                   radix == 0 && (flags & BF_ATOF_LEGACY_OCTAL)) {
+            int i;
+            ret_legacy_octal = BF_ATOF_ST_LEGACY_OCTAL;
+            /* the separator is not allowed in legacy octal literals */
+            sep = 256;
+            for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++)
+                continue;
+            if (p[i] == '8' || p[i] == '9')
+                goto no_prefix;
+            p += 1;
+            radix = 8;
+        } else {
+            /* 0 cannot be followed by a separator */
+            if (p[1] == sep) {
+                p++;
+                bf_set_zero(r, 0);
+                ret = 0;
+                if (flags & BF_ATOF_INT_PREC_INF)
+                    ret |= BF_ATOF_ST_INTEGER;
+                goto done;
+            }
+            goto no_prefix;
+        }
+        /* there must be a digit after the prefix */
+        if (to_digit((uint8_t)*p) >= radix) {
+            bf_set_nan(r);
+            ret = 0;
+            goto done;
+        }
+    no_prefix: ;
+    } else {
+    no_radix_prefix:
+        if (!(flags & BF_ATOF_INT_ONLY) && radix <= 16 &&
+            ((!(flags & BF_ATOF_JS_QUIRKS) && strcasestart(p, "inf", &p)) ||
+             ((flags & BF_ATOF_JS_QUIRKS) && strstart(p, "Infinity", &p)))) {
+            bf_set_inf(r, is_neg);
+            ret = 0;
+            goto done;
+        }
+    }
+    
+    if (radix == 0)
+        radix = 10;
+    if ((radix & (radix - 1)) != 0) {
+        radix_bits = 0; /* base is not a power of two */
+        a = &a_s;
+        bf_init(r->ctx, a);
+    } else {
+        radix_bits = ceil_log2(radix);
+        a = r;
+    }
+
+    /* skip leading zeros */
+    /* XXX: could also skip zeros after the decimal point */
+    while (*p == '0' || (*p == sep && to_digit(p[1]) < radix))
+        p++;
+
+    if (radix_bits) {
+        shift = digits_per_limb = LIMB_BITS;
+    } else {
+        radix_bits = 0;
+        shift = digits_per_limb = digits_per_limb_table[radix - 2];
+    }
+    cur_limb = 0;
+    bf_resize(a, 1);
+    pos = 0;
+    has_decpt = FALSE;
+    int_len = digit_count = 0;
+    is_float = FALSE;
+    for(;;) {
+        limb_t c;
+        if (*p == '.' && (p > p_start || to_digit(p[1]) < radix)) {
+            if ((flags & BF_ATOF_INT_ONLY) ||
+                (radix != 10 && (flags & BF_ATOF_ONLY_DEC_FLOAT)))
+                break;
+            if (has_decpt)
+                break;
+            is_float = TRUE;
+            has_decpt = TRUE;
+            int_len = digit_count;
+            p++;
+        }
+        if (*p == sep && to_digit(p[1]) < radix)
+            p++;
+        c = to_digit(*p);
+        if (c >= radix)
+            break;
+        digit_count++;
+        p++;
+        if (radix_bits) {
+            shift -= radix_bits;
+            if (shift <= 0) {
+                cur_limb |= c >> (-shift);
+                bf_add_limb(a, &pos, cur_limb);
+                if (shift < 0)
+                    cur_limb = c << (LIMB_BITS + shift);
+                else
+                    cur_limb = 0;
+                shift += LIMB_BITS;
+            } else {
+                cur_limb |= c << shift;
+            }
+        } else {
+            cur_limb = cur_limb * radix + c;
+            shift--;
+            if (shift == 0) {
+                bf_add_limb(a, &pos, cur_limb);
+                shift = digits_per_limb;
+                cur_limb = 0;
+            }
+        }
+    }
+    if (!has_decpt)
+        int_len = digit_count;
+
+    /* add the last limb and pad with zeros */
+    if (shift != digits_per_limb) {
+        if (radix_bits == 0) {
+            while (shift != 0) {
+                cur_limb *= radix;
+                shift--;
+            }
+        }
+        bf_add_limb(a, &pos, cur_limb);
+    }
+            
+    /* reset the next limbs to zero (we prefer to reallocate in the
+       renormalization) */
+    memset(a->tab, 0, (pos + 1) * sizeof(limb_t));
+
+    if (p == p_start)
+        goto error;
+
+    /* parse the exponent, if any */
+    expn = 0;
+    is_bin_exp = FALSE;
+    if (!(flags & BF_ATOF_INT_ONLY) && 
+        !(radix != 10 && (flags & BF_ATOF_ONLY_DEC_FLOAT)) &&
+        ((radix == 10 && (*p == 'e' || *p == 'E')) ||
+         (radix != 10 && (*p == '@' ||
+                          (radix_bits && (*p == 'p' || *p == 'P'))))) &&
+        p > p_start) {
+        is_bin_exp = (*p == 'p' || *p == 'P');
+        p++;
+        is_float = TRUE;
+        exp_is_neg = 0;
+        if (*p == '+') {
+            p++;
+        } else if (*p == '-') {
+            exp_is_neg = 1;
+            p++;
+        }
+        for(;;) {
+            int c;
+            if (*p == sep && to_digit(p[1]) < 10)
+                p++;
+            c = to_digit(*p);
+            if (c >= 10)
+                break;
+            if (unlikely(expn > ((EXP_MAX - 2 - 9) / 10))) {
+                /* exponent overflow */
+                if (exp_is_neg) {
+                    bf_set_zero(r, is_neg);
+                    ret = BF_ST_UNDERFLOW | BF_ST_INEXACT;
+                } else {
+                    bf_set_inf(r, is_neg);
+                    ret = BF_ST_OVERFLOW | BF_ST_INEXACT;
+                }
+                goto done;
+            }
+            p++;
+            expn = expn * 10 + c;
+        }
+        if (exp_is_neg)
+            expn = -expn;
+    } else if (!is_float) {
+        if (*p == 'n' && (flags & BF_ATOF_INT_N_SUFFIX)) {
+            p++;
+            prec = BF_PREC_INF;
+        } else if (flags & BF_ATOF_INT_PREC_INF) {
+            prec = BF_PREC_INF;
+        } else {
+            is_float = TRUE;
+        }
+    }
+    if (radix_bits) {
+        /* XXX: may overflow */
+        if (!is_bin_exp)
+            expn *= radix_bits; 
+        a->expn = expn + (int_len * radix_bits);
+        a->sign = is_neg;
+        ret = bf_normalize_and_round(a, prec, flags);
+    } else {
+        limb_t l;
+        pos++;
+        l = a->len - pos; /* number of limbs */
+        if (l == 0) {
+            bf_set_zero(r, is_neg);
+            ret = 0;
+        } else {
+            bf_t T_s, *T = &T_s;
+
+            expn -= l * digits_per_limb - int_len;
+            bf_init(r->ctx, T);
+            bf_integer_from_radix(T, a->tab + pos, l, radix);
+            T->sign = is_neg;
+            if (prec == BF_PREC_INF && is_float) {
+                /* return the exponent */
+                *pexponent = expn;
+                bf_set(r, T);
+                ret = 0;
+            } else {
+                ret = bf_mul_pow_radix(r, T, radix, expn, prec, flags);
+            }
+            bf_delete(T);
+        }
+        bf_delete(a);
+    }
+    if (!is_float)
+        ret |= BF_ATOF_ST_INTEGER;
+ done:
+    if (pnext)
+        *pnext = p;
+    return ret | ret_legacy_octal;
+ error:
+    if (!radix_bits)
+        bf_delete(a);
+    ret = 0;
+    if (flags & BF_ATOF_NAN_IF_EMPTY)  {
+        bf_set_nan(r);
+    } else {
+        bf_set_zero(r, 0);
+        if (flags & BF_ATOF_INT_PREC_INF)
+            ret |= BF_ATOF_ST_INTEGER;
+    }
+    goto done;
+}
+
+int bf_atof(bf_t *r, const char *str, const char **pnext, int radix,
+            limb_t prec, bf_flags_t flags)
+{
+    slimb_t dummy_exp;
+    return bf_atof2(r, &dummy_exp, str, pnext, radix, prec, flags);
+}
+
+/* base conversion to radix */
+
+#if LIMB_BITS == 64
+#define RADIXL_10 UINT64_C(10000000000000000000)
+#else
+#define RADIXL_10 UINT64_C(1000000000)
+#endif
+
+static const uint32_t inv_log2_radix[BF_RADIX_MAX - 1][LIMB_BITS / 2 + 1] = {
+#if LIMB_BITS == 32
+{ 0x80000000, 0x00000000,},
+{ 0x50c24e60, 0xd4d4f4a7,},
+{ 0x40000000, 0x00000000,},
+{ 0x372068d2, 0x0a1ee5ca,},
+{ 0x3184648d, 0xb8153e7a,},
+{ 0x2d983275, 0x9d5369c4,},
+{ 0x2aaaaaaa, 0xaaaaaaab,},
+{ 0x28612730, 0x6a6a7a54,},
+{ 0x268826a1, 0x3ef3fde6,},
+{ 0x25001383, 0xbac8a744,},
+{ 0x23b46706, 0x82c0c709,},
+{ 0x229729f1, 0xb2c83ded,},
+{ 0x219e7ffd, 0xa5ad572b,},
+{ 0x20c33b88, 0xda7c29ab,},
+{ 0x20000000, 0x00000000,},
+{ 0x1f50b57e, 0xac5884b3,},
+{ 0x1eb22cc6, 0x8aa6e26f,},
+{ 0x1e21e118, 0x0c5daab2,},
+{ 0x1d9dcd21, 0x439834e4,},
+{ 0x1d244c78, 0x367a0d65,},
+{ 0x1cb40589, 0xac173e0c,},
+{ 0x1c4bd95b, 0xa8d72b0d,},
+{ 0x1bead768, 0x98f8ce4c,},
+{ 0x1b903469, 0x050f72e5,},
+{ 0x1b3b433f, 0x2eb06f15,},
+{ 0x1aeb6f75, 0x9c46fc38,},
+{ 0x1aa038eb, 0x0e3bfd17,},
+{ 0x1a593062, 0xb38d8c56,},
+{ 0x1a15f4c3, 0x2b95a2e6,},
+{ 0x19d630dc, 0xcc7ddef9,},
+{ 0x19999999, 0x9999999a,},
+{ 0x195fec80, 0x8a609431,},
+{ 0x1928ee7b, 0x0b4f22f9,},
+{ 0x18f46acf, 0x8c06e318,},
+{ 0x18c23246, 0xdc0a9f3d,},
+#else
+{ 0x80000000, 0x00000000, 0x00000000,},
+{ 0x50c24e60, 0xd4d4f4a7, 0x021f57bc,},
+{ 0x40000000, 0x00000000, 0x00000000,},
+{ 0x372068d2, 0x0a1ee5ca, 0x19ea911b,},
+{ 0x3184648d, 0xb8153e7a, 0x7fc2d2e1,},
+{ 0x2d983275, 0x9d5369c4, 0x4dec1661,},
+{ 0x2aaaaaaa, 0xaaaaaaaa, 0xaaaaaaab,},
+{ 0x28612730, 0x6a6a7a53, 0x810fabde,},
+{ 0x268826a1, 0x3ef3fde6, 0x23e2566b,},
+{ 0x25001383, 0xbac8a744, 0x385a3349,},
+{ 0x23b46706, 0x82c0c709, 0x3f891718,},
+{ 0x229729f1, 0xb2c83ded, 0x15fba800,},
+{ 0x219e7ffd, 0xa5ad572a, 0xe169744b,},
+{ 0x20c33b88, 0xda7c29aa, 0x9bddee52,},
+{ 0x20000000, 0x00000000, 0x00000000,},
+{ 0x1f50b57e, 0xac5884b3, 0x70e28eee,},
+{ 0x1eb22cc6, 0x8aa6e26f, 0x06d1a2a2,},
+{ 0x1e21e118, 0x0c5daab1, 0x81b4f4bf,},
+{ 0x1d9dcd21, 0x439834e3, 0x81667575,},
+{ 0x1d244c78, 0x367a0d64, 0xc8204d6d,},
+{ 0x1cb40589, 0xac173e0c, 0x3b7b16ba,},
+{ 0x1c4bd95b, 0xa8d72b0d, 0x5879f25a,},
+{ 0x1bead768, 0x98f8ce4c, 0x66cc2858,},
+{ 0x1b903469, 0x050f72e5, 0x0cf5488e,},
+{ 0x1b3b433f, 0x2eb06f14, 0x8c89719c,},
+{ 0x1aeb6f75, 0x9c46fc37, 0xab5fc7e9,},
+{ 0x1aa038eb, 0x0e3bfd17, 0x1bd62080,},
+{ 0x1a593062, 0xb38d8c56, 0x7998ab45,},
+{ 0x1a15f4c3, 0x2b95a2e6, 0x46aed6a0,},
+{ 0x19d630dc, 0xcc7ddef9, 0x5aadd61b,},
+{ 0x19999999, 0x99999999, 0x9999999a,},
+{ 0x195fec80, 0x8a609430, 0xe1106014,},
+{ 0x1928ee7b, 0x0b4f22f9, 0x5f69791d,},
+{ 0x18f46acf, 0x8c06e318, 0x4d2aeb2c,},
+{ 0x18c23246, 0xdc0a9f3d, 0x3fe16970,},
+#endif
+};
+
+static const limb_t log2_radix[BF_RADIX_MAX - 1] = {
+#if LIMB_BITS == 32
+0x20000000,
+0x32b80347,
+0x40000000,
+0x4a4d3c26,
+0x52b80347,
+0x59d5d9fd,
+0x60000000,
+0x6570068e,
+0x6a4d3c26,
+0x6eb3a9f0,
+0x72b80347,
+0x766a008e,
+0x79d5d9fd,
+0x7d053f6d,
+0x80000000,
+0x82cc7edf,
+0x8570068e,
+0x87ef05ae,
+0x8a4d3c26,
+0x8c8ddd45,
+0x8eb3a9f0,
+0x90c10501,
+0x92b80347,
+0x949a784c,
+0x966a008e,
+0x982809d6,
+0x99d5d9fd,
+0x9b74948f,
+0x9d053f6d,
+0x9e88c6b3,
+0xa0000000,
+0xa16bad37,
+0xa2cc7edf,
+0xa4231623,
+0xa570068e,
+#else
+0x2000000000000000,
+0x32b803473f7ad0f4,
+0x4000000000000000,
+0x4a4d3c25e68dc57f,
+0x52b803473f7ad0f4,
+0x59d5d9fd5010b366,
+0x6000000000000000,
+0x6570068e7ef5a1e8,
+0x6a4d3c25e68dc57f,
+0x6eb3a9f01975077f,
+0x72b803473f7ad0f4,
+0x766a008e4788cbcd,
+0x79d5d9fd5010b366,
+0x7d053f6d26089673,
+0x8000000000000000,
+0x82cc7edf592262d0,
+0x8570068e7ef5a1e8,
+0x87ef05ae409a0289,
+0x8a4d3c25e68dc57f,
+0x8c8ddd448f8b845a,
+0x8eb3a9f01975077f,
+0x90c10500d63aa659,
+0x92b803473f7ad0f4,
+0x949a784bcd1b8afe,
+0x966a008e4788cbcd,
+0x982809d5be7072dc,
+0x99d5d9fd5010b366,
+0x9b74948f5532da4b,
+0x9d053f6d26089673,
+0x9e88c6b3626a72aa,
+0xa000000000000000,
+0xa16bad3758efd873,
+0xa2cc7edf592262d0,
+0xa4231623369e78e6,
+0xa570068e7ef5a1e8,
+#endif
+};
+
+/* compute floor(a*b) or ceil(a*b) with b = log2(radix) or
+   b=1/log2(radix). For is_inv = 0, strict accuracy is not guaranteed
+   when radix is not a power of two. */
+slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv,
+                          int is_ceil1)
+{
+    int is_neg;
+    limb_t a;
+    BOOL is_ceil;
+
+    is_ceil = is_ceil1;
+    a = a1;
+    if (a1 < 0) {
+        a = -a;
+        is_neg = 1;
+    } else {
+        is_neg = 0;
+    }
+    is_ceil ^= is_neg;
+    if ((radix & (radix - 1)) == 0) {
+        int radix_bits;
+        /* radix is a power of two */
+        radix_bits = ceil_log2(radix);
+        if (is_inv) {
+            if (is_ceil)
+                a += radix_bits - 1;
+            a = a / radix_bits;
+        } else {
+            a = a * radix_bits;
+        }
+    } else {
+        const uint32_t *tab;
+        limb_t b0, b1;
+        dlimb_t t;
+        
+        if (is_inv) {
+            tab = inv_log2_radix[radix - 2];
+#if LIMB_BITS == 32
+            b1 = tab[0];
+            b0 = tab[1];
+#else
+            b1 = ((limb_t)tab[0] << 32) | tab[1];
+            b0 = (limb_t)tab[2] << 32;
+#endif
+            t = (dlimb_t)b0 * (dlimb_t)a;
+            t = (dlimb_t)b1 * (dlimb_t)a + (t >> LIMB_BITS);
+            a = t >> (LIMB_BITS - 1);
+        } else {
+            b0 = log2_radix[radix - 2];
+            t = (dlimb_t)b0 * (dlimb_t)a;
+            a = t >> (LIMB_BITS - 3);
+        }
+        /* a = floor(result) and 'result' cannot be an integer */
+        a += is_ceil;
+    }
+    if (is_neg)
+        a = -a;
+    return a;
+}
+
+/* 'n' is the number of output limbs */
+static void bf_integer_to_radix_rec(bf_t *pow_tab,
+                                    limb_t *out, const bf_t *a, limb_t n,
+                                    int level, limb_t n0, limb_t radixl,
+                                    unsigned int radixl_bits)
+{
+    limb_t n1, n2, q_prec;
+    assert(n >= 1);
+    if (n == 1) {
+        out[0] = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn);
+    } else if (n == 2) {
+        dlimb_t t;
+        slimb_t pos;
+        pos = a->len * LIMB_BITS - a->expn;
+        t = ((dlimb_t)get_bits(a->tab, a->len, pos + LIMB_BITS) << LIMB_BITS) |
+            get_bits(a->tab, a->len, pos);
+        if (likely(radixl == RADIXL_10)) {
+            /* use division by a constant when possible */
+            out[0] = t % RADIXL_10;
+            out[1] = t / RADIXL_10;
+        } else {
+            out[0] = t % radixl;
+            out[1] = t / radixl;
+        }
+    } else {
+        bf_t Q, R, *B, *B_inv;
+        int q_add;
+        bf_init(a->ctx, &Q);
+        bf_init(a->ctx, &R);
+        n2 = (((n0 * 2) >> (level + 1)) + 1) / 2;
+        n1 = n - n2;
+        B = &pow_tab[2 * level];
+        B_inv = &pow_tab[2 * level + 1];
+        if (B->len == 0) {
+            /* compute BASE^n2 */
+            bf_pow_ui_ui(B, radixl, n2, BF_PREC_INF, BF_RNDZ);
+            /* we use enough bits for the maximum possible 'n1' value,
+               i.e. n2 + 1 */
+            bf_recip(B_inv, B, (n2 + 1) * radixl_bits + 2);
+        }
+        //        printf("%d: n1=% " PRId64 " n2=%" PRId64 "\n", level, n1, n2);
+        q_prec = n1 * radixl_bits;
+        bf_mul(&Q, a, B_inv, q_prec, BF_RNDN);
+        bf_rint(&Q, BF_PREC_INF, BF_RNDZ);
+        
+        bf_mul(&R, &Q, B, BF_PREC_INF, BF_RNDZ);
+        bf_sub(&R, a, &R, BF_PREC_INF, BF_RNDZ);
+        /* adjust if necessary */
+        q_add = 0;
+        while (R.sign && R.len != 0) {
+            bf_add(&R, &R, B, BF_PREC_INF, BF_RNDZ);
+            q_add--;
+        }
+        while (bf_cmpu(&R, B) >= 0) {
+            bf_sub(&R, &R, B, BF_PREC_INF, BF_RNDZ);
+            q_add++;
+        }
+        if (q_add != 0) {
+            bf_add_si(&Q, &Q, q_add, BF_PREC_INF, BF_RNDZ);
+        }
+        bf_integer_to_radix_rec(pow_tab, out + n2, &Q, n1, level + 1, n0,
+                                radixl, radixl_bits);
+        bf_integer_to_radix_rec(pow_tab, out, &R, n2, level + 1, n0,
+                                radixl, radixl_bits);
+        bf_delete(&Q);
+        bf_delete(&R);
+    }
+}
+
+static void bf_integer_to_radix(bf_t *r, const bf_t *a, limb_t radixl)
+{
+    bf_context_t *s = r->ctx;
+    limb_t r_len;
+    bf_t *pow_tab;
+    int i, pow_tab_len;
+    
+    r_len = r->len;
+    pow_tab_len = (ceil_log2(r_len) + 2) * 2; /* XXX: check */
+    pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len);
+    for(i = 0; i < pow_tab_len; i++)
+        bf_init(r->ctx, &pow_tab[i]);
+
+    bf_integer_to_radix_rec(pow_tab, r->tab, a, r_len, 0, r_len, radixl,
+                            ceil_log2(radixl));
+
+    for(i = 0; i < pow_tab_len; i++) {
+        bf_delete(&pow_tab[i]);
+    }
+    bf_free(s, pow_tab);
+}
+
+/* a must be >= 0. 'P' is the wanted number of digits in radix
+   'radix'. 'r' is the mantissa represented as an integer. *pE
+   contains the exponent. */
+static void bf_convert_to_radix(bf_t *r, slimb_t *pE,
+                                const bf_t *a, int radix,
+                                limb_t P, bf_rnd_t rnd_mode,
+                                BOOL is_fixed_exponent)
+{
+    slimb_t E, e, prec, extra_bits, ziv_extra_bits, prec0;
+    bf_t B_s, *B = &B_s;
+    int e_sign, ret, res;
+    
+    if (a->len == 0) {
+        /* zero case */
+        *pE = 0;
+        bf_set(r, a);
+        return;
+    }
+
+    if (is_fixed_exponent) {
+        E = *pE;
+    } else {
+        /* compute the new exponent */
+        E = 1 + bf_mul_log2_radix(a->expn - 1, radix, TRUE, FALSE);
+    }
+    //    bf_print_str("a", a);
+    //    printf("E=%ld P=%ld radix=%d\n", E, P, radix);
+    
+    for(;;) {
+        e = P - E;
+        e_sign = 0;
+        if (e < 0) {
+            e = -e;
+            e_sign = 1;
+        }
+        /* Note: precision for log2(radix) is not critical here */
+        prec0 = bf_mul_log2_radix(P, radix, FALSE, TRUE);
+        ziv_extra_bits = 16;
+        for(;;) {
+            prec = prec0 + ziv_extra_bits;
+            /* XXX: rigorous error analysis needed */
+            extra_bits = ceil_log2(e) * 2 + 1;
+            ret = bf_pow_ui_ui(r, radix, e, prec + extra_bits, BF_RNDN);
+            if (!e_sign)
+                ret |= bf_mul(r, r, a, prec + extra_bits, BF_RNDN);
+            else
+                ret |= bf_div(r, a, r, prec + extra_bits, BF_RNDN);
+            /* if the result is not exact, check that it can be safely
+               rounded to an integer */
+            if ((ret & BF_ST_INEXACT) &&
+                !bf_can_round(r, r->expn, rnd_mode, prec)) {
+                /* and more precision and retry */
+                ziv_extra_bits = ziv_extra_bits  + (ziv_extra_bits / 2);
+                continue;
+            } else {
+                bf_rint(r, BF_PREC_INF, rnd_mode);
+                break;
+            }
+        }
+        if (is_fixed_exponent)
+            break;
+        /* check that the result is < B^P */
+        /* XXX: do an fast approximate test first ? */
+        bf_init(r->ctx, B);
+        bf_pow_ui_ui(B, radix, P, BF_PREC_INF, BF_RNDZ);
+        res = bf_cmpu(r, B);
+        bf_delete(B);
+        if (res < 0)
+            break;
+        /* try a larger exponent */
+        E++;
+    }
+    *pE = E;
+}
+
+static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len)
+{
+    int digit, i;
+
+    if (radix == 10) {
+        /* specific case with constant divisor */
+        for(i = len - 1; i >= 0; i--) {
+            digit = (limb_t)n % 10;
+            n = (limb_t)n / 10;
+            buf[i] = digit + '0';
+        }
+    } else {
+        for(i = len - 1; i >= 0; i--) {
+            digit = (limb_t)n % radix;
+            n = (limb_t)n / radix;
+            if (digit < 10)
+                digit += '0';
+            else
+                digit += 'a' - 10;
+            buf[i] = digit;
+        }
+    }
+}
+
+/* for power of 2 radixes */
+static void limb_to_a2(char *buf, limb_t n, unsigned int radix_bits, int len)
+{
+    int digit, i;
+    unsigned int mask;
+
+    mask = (1 << radix_bits) - 1;
+    for(i = len - 1; i >= 0; i--) {
+        digit = n & mask;
+        n >>= radix_bits;
+        if (digit < 10)
+            digit += '0';
+        else
+            digit += 'a' - 10;
+        buf[i] = digit;
+    }
+}
+
+/* 'a' must be an integer. A dot is added before the 'dot_pos'
+   digit. dot_pos = n_digits does not display the dot. 0 <= dot_pos <=
+   n_digits. n_digits >= 1. */
+static void output_digits(DynBuf *s, const bf_t *a1, int radix, limb_t n_digits,
+                          limb_t dot_pos)
+{
+    limb_t i, v, l;
+    slimb_t pos, pos_incr;
+    int digits_per_limb, buf_pos, radix_bits, first_buf_pos;
+    char buf[65];
+    bf_t a_s, *a;
+    
+    if ((radix & (radix - 1)) != 0) {
+        limb_t n, radixl;
+
+        digits_per_limb = digits_per_limb_table[radix - 2];
+        radixl = get_limb_radix(radix);
+        a = &a_s;
+        bf_init(a1->ctx, a);
+        n = (n_digits + digits_per_limb - 1) / digits_per_limb;
+        bf_resize(a, n);
+        bf_integer_to_radix(a, a1, radixl);
+        radix_bits = 0;
+        pos = n;
+        pos_incr = 1;
+        first_buf_pos = pos * digits_per_limb - n_digits;
+    } else {
+        a = (bf_t *)a1;
+        radix_bits = ceil_log2(radix);
+        digits_per_limb = LIMB_BITS / radix_bits;
+        pos_incr = digits_per_limb * radix_bits;
+        pos = a->len * LIMB_BITS - a->expn + n_digits * radix_bits;
+        first_buf_pos = 0;
+    }
+    buf_pos = digits_per_limb;
+    i = 0;
+    while (i < n_digits) {
+        if (buf_pos == digits_per_limb) {
+            pos -= pos_incr;
+            if (radix_bits == 0) {
+                v = get_limbz(a, pos);
+                limb_to_a(buf, v, radix, digits_per_limb);
+            } else {
+                v = get_bits(a->tab, a->len, pos);
+                limb_to_a2(buf, v, radix_bits, digits_per_limb);
+            }
+            buf_pos = first_buf_pos;
+            first_buf_pos = 0;
+        }
+        if (i < dot_pos) {
+            l = dot_pos;
+        } else {
+            if (i == dot_pos)
+                dbuf_putc(s, '.');
+            l = n_digits;
+        }
+        l = bf_min(digits_per_limb - buf_pos, l - i);
+        dbuf_put(s, (uint8_t *)(buf + buf_pos), l);
+        buf_pos += l;
+        i += l;
+    }
+    if (a != a1)
+        bf_delete(a);
+}
+
+static void *bf_dbuf_realloc(void *opaque, void *ptr, size_t size)
+{
+    bf_context_t *s = opaque;
+    return bf_realloc(s, ptr, size);
+}
+
+/* return the length in bytes. A trailing '\0' is added */
+size_t bf_ftoa(char **pbuf, const bf_t *a2, int radix, limb_t prec,
+               bf_flags_t flags)
+{
+    DynBuf s_s, *s = &s_s;
+    int radix_bits;
+    
+    //    bf_print_str("ftoa", a2);
+    //    printf("radix=%d\n", radix);
+    dbuf_init2(s, a2->ctx, bf_dbuf_realloc);
+    if (a2->expn == BF_EXP_NAN) {
+        dbuf_putstr(s, "NaN");
+    } else {
+        if (a2->sign)
+            dbuf_putc(s, '-');
+        if (a2->expn == BF_EXP_INF) {
+            if (flags & BF_FTOA_JS_QUIRKS)
+                dbuf_putstr(s, "Infinity");
+            else
+                dbuf_putstr(s, "Inf");
+        } else {
+            int fmt;
+            slimb_t n_digits, n, i, n_max, n1;
+            bf_t a1_s, *a1;
+            bf_t a_s, *a = &a_s;
+
+            /* make a positive number */
+            a->tab = a2->tab;
+            a->len = a2->len;
+            a->expn = a2->expn;
+            a->sign = 0;
+            
+            if ((radix & (radix - 1)) != 0)
+                radix_bits = 0;
+            else
+                radix_bits = ceil_log2(radix);
+
+            fmt = flags & BF_FTOA_FORMAT_MASK;
+            a1 = &a1_s;
+            bf_init(a2->ctx, a1);
+            if (fmt == BF_FTOA_FORMAT_FRAC) {
+                size_t pos, start;
+                /* one more digit for the rounding */
+                n = 1 + bf_mul_log2_radix(bf_max(a->expn, 0), radix, TRUE, TRUE);
+                n_digits = n + prec;
+                n1 = n;
+                bf_convert_to_radix(a1, &n1, a, radix, n_digits,
+                                    flags & BF_RND_MASK, TRUE);
+                start = s->size;
+                output_digits(s, a1, radix, n_digits, n);
+                /* remove leading zeros because we allocated one more digit */
+                pos = start;
+                while ((pos + 1) < s->size && s->buf[pos] == '0' &&
+                       s->buf[pos + 1] != '.')
+                    pos++;
+                if (pos > start) {
+                    memmove(s->buf + start, s->buf + pos, s->size - pos);
+                    s->size -= (pos - start);
+                }
+            } else {
+                if (fmt == BF_FTOA_FORMAT_FIXED) {
+                    n_digits = prec;
+                    n_max = n_digits;
+                } else {
+                    slimb_t n_digits_max, n_digits_min;
+                    
+                    if (prec == BF_PREC_INF) {
+                        assert(radix_bits != 0);
+                        /* XXX: could use the exact number of bits */
+                        prec = a->len * LIMB_BITS;
+                    }
+                    n_digits = 1 + bf_mul_log2_radix(prec, radix, TRUE, TRUE);
+                    /* max number of digits for non exponential
+                       notation. The rational is to have the same rule
+                       as JS i.e. n_max = 21 for 64 bit float in base 10. */
+                    n_max = n_digits + 4;
+                    if (fmt == BF_FTOA_FORMAT_FREE_MIN) {
+                        bf_t b_s, *b = &b_s;
+                        
+                        /* find the minimum number of digits by
+                           dichotomy. */
+                        n_digits_max = n_digits;
+                        n_digits_min = 1;
+                        bf_init(a2->ctx, b);
+                        while (n_digits_min < n_digits_max) {
+                            n_digits = (n_digits_min + n_digits_max) / 2;
+                            bf_convert_to_radix(a1, &n, a, radix, n_digits,
+                                                flags & BF_RND_MASK, FALSE);
+                            /* convert back to a number and compare */
+                            bf_mul_pow_radix(b, a1, radix, n - n_digits,
+                                             prec,
+                                             (flags & ~BF_RND_MASK) |
+                                             BF_RNDN);
+                            if (bf_cmpu(b, a) == 0) {
+                                n_digits_max = n_digits;
+                            } else {
+                                n_digits_min = n_digits + 1;
+                            }
+                        }
+                        bf_delete(b);
+                        n_digits = n_digits_max;
+                    }
+                }
+                bf_convert_to_radix(a1, &n, a, radix, n_digits,
+                                    flags & BF_RND_MASK, FALSE);
+                if (a1->expn == BF_EXP_ZERO &&
+                    fmt != BF_FTOA_FORMAT_FIXED &&
+                    !(flags & BF_FTOA_FORCE_EXP)) {
+                    /* just output zero */
+                    dbuf_putstr(s, "0");
+                } else {
+                    if (flags & BF_FTOA_ADD_PREFIX) {
+                        if (radix == 16)
+                            dbuf_putstr(s, "0x");
+                        else if (radix == 8)
+                            dbuf_putstr(s, "0o");
+                        else if (radix == 2)
+                            dbuf_putstr(s, "0b");
+                    }
+                    if (a1->expn == BF_EXP_ZERO)
+                        n = 1;
+                    if ((flags & BF_FTOA_FORCE_EXP) ||
+                        n <= -6 || n > n_max) {
+                        const char *fmt;
+                        /* exponential notation */
+                        output_digits(s, a1, radix, n_digits, 1);
+                        if (radix_bits != 0 && radix <= 16) {
+                            if (flags & BF_FTOA_JS_QUIRKS)
+                                fmt = "p%+" PRId_LIMB;
+                            else
+                                fmt = "p%" PRId_LIMB;
+                            dbuf_printf(s, fmt, (n - 1) * radix_bits);
+                        } else {
+                            if (flags & BF_FTOA_JS_QUIRKS)
+                                fmt = "%c%+" PRId_LIMB;
+                            else
+                                fmt = "%c%" PRId_LIMB;
+                            dbuf_printf(s, fmt,
+                                        radix <= 10 ? 'e' : '@', n - 1);
+                        }
+                    } else if (n <= 0) {
+                        /* 0.x */
+                        dbuf_putstr(s, "0.");
+                        for(i = 0; i < -n; i++) {
+                            dbuf_putc(s, '0');
+                        }
+                        output_digits(s, a1, radix, n_digits, n_digits);
+                    } else {
+                        if (n_digits <= n) {
+                            /* no dot */
+                            output_digits(s, a1, radix, n_digits, n_digits);
+                            for(i = 0; i < (n - n_digits); i++)
+                                dbuf_putc(s, '0');
+                        } else {
+                            output_digits(s, a1, radix, n_digits, n);
+                        }
+                    }
+                }
+            }
+            bf_delete(a1);
+        }
+    }
+    dbuf_putc(s, '\0');
+    *pbuf = (char *)s->buf;
+    return s->size - 1;
+}
+
+/***************************************************************/
+/* transcendental functions */
+
+/* Note: the algorithm is from MPFR */
+static void bf_const_log2_rec(bf_t *T, bf_t *P, bf_t *Q, limb_t n1,
+                              limb_t n2, BOOL need_P)
+{
+    bf_context_t *s = T->ctx;
+    if ((n2 - n1) == 1) {
+        if (n1 == 0) {
+            bf_set_ui(P, 3);
+        } else {
+            bf_set_ui(P, n1);
+            P->sign = 1;
+        }
+        bf_set_ui(Q, 2 * n1 + 1);
+        Q->expn += 2;
+        bf_set(T, P);
+    } else {
+        limb_t m;
+        bf_t T1_s, *T1 = &T1_s;
+        bf_t P1_s, *P1 = &P1_s;
+        bf_t Q1_s, *Q1 = &Q1_s;
+        
+        m = n1 + ((n2 - n1) >> 1);
+        bf_const_log2_rec(T, P, Q, n1, m, TRUE);
+        bf_init(s, T1);
+        bf_init(s, P1);
+        bf_init(s, Q1);
+        bf_const_log2_rec(T1, P1, Q1, m, n2, need_P);
+        bf_mul(T, T, Q1, BF_PREC_INF, BF_RNDZ);
+        bf_mul(T1, T1, P, BF_PREC_INF, BF_RNDZ);
+        bf_add(T, T, T1, BF_PREC_INF, BF_RNDZ);
+        if (need_P)
+            bf_mul(P, P, P1, BF_PREC_INF, BF_RNDZ);
+        bf_mul(Q, Q, Q1, BF_PREC_INF, BF_RNDZ);
+        bf_delete(T1);
+        bf_delete(P1);
+        bf_delete(Q1);
+    }
+}
+
+/* compute log(2) with faithful rounding at precision 'prec' */
+static void bf_const_log2_internal(bf_t *T, limb_t prec)
+{
+    limb_t w, N;
+    bf_t P_s, *P = &P_s;
+    bf_t Q_s, *Q = &Q_s;
+
+    w = prec + 15;
+    N = w / 3 + 1;
+    bf_init(T->ctx, P);
+    bf_init(T->ctx, Q);
+    bf_const_log2_rec(T, P, Q, 0, N, FALSE);
+    bf_div(T, T, Q, prec, BF_RNDN);
+    bf_delete(P);
+    bf_delete(Q);
+}
+
+/* PI constant */
+
+#define CHUD_A 13591409
+#define CHUD_B 545140134
+#define CHUD_C 640320
+#define CHUD_BITS_PER_TERM 47
+
+static void chud_bs(bf_t *P, bf_t *Q, bf_t *G, int64_t a, int64_t b, int need_g,
+                    limb_t prec)
+{
+    bf_context_t *s = P->ctx;
+    int64_t c;
+
+    if (a == (b - 1)) {
+        bf_t T0, T1;
+        
+        bf_init(s, &T0);
+        bf_init(s, &T1);
+        bf_set_ui(G, 2 * b - 1);
+        bf_mul_ui(G, G, 6 * b - 1, prec, BF_RNDN);
+        bf_mul_ui(G, G, 6 * b - 5, prec, BF_RNDN);
+        bf_set_ui(&T0, CHUD_B);
+        bf_mul_ui(&T0, &T0, b, prec, BF_RNDN);
+        bf_set_ui(&T1, CHUD_A);
+        bf_add(&T0, &T0, &T1, prec, BF_RNDN);
+        bf_mul(P, G, &T0, prec, BF_RNDN);
+        P->sign = b & 1;
+
+        bf_set_ui(Q, b);
+        bf_mul_ui(Q, Q, b, prec, BF_RNDN);
+        bf_mul_ui(Q, Q, b, prec, BF_RNDN);
+        bf_mul_ui(Q, Q, (uint64_t)CHUD_C * CHUD_C * CHUD_C / 24, prec, BF_RNDN);
+        bf_delete(&T0);
+        bf_delete(&T1);
+    } else {
+        bf_t P2, Q2, G2;
+        
+        bf_init(s, &P2);
+        bf_init(s, &Q2);
+        bf_init(s, &G2);
+
+        c = (a + b) / 2;
+        chud_bs(P, Q, G, a, c, 1, prec);
+        chud_bs(&P2, &Q2, &G2, c, b, need_g, prec);
+        
+        /* Q = Q1 * Q2 */
+        /* G = G1 * G2 */
+        /* P = P1 * Q2 + P2 * G1 */
+        bf_mul(&P2, &P2, G, prec, BF_RNDN);
+        if (!need_g)
+            bf_set_ui(G, 0);
+        bf_mul(P, P, &Q2, prec, BF_RNDN);
+        bf_add(P, P, &P2, prec, BF_RNDN);
+        bf_delete(&P2);
+
+        bf_mul(Q, Q, &Q2, prec, BF_RNDN);
+        bf_delete(&Q2);
+        if (need_g)
+            bf_mul(G, G, &G2, prec, BF_RNDN);
+        bf_delete(&G2);
+    }
+}
+
+/* compute Pi with faithful rounding at precision 'prec' using the
+   Chudnovsky formula */
+static void bf_const_pi_internal(bf_t *Q, limb_t prec)
+{
+    bf_context_t *s = Q->ctx;
+    int64_t n, prec1;
+    bf_t P, G;
+
+    /* number of serie terms */
+    n = prec / CHUD_BITS_PER_TERM + 1;
+    /* XXX: precision analysis */
+    prec1 = prec + 32;
+
+    bf_init(s, &P);
+    bf_init(s, &G);
+
+    chud_bs(&P, Q, &G, 0, n, 0, BF_PREC_INF);
+    
+    bf_mul_ui(&G, Q, CHUD_A, prec1, BF_RNDN);
+    bf_add(&P, &G, &P, prec1, BF_RNDN);
+    bf_div(Q, Q, &P, prec1, BF_RNDF);
+ 
+    bf_set_ui(&P, CHUD_C / 64);
+    bf_rsqrt(&G, &P, prec1);
+    bf_mul_ui(&G, &G, (uint64_t)CHUD_C * CHUD_C / (8 * 12), prec1, BF_RNDF);
+    bf_mul(Q, Q, &G, prec, BF_RNDN);
+    bf_delete(&P);
+    bf_delete(&G);
+}
+
+static int bf_const_get(bf_t *T, limb_t prec, bf_flags_t flags,
+                        BFConstCache *c,
+                        void (*func)(bf_t *res, limb_t prec))
+{
+    limb_t ziv_extra_bits, prec1;
+
+    ziv_extra_bits = 32;
+    for(;;) {
+        prec1 = prec + ziv_extra_bits;
+        if (c->prec < prec1) {
+            if (c->val.len == 0)
+                bf_init(T->ctx, &c->val);
+            func(&c->val, prec1);
+            c->prec = prec1;
+        } else {
+            prec1 = c->prec;
+        }
+        bf_set(T, &c->val);
+        if (!bf_can_round(T, prec, flags & BF_RND_MASK, prec1)) {
+            /* and more precision and retry */
+            ziv_extra_bits = ziv_extra_bits  + (ziv_extra_bits / 2);
+        } else {
+            break;
+        }
+    }
+    return bf_round(T, prec, flags);
+}
+
+static void bf_const_free(BFConstCache *c)
+{
+    bf_delete(&c->val);
+    memset(c, 0, sizeof(*c));
+}
+
+int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags)
+{
+    bf_context_t *s = T->ctx;
+    return bf_const_get(T, prec, flags, &s->log2_cache, bf_const_log2_internal);
+}
+
+int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags)
+{
+    bf_context_t *s = T->ctx;
+    return bf_const_get(T, prec, flags, &s->pi_cache, bf_const_pi_internal);
+}
+
+void bf_clear_cache(bf_context_t *s)
+{
+#ifdef USE_FFT_MUL
+    fft_clear_cache(s);
+#endif
+    bf_const_free(&s->log2_cache);
+    bf_const_free(&s->pi_cache);
+}
+
+/* ZivFunc should compute the result 'r' with faithful rounding at
+   precision 'prec'. For efficiency purposes, the final bf_round()
+   does not need to be done in the function. */
+typedef int ZivFunc(bf_t *r, const bf_t *a, limb_t prec, void *opaque);
+
+static int bf_ziv_rounding(bf_t *r, const bf_t *a,
+                           limb_t prec, bf_flags_t flags,
+                           ZivFunc *f, void *opaque)
+{
+    int rnd_mode, ret;
+    slimb_t prec1, ziv_extra_bits;
+    
+    rnd_mode = flags & BF_RND_MASK;
+    if (rnd_mode == BF_RNDF) {
+        /* no need to iterate */
+        f(r, a, prec, opaque);
+        ret = 0;
+    } else {
+        ziv_extra_bits = 32;
+        for(;;) {
+            prec1 = prec + ziv_extra_bits;
+            ret = f(r, a, prec1, opaque);
+            if (ret & (BF_ST_OVERFLOW | BF_ST_UNDERFLOW)) {
+                /* should never happen because it indicates the
+                   rounding cannot be done correctly, but we do not
+                   catch all the cases */
+                return ret;
+            }
+            /* if the result is exact, we can stop */
+            if (!(ret & BF_ST_INEXACT)) {
+                ret = 0;
+                break;
+            }
+            if (bf_can_round(r, prec, rnd_mode, prec1)) {
+                ret = BF_ST_INEXACT;
+                break;
+            }
+            ziv_extra_bits = ziv_extra_bits * 2;
+        }
+    }
+    return bf_round(r, prec, flags) | ret;
+}
+
+/* Compute the exponential using faithful rounding at precision 'prec'.
+   Note: the algorithm is from MPFR */
+static int bf_exp_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    slimb_t n, K, l, i, prec1;
+    
+    assert(r != a);
+
+    /* argument reduction:
+       T = a - n*log(2) with 0 <= T < log(2) and n integer.
+    */
+    bf_init(s, T);
+    if (a->expn <= -1) {
+        /* 0 <= abs(a) <= 0.5 */
+        if (a->sign)
+            n = -1;
+        else
+            n = 0;
+    } else {
+        bf_const_log2(T, LIMB_BITS, BF_RNDZ);
+        bf_div(T, a, T, LIMB_BITS, BF_RNDD);
+        bf_get_limb(&n, T, 0);
+    }
+
+    K = bf_isqrt((prec + 1) / 2);
+    l = (prec - 1) / K + 1;
+    /* XXX: precision analysis ? */
+    prec1 = prec + (K + 2 * l + 18) + K + 8;
+    if (a->expn > 0)
+        prec1 += a->expn;
+    //    printf("n=%ld K=%ld prec1=%ld\n", n, K, prec1);
+
+    bf_const_log2(T, prec1, BF_RNDF);
+    bf_mul_si(T, T, n, prec1, BF_RNDN);
+    bf_sub(T, a, T, prec1, BF_RNDN);
+
+    /* reduce the range of T */
+    bf_mul_2exp(T, -K, BF_PREC_INF, BF_RNDZ);
+    
+    /* Taylor expansion around zero :
+     1 + x + x^2/2 + ... + x^n/n! 
+     = (1 + x * (1 + x/2 * (1 + ... (x/n))))
+    */
+    {
+        bf_t U_s, *U = &U_s;
+        
+        bf_init(s, U);
+        bf_set_ui(r, 1);
+        for(i = l ; i >= 1; i--) {
+            bf_set_ui(U, i);
+            bf_div(U, T, U, prec1, BF_RNDN);
+            bf_mul(r, r, U, prec1, BF_RNDN);
+            bf_add_si(r, r, 1, prec1, BF_RNDN);
+        }
+        bf_delete(U);
+    }
+    bf_delete(T);
+    
+    /* undo the range reduction */
+    for(i = 0; i < K; i++) {
+        bf_mul(r, r, r, prec1, BF_RNDN);
+    }
+
+    /* undo the argument reduction */
+    bf_mul_2exp(r, n, BF_PREC_INF, BF_RNDZ);
+
+    return BF_ST_INEXACT;
+}
+
+int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    bf_context_t *s = r->ctx;
+    assert(r != a);
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+        } else if (a->expn == BF_EXP_INF) {
+            if (a->sign)
+                bf_set_zero(r, 0);
+            else
+                bf_set_inf(r, 0);
+        } else {
+            bf_set_ui(r, 1);
+        }
+        return 0;
+    }
+
+    /* crude overflow and underflow tests */
+    if (a->expn > 0) {
+        bf_t T_s, *T = &T_s;
+        bf_t log2_s, *log2 = &log2_s;
+        slimb_t e_min, e_max;
+        e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1);
+        e_min = -e_max + 3;
+        if (flags & BF_FLAG_SUBNORMAL)
+            e_min -= (prec - 1);
+
+        bf_init(s, T);
+        bf_init(s, log2);
+        bf_const_log2(log2, LIMB_BITS, BF_RNDU);
+        bf_mul_ui(T, log2, e_max, LIMB_BITS, BF_RNDU);
+        /* a > e_max * log(2) implies exp(a) > e_max */
+        if (bf_cmp_lt(T, a) > 0) {
+            /* overflow */
+            bf_delete(T);
+            bf_delete(log2);
+            return bf_set_overflow(r, 0, prec, flags);
+        }
+        /* a < e_min * log(2) implies exp(a) < e_min */
+        bf_mul_si(T, log2, e_min, LIMB_BITS, BF_RNDD);
+        if (bf_cmp_lt(a, T)) {
+            int rnd_mode = flags & BF_RND_MASK;
+
+            /* underflow */
+            bf_delete(T);
+            bf_delete(log2);
+            if (rnd_mode == BF_RNDU) {
+                /* set the smallest value */
+                bf_set_ui(r, 1);
+                r->expn = e_min;
+            } else {
+                bf_set_zero(r, 0);
+            }
+            return BF_ST_UNDERFLOW | BF_ST_INEXACT;
+        }
+        bf_delete(log2);
+        bf_delete(T);
+    }
+
+    return bf_ziv_rounding(r, a, prec, flags, bf_exp_internal, NULL);
+}
+
+static int bf_log_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    bf_t U_s, *U = &U_s;
+    bf_t V_s, *V = &V_s;
+    slimb_t n, prec1, l, i, K;
+    
+    assert(r != a);
+
+    bf_init(s, T);
+    /* argument reduction 1 */
+    /* T=a*2^n with 2/3 <= T <= 4/3 */
+    {
+        bf_t U_s, *U = &U_s;
+        bf_set(T, a);
+        n = T->expn;
+        T->expn = 0;
+        /* U= ~ 2/3 */
+        bf_init(s, U);
+        bf_set_ui(U, 0xaaaaaaaa); 
+        U->expn = 0;
+        if (bf_cmp_lt(T, U)) {
+            T->expn++;
+            n--;
+        }
+        bf_delete(U);
+    }
+    //    printf("n=%ld\n", n);
+    //    bf_print_str("T", T);
+
+    /* XXX: precision analysis */
+    /* number of iterations for argument reduction 2 */
+    K = bf_isqrt((prec + 1) / 2); 
+    /* order of Taylor expansion */
+    l = prec / (2 * K) + 1; 
+    /* precision of the intermediate computations */
+    prec1 = prec + K + 2 * l + 32;
+
+    bf_init(s, U);
+    bf_init(s, V);
+    
+    /* Note: cancellation occurs here, so we use more precision (XXX:
+       reduce the precision by computing the exact cancellation) */
+    bf_add_si(T, T, -1, BF_PREC_INF, BF_RNDN); 
+
+    /* argument reduction 2 */
+    for(i = 0; i < K; i++) {
+        /* T = T / (1 + sqrt(1 + T)) */
+        bf_add_si(U, T, 1, prec1, BF_RNDN);
+        bf_sqrt(V, U, prec1, BF_RNDF);
+        bf_add_si(U, V, 1, prec1, BF_RNDN);
+        bf_div(T, T, U, prec1, BF_RNDN);
+    }
+
+    {
+        bf_t Y_s, *Y = &Y_s;
+        bf_t Y2_s, *Y2 = &Y2_s;
+        bf_init(s, Y);
+        bf_init(s, Y2);
+
+        /* compute ln(1+x) = ln((1+y)/(1-y)) with y=x/(2+x)
+           = y + y^3/3 + ... + y^(2*l + 1) / (2*l+1) 
+           with Y=Y^2
+           = y*(1+Y/3+Y^2/5+...) = y*(1+Y*(1/3+Y*(1/5 + ...)))
+        */
+        bf_add_si(Y, T, 2, prec1, BF_RNDN);
+        bf_div(Y, T, Y, prec1, BF_RNDN);
+
+        bf_mul(Y2, Y, Y, prec1, BF_RNDN);
+        bf_set_ui(r, 0);
+        for(i = l; i >= 1; i--) {
+            bf_set_ui(U, 1);
+            bf_set_ui(V, 2 * i + 1);
+            bf_div(U, U, V, prec1, BF_RNDN);
+            bf_add(r, r, U, prec1, BF_RNDN);
+            bf_mul(r, r, Y2, prec1, BF_RNDN);
+        }
+        bf_add_si(r, r, 1, prec1, BF_RNDN);
+        bf_mul(r, r, Y, prec1, BF_RNDN);
+        bf_delete(Y);
+        bf_delete(Y2);
+    }
+    bf_delete(V);
+    bf_delete(U);
+
+    /* multiplication by 2 for the Taylor expansion and undo the
+       argument reduction 2*/
+    bf_mul_2exp(r, K + 1, BF_PREC_INF, BF_RNDZ);
+    
+    /* undo the argument reduction 1 */
+    bf_const_log2(T, prec1, BF_RNDF);
+    bf_mul_si(T, T, n, prec1, BF_RNDN);
+    bf_add(r, r, T, prec1, BF_RNDN);
+    
+    bf_delete(T);
+    return BF_ST_INEXACT;
+}
+
+int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    
+    assert(r != a);
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            return 0;
+        } else if (a->expn == BF_EXP_INF) {
+            if (a->sign) {
+                bf_set_nan(r);
+                return BF_ST_INVALID_OP;
+            } else {
+                bf_set_inf(r, 0);
+                return 0;
+            }
+        } else {
+            bf_set_inf(r, 1);
+            return 0;
+        }
+    }
+    if (a->sign) {
+        bf_set_nan(r);
+        return BF_ST_INVALID_OP;
+    }
+    bf_init(s, T);
+    bf_set_ui(T, 1);
+    if (bf_cmp_eq(a, T)) {
+        bf_set_zero(r, 0);
+        bf_delete(T);
+        return 0;
+    }
+    bf_delete(T);
+
+    return bf_ziv_rounding(r, a, prec, flags, bf_log_internal, NULL);
+}
+
+/* x and y finite and x > 0 */
+/* XXX: overflow/underflow handling */
+static int bf_pow_generic(bf_t *r, const bf_t *x, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    const bf_t *y = opaque;
+    bf_t T_s, *T = &T_s;
+    limb_t prec1;
+
+    bf_init(s, T);
+    /* XXX: proof for the added precision */
+    prec1 = prec + 32;
+    bf_log(T, x, prec1, BF_RNDF);
+    bf_mul(T, T, y, prec1, BF_RNDF);
+    bf_exp(r, T, prec1, BF_RNDF);
+    bf_delete(T);
+    return BF_ST_INEXACT;
+}
+
+/* x and y finite, x > 0, y integer and y fits on one limb */
+/* XXX: overflow/underflow handling */ 
+static int bf_pow_int(bf_t *r, const bf_t *x, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    const bf_t *y = opaque;
+    bf_t T_s, *T = &T_s;
+    limb_t prec1;
+    int ret;
+    slimb_t y1;
+    
+    bf_get_limb(&y1, y, 0);
+    if (y1 < 0)
+        y1 = -y1;
+    /* XXX: proof for the added precision */
+    prec1 = prec + ceil_log2(y1) * 2 + 8;
+    ret = bf_pow_ui(r, x, y1 < 0 ? -y1 : y1, prec1, BF_RNDN);
+    if (y->sign) {
+        bf_init(s, T);
+        bf_set_ui(T, 1);
+        ret |= bf_div(r, T, r, prec1, BF_RNDN);
+        bf_delete(T);
+    }
+    return ret;
+}
+
+/* x must be a finite non zero float. Return TRUE if there is a
+   floating point number r such as x=r^(2^n) and return this floating
+   point number 'r'. Otherwise return FALSE and r is undefined. */
+static BOOL check_exact_power2n(bf_t *r, const bf_t *x, slimb_t n)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    slimb_t e, i, er;
+    limb_t v;
+    
+    /* x = m*2^e with m odd integer */
+    e = bf_get_exp_min(x);
+    /* fast check on the exponent */
+    if (n > (LIMB_BITS - 1)) {
+        if (e != 0)
+            return FALSE;
+        er = 0;
+    } else {
+        if ((e & (((limb_t)1 << n) - 1)) != 0)
+            return FALSE;
+        er = e >> n;
+    }
+    /* every perfect odd square = 1 modulo 8 */
+    v = get_bits(x->tab, x->len, x->len * LIMB_BITS - x->expn + e);
+    if ((v & 7) != 1)
+        return FALSE;
+
+    bf_init(s, T);
+    bf_set(T, x);
+    T->expn -= e;
+    for(i = 0; i < n; i++) {
+        if (i != 0)
+            bf_set(T, r);
+        if (bf_sqrtrem(r, NULL, T) != 0)
+            return FALSE;
+    }
+    r->expn += er;
+    return TRUE;
+}
+
+/* prec = BF_PREC_INF is accepted for x and y integers and y >= 0 */
+int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    bf_t ytmp_s;
+    BOOL y_is_int, y_is_odd;
+    int r_sign, ret, rnd_mode;
+    slimb_t y_emin;
+    
+    if (x->len == 0 || y->len == 0) {
+        if (y->expn == BF_EXP_ZERO) {
+            /* pow(x, 0) = 1 */
+            bf_set_ui(r, 1);
+        } else if (x->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+        } else {
+            int cmp_x_abs_1;
+            bf_set_ui(r, 1);
+            cmp_x_abs_1 = bf_cmpu(x, r);
+            if (cmp_x_abs_1 == 0 && (flags & BF_POW_JS_QUICKS) &&
+                (y->expn >= BF_EXP_INF)) {
+                bf_set_nan(r);
+            } else if (cmp_x_abs_1 == 0 &&
+                       (!x->sign || y->expn != BF_EXP_NAN)) {
+                /* pow(1, y) = 1 even if y = NaN */
+                /* pow(-1, +/-inf) = 1 */
+            } else if (y->expn == BF_EXP_NAN) {
+                bf_set_nan(r);
+            } else if (y->expn == BF_EXP_INF) {
+                if (y->sign == (cmp_x_abs_1 > 0)) {
+                    bf_set_zero(r, 0);
+                } else {
+                    bf_set_inf(r, 0);
+                }
+            } else {
+                y_emin = bf_get_exp_min(y);
+                y_is_odd = (y_emin == 0);
+                if (y->sign == (x->expn == BF_EXP_ZERO)) {
+                    bf_set_inf(r, y_is_odd & x->sign);
+                    if (y->sign) {
+                        /* pow(0, y) with y < 0 */
+                        return BF_ST_DIVIDE_ZERO;
+                    }
+                } else {
+                    bf_set_zero(r, y_is_odd & x->sign);
+                }
+            }
+        }
+        return 0;
+    }
+    bf_init(s, T);
+    bf_set(T, x);
+    y_emin = bf_get_exp_min(y);
+    y_is_int = (y_emin >= 0);
+    rnd_mode = flags & BF_RND_MASK;
+    if (x->sign) {
+        if (!y_is_int) {
+            bf_set_nan(r);
+            bf_delete(T);
+            return BF_ST_INVALID_OP;
+        }
+        y_is_odd = (y_emin == 0);
+        r_sign = y_is_odd;
+        /* change the directed rounding mode if the sign of the result
+           is changed */
+        if (r_sign && (rnd_mode == BF_RNDD || rnd_mode == BF_RNDU))
+            flags ^= 1;
+        bf_neg(T);
+    } else {
+        r_sign = 0;
+    }
+
+    bf_set_ui(r, 1);
+    if (bf_cmp_eq(T, r)) {
+        /* abs(x) = 1: nothing more to do */
+        ret = 0;
+    } else if (y_is_int) {
+        slimb_t T_bits, e;
+    int_pow:
+        T_bits = T->expn - bf_get_exp_min(T);
+        if (T_bits == 1) {
+            /* pow(2^b, y) = 2^(b*y) */
+            bf_mul_si(T, y, T->expn - 1, LIMB_BITS, BF_RNDZ);
+            bf_get_limb(&e, T, 0);
+            bf_set_ui(r, 1);
+            ret = bf_mul_2exp(r, e, prec, flags);
+        } else if (prec == BF_PREC_INF) {
+            slimb_t y1;
+            /* specific case for infinite precision (integer case) */
+            bf_get_limb(&y1, y, 0);
+            assert(!y->sign);
+            /* x must be an integer, so abs(x) >= 2 */
+            if (y1 >= ((slimb_t)1 << BF_EXP_BITS_MAX)) {
+                bf_delete(T);
+                return bf_set_overflow(r, 0, BF_PREC_INF, flags);
+            }
+            ret = bf_pow_ui(r, T, y1, BF_PREC_INF, BF_RNDZ);
+        } else {
+            if (y->expn <= 31) {
+                /* small enough power: use exponentiation in all cases */
+            } else if (y->sign) {
+                /* cannot be exact */
+                goto general_case;
+            } else {
+                if (rnd_mode == BF_RNDF)
+                    goto general_case; /* no need to track exact results */
+                /* see if the result has a chance to be exact:
+                   if x=a*2^b (a odd), x^y=a^y*2^(b*y)
+                   x^y needs a precision of at least floor_log2(a)*y bits
+                */
+                bf_mul_si(r, y, T_bits - 1, LIMB_BITS, BF_RNDZ);
+                bf_get_limb(&e, r, 0);
+                if (prec < e)
+                    goto general_case;
+            }
+            ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_int, (void *)y);
+        }
+    } else {
+        if (rnd_mode != BF_RNDF) {
+            bf_t *y1;
+            if (y_emin < 0 && check_exact_power2n(r, T, -y_emin)) {
+                /* the problem is reduced to a power to an integer */
+#if 0
+                printf("\nn=%ld\n", -y_emin);
+                bf_print_str("T", T);
+                bf_print_str("r", r);
+#endif
+                bf_set(T, r);
+                y1 = &ytmp_s;
+                y1->tab = y->tab;
+                y1->len = y->len;
+                y1->sign = y->sign;
+                y1->expn = y->expn - y_emin;
+                y = y1;
+                goto int_pow;
+            }
+        }
+    general_case:
+        ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_generic, (void *)y);
+    }
+    bf_delete(T);
+    r->sign = r_sign;
+    return ret;
+}
+
+/* compute sqrt(-2*x-x^2) to get |sin(x)| from cos(x) - 1. */
+static void bf_sqrt_sin(bf_t *r, const bf_t *x, limb_t prec1)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    bf_init(s, T);
+    bf_set(T, x);
+    bf_mul(r, T, T, prec1, BF_RNDN);
+    bf_mul_2exp(T, 1, BF_PREC_INF, BF_RNDZ);
+    bf_add(T, T, r, prec1, BF_RNDN);
+    bf_neg(T);
+    bf_sqrt(r, T, prec1, BF_RNDF);
+    bf_delete(T);
+}
+
+int bf_sincos(bf_t *s, bf_t *c, const bf_t *a, limb_t prec)
+{
+    bf_context_t *s1 = a->ctx;
+    bf_t T_s, *T = &T_s;
+    bf_t U_s, *U = &U_s;
+    bf_t r_s, *r = &r_s;
+    slimb_t K, prec1, i, l, mod, prec2;
+    int is_neg;
+    
+    assert(c != a && s != a);
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            if (c)
+                bf_set_nan(c);
+            if (s)
+                bf_set_nan(s);
+            return 0;
+        } else if (a->expn == BF_EXP_INF) {
+            if (c)
+                bf_set_nan(c);
+            if (s)
+                bf_set_nan(s);
+            return BF_ST_INVALID_OP;
+        } else {
+            if (c)
+                bf_set_ui(c, 1);
+            if (s)
+                bf_set_zero(s, a->sign);
+            return 0;
+        }
+    }
+
+    bf_init(s1, T);
+    bf_init(s1, U);
+    bf_init(s1, r);
+    
+    /* XXX: precision analysis */
+    K = bf_isqrt(prec / 2);
+    l = prec / (2 * K) + 1;
+    prec1 = prec + 2 * K + l + 8;
+    
+    /* after the modulo reduction, -pi/4 <= T <= pi/4 */
+    if (a->expn <= -1) {
+        /* abs(a) <= 0.25: no modulo reduction needed */
+        bf_set(T, a);
+        mod = 0;
+    } else {
+        slimb_t cancel;
+        cancel = 0;
+        for(;;) {
+            prec2 = prec1 + cancel;
+            bf_const_pi(U, prec2, BF_RNDF);
+            bf_mul_2exp(U, -1, BF_PREC_INF, BF_RNDZ);
+            bf_remquo(&mod, T, a, U, prec2, BF_RNDN);
+            //            printf("T.expn=%ld prec2=%ld\n", T->expn, prec2);
+            if (mod == 0 || (T->expn != BF_EXP_ZERO &&
+                             (T->expn + prec2) >= (prec1 - 1)))
+                break;
+            /* increase the number of bits until the precision is good enough */
+            cancel = bf_max(-T->expn, (cancel + 1) * 3 / 2);
+        }
+        mod &= 3;
+    }
+    
+    is_neg = T->sign;
+        
+    /* compute cosm1(x) = cos(x) - 1 */
+    bf_mul(T, T, T, prec1, BF_RNDN);
+    bf_mul_2exp(T, -2 * K, BF_PREC_INF, BF_RNDZ);
+    
+    /* Taylor expansion:
+       -x^2/2 + x^4/4! - x^6/6! + ...
+    */
+    bf_set_ui(r, 1);
+    for(i = l ; i >= 1; i--) {
+        bf_set_ui(U, 2 * i - 1);
+        bf_mul_ui(U, U, 2 * i, BF_PREC_INF, BF_RNDZ);
+        bf_div(U, T, U, prec1, BF_RNDN);
+        bf_mul(r, r, U, prec1, BF_RNDN);
+        bf_neg(r);
+        if (i != 1)
+            bf_add_si(r, r, 1, prec1, BF_RNDN);
+    }
+    bf_delete(U);
+
+    /* undo argument reduction:
+       cosm1(2*x)= 2*(2*cosm1(x)+cosm1(x)^2)
+    */
+    for(i = 0; i < K; i++) {
+        bf_mul(T, r, r, prec1, BF_RNDN);
+        bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ);
+        bf_add(r, r, T, prec1, BF_RNDN);
+        bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ);
+    }
+    bf_delete(T);
+
+    if (c) {
+        if ((mod & 1) == 0) {
+            bf_add_si(c, r, 1, prec1, BF_RNDN);
+        } else {
+            bf_sqrt_sin(c, r, prec1);
+            c->sign = is_neg ^ 1;
+        }
+        c->sign ^= mod >> 1;
+    }
+    if (s) {
+        if ((mod & 1) == 0) {
+            bf_sqrt_sin(s, r, prec1);
+            s->sign = is_neg;
+        } else {
+            bf_add_si(s, r, 1, prec1, BF_RNDN);
+        }
+        s->sign ^= mod >> 1;
+    }
+    bf_delete(r);
+    return BF_ST_INEXACT;
+}
+
+static int bf_cos_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque)
+{
+    return bf_sincos(NULL, r, a, prec);
+}
+
+int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, a, prec, flags, bf_cos_internal, NULL);
+}
+
+static int bf_sin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque)
+{
+    return bf_sincos(r, NULL, a, prec);
+}
+
+int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, a, prec, flags, bf_sin_internal, NULL);
+}
+
+static int bf_tan_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    bf_t T_s, *T = &T_s;
+    limb_t prec1;
+    
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            return 0;
+        } else if (a->expn == BF_EXP_INF) {
+            bf_set_nan(r);
+            return BF_ST_INVALID_OP;
+        } else {
+            bf_set_zero(r, a->sign);
+            return 0;
+        }
+    }
+
+    /* XXX: precision analysis */
+    prec1 = prec + 8;
+    bf_init(s, T);
+    bf_sincos(r, T, a, prec1);
+    bf_div(r, r, T, prec1, BF_RNDF);
+    bf_delete(T);
+    return BF_ST_INEXACT;
+}
+
+int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, a, prec, flags, bf_tan_internal, NULL);
+}
+
+/* if add_pi2 is true, add pi/2 to the result (used for acos(x) to
+   avoid cancellation) */
+static int bf_atan_internal(bf_t *r, const bf_t *a, limb_t prec,
+                            void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    BOOL add_pi2 = (BOOL)(intptr_t)opaque;
+    bf_t T_s, *T = &T_s;
+    bf_t U_s, *U = &U_s;
+    bf_t V_s, *V = &V_s;
+    bf_t X2_s, *X2 = &X2_s;
+    int cmp_1;
+    slimb_t prec1, i, K, l;
+    
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            return 0;
+        } else {
+            if (a->expn == BF_EXP_INF)
+                i = 1 - 2 * a->sign;
+            else
+                i = 0;
+            i += add_pi2;
+            /* return i*(pi/2) with -1 <= i <= 2 */
+            if (i == 0) {
+                bf_set_zero(r, add_pi2 ? 0 : a->sign);
+                return 0;
+            } else {
+                /* PI or PI/2 */
+                bf_const_pi(r, prec, BF_RNDF);
+                if (i != 2)
+                    bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ);
+                r->sign = (i < 0);
+                return BF_ST_INEXACT;
+            }
+        }
+    }
+    
+    bf_init(s, T);
+    bf_set_ui(T, 1);
+    cmp_1 = bf_cmpu(a, T);
+    if (cmp_1 == 0 && !add_pi2) {
+        /* short cut: abs(a) == 1 -> +/-pi/4 */
+        bf_const_pi(r, prec, BF_RNDF);
+        bf_mul_2exp(r, -2, BF_PREC_INF, BF_RNDZ);
+        r->sign = a->sign;
+        bf_delete(T);
+        return BF_ST_INEXACT;
+    }
+
+    /* XXX: precision analysis */
+    K = bf_isqrt((prec + 1) / 2);
+    l = prec / (2 * K) + 1;
+    prec1 = prec + K + 2 * l + 32;
+    //    printf("prec=%ld K=%ld l=%ld prec1=%ld\n", prec, K, l, prec1);
+    
+    if (cmp_1 > 0) {
+        bf_set_ui(T, 1);
+        bf_div(T, T, a, prec1, BF_RNDN);
+    } else {
+        bf_set(T, a);
+    }
+
+    /* abs(T) <= 1 */
+
+    /* argument reduction */
+
+    bf_init(s, U);
+    bf_init(s, V);
+    bf_init(s, X2);
+    for(i = 0; i < K; i++) {
+        /* T = T / (1 + sqrt(1 + T^2)) */
+        bf_mul(U, T, T, prec1, BF_RNDN);
+        bf_add_si(U, U, 1, prec1, BF_RNDN);
+        bf_sqrt(V, U, prec1, BF_RNDN);
+        bf_add_si(V, V, 1, prec1, BF_RNDN);
+        bf_div(T, T, V, prec1, BF_RNDN);
+    }
+
+    /* Taylor series: 
+       x - x^3/3 + ... + (-1)^ l * y^(2*l + 1) / (2*l+1) 
+    */
+    bf_mul(X2, T, T, prec1, BF_RNDN);
+    bf_set_ui(r, 0);
+    for(i = l; i >= 1; i--) {
+        bf_set_si(U, 1);
+        bf_set_ui(V, 2 * i + 1);
+        bf_div(U, U, V, prec1, BF_RNDN);
+        bf_neg(r);
+        bf_add(r, r, U, prec1, BF_RNDN);
+        bf_mul(r, r, X2, prec1, BF_RNDN);
+    }
+    bf_neg(r);
+    bf_add_si(r, r, 1, prec1, BF_RNDN);
+    bf_mul(r, r, T, prec1, BF_RNDN);
+
+    /* undo the argument reduction */
+    bf_mul_2exp(r, K, BF_PREC_INF, BF_RNDZ);
+    
+    bf_delete(U);
+    bf_delete(V);
+    bf_delete(X2);
+
+    i = add_pi2;
+    if (cmp_1 > 0) {
+        /* undo the inversion : r = sign(a)*PI/2 - r */
+        bf_neg(r);
+        i += 1 - 2 * a->sign;
+    }
+    /* add i*(pi/2) with -1 <= i <= 2 */
+    if (i != 0) {
+        bf_const_pi(T, prec1, BF_RNDF);
+        if (i != 2)
+            bf_mul_2exp(T, -1, BF_PREC_INF, BF_RNDZ);
+        T->sign = (i < 0);
+        bf_add(r, T, r, prec1, BF_RNDN);
+    }
+    
+    bf_delete(T);
+    return BF_ST_INEXACT;
+}
+
+int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, a, prec, flags, bf_atan_internal, (void *)FALSE);
+}
+
+static int bf_atan2_internal(bf_t *r, const bf_t *y, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    const bf_t *x = opaque;
+    bf_t T_s, *T = &T_s;
+    limb_t prec1;
+    int ret;
+    
+    if (y->expn == BF_EXP_NAN || x->expn == BF_EXP_NAN) {
+        bf_set_nan(r);
+        return 0;
+    }
+
+    /* compute atan(y/x) assumming inf/inf = 1 and 0/0 = 0 */
+    bf_init(s, T);
+    prec1 = prec + 32;
+    if (y->expn == BF_EXP_INF && x->expn == BF_EXP_INF) {
+        bf_set_ui(T, 1);
+        T->sign = y->sign ^ x->sign;
+    } else if (y->expn == BF_EXP_ZERO && x->expn == BF_EXP_ZERO) {
+        bf_set_zero(T, y->sign ^ x->sign);
+    } else {
+        bf_div(T, y, x, prec1, BF_RNDF);
+    }
+    ret = bf_atan(r, T, prec1, BF_RNDF);
+
+    if (x->sign) {
+        /* if x < 0 (it includes -0), return sign(y)*pi + atan(y/x) */
+        bf_const_pi(T, prec1, BF_RNDF);
+        T->sign = y->sign;
+        bf_add(r, r, T, prec1, BF_RNDN);
+        ret |= BF_ST_INEXACT;
+    }
+
+    bf_delete(T);
+    return ret;
+}
+
+int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x,
+             limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, y, prec, flags, bf_atan2_internal, (void *)x);
+}
+
+static int bf_asin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque)
+{
+    bf_context_t *s = r->ctx;
+    BOOL is_acos = (BOOL)(intptr_t)opaque;
+    bf_t T_s, *T = &T_s;
+    limb_t prec1, prec2;
+    int res;
+    
+    if (a->len == 0) {
+        if (a->expn == BF_EXP_NAN) {
+            bf_set_nan(r);
+            return 0;
+        } else if (a->expn == BF_EXP_INF) {
+            bf_set_nan(r);
+            return BF_ST_INVALID_OP;
+        } else {
+            if (is_acos) {
+                bf_const_pi(r, prec, BF_RNDF);
+                bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ);
+                return BF_ST_INEXACT;
+            } else {
+                bf_set_zero(r, a->sign);
+                return 0;
+            }
+        }
+    }
+    bf_init(s, T);
+    bf_set_ui(T, 1);
+    res = bf_cmpu(a, T);
+    if (res > 0) {
+        bf_delete(T);
+        bf_set_nan(r);
+        return BF_ST_INVALID_OP;
+    } else if (res == 0 && a->sign == 0 && is_acos) {
+        bf_set_zero(r, 0);
+        bf_delete(T);
+        return 0;
+    }
+    
+    /* asin(x) = atan(x/sqrt(1-x^2)) 
+       acos(x) = pi/2 - asin(x) */
+    prec1 = prec + 8;
+    /* increase the precision in x^2 to compensate the cancellation in
+       (1-x^2) if x is close to 1 */
+    /* XXX: use less precision when possible */
+    if (a->expn >= 0)
+        prec2 = BF_PREC_INF;
+    else
+        prec2 = prec1;
+    bf_mul(T, a, a, prec2, BF_RNDN);
+    bf_neg(T);
+    bf_add_si(T, T, 1, prec2, BF_RNDN);
+
+    bf_sqrt(r, T, prec1, BF_RNDN);
+    bf_div(T, a, r, prec1, BF_RNDN);
+    if (is_acos)
+        bf_neg(T);
+    bf_atan_internal(r, T, prec1, (void *)(intptr_t)is_acos);
+    bf_delete(T);
+    return BF_ST_INEXACT;
+}
+
+int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)FALSE);
+}
+
+int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
+{
+    return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)TRUE);
+}
+
+#ifdef USE_FFT_MUL
+/***************************************************************/
+/* Integer multiplication with FFT */
+
+/* or LIMB_BITS at bit position 'pos' in tab */
+static inline void put_bits(limb_t *tab, limb_t len, slimb_t pos, limb_t val)
+{
+    limb_t i;
+    int p;
+
+    i = pos >> LIMB_LOG2_BITS;
+    p = pos & (LIMB_BITS - 1);
+    if (i < len)
+        tab[i] |= val << p;
+    if (p != 0) {
+        i++;
+        if (i < len) {
+            tab[i] |= val >> (LIMB_BITS - p);
+        }
+    }
+}
+
+#if defined(__AVX2__)
+
+typedef double NTTLimb;
+
+/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */
+#define NTT_MOD_LOG2_MIN 50
+#define NTT_MOD_LOG2_MAX 51
+#define NB_MODS 5
+#define NTT_PROOT_2EXP 39
+static const int ntt_int_bits[NB_MODS] = { 254, 203, 152, 101, 50, };
+
+static const limb_t ntt_mods[NB_MODS] = { 0x00073a8000000001, 0x0007858000000001, 0x0007a38000000001, 0x0007a68000000001, 0x0007fd8000000001,
+};
+
+static const limb_t ntt_proot[2][NB_MODS] = {
+    { 0x00056198d44332c8, 0x0002eb5d640aad39, 0x00047e31eaa35fd0, 0x0005271ac118a150, 0x00075e0ce8442bd5, },
+    { 0x000461169761bcc5, 0x0002dac3cb2da688, 0x0004abc97751e3bf, 0x000656778fc8c485, 0x0000dc6469c269fa, },
+};
+
+static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = {
+ 0x00020e4da740da8e, 0x0004c3dc09c09c1d, 0x000063bd097b4271, 0x000799d8f18f18fd,
+ 0x0005384222222264, 0x000572b07c1f07fe, 0x00035cd08888889a,
+ 0x00066015555557e3, 0x000725960b60b623,
+ 0x0002fc1fa1d6ce12,
+};
+
+#else
+
+typedef limb_t NTTLimb;
+
+#if LIMB_BITS == 64
+
+#define NTT_MOD_LOG2_MIN 61
+#define NTT_MOD_LOG2_MAX 62
+#define NB_MODS 5
+#define NTT_PROOT_2EXP 51
+static const int ntt_int_bits[NB_MODS] = { 307, 246, 185, 123, 61, };
+
+static const limb_t ntt_mods[NB_MODS] = { 0x28d8000000000001, 0x2a88000000000001, 0x2ed8000000000001, 0x3508000000000001, 0x3aa8000000000001,
+};
+
+static const limb_t ntt_proot[2][NB_MODS] = {
+    { 0x1b8ea61034a2bea7, 0x21a9762de58206fb, 0x02ca782f0756a8ea, 0x278384537a3e50a1, 0x106e13fee74ce0ab, },
+    { 0x233513af133e13b8, 0x1d13140d1c6f75f1, 0x12cde57f97e3eeda, 0x0d6149e23cbe654f, 0x36cd204f522a1379, },
+};
+
+static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = {
+ 0x08a9ed097b425eea, 0x18a44aaaaaaaaab3, 0x2493f57f57f57f5d, 0x126b8d0649a7f8d4,
+ 0x09d80ed7303b5ccc, 0x25b8bcf3cf3cf3d5, 0x2ce6ce63398ce638,
+ 0x0e31fad40a57eb59, 0x02a3529fd4a7f52f,
+ 0x3a5493e93e93e94a,
+};
+
+#elif LIMB_BITS == 32
+
+/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */
+#define NTT_MOD_LOG2_MIN 29
+#define NTT_MOD_LOG2_MAX 30
+#define NB_MODS 5
+#define NTT_PROOT_2EXP 20
+static const int ntt_int_bits[NB_MODS] = { 148, 119, 89, 59, 29, };
+
+static const limb_t ntt_mods[NB_MODS] = { 0x0000000032b00001, 0x0000000033700001, 0x0000000036d00001, 0x0000000037300001, 0x000000003e500001,
+};
+
+static const limb_t ntt_proot[2][NB_MODS] = {
+    { 0x0000000032525f31, 0x0000000005eb3b37, 0x00000000246eda9f, 0x0000000035f25901, 0x00000000022f5768, },
+    { 0x00000000051eba1a, 0x00000000107be10e, 0x000000001cd574e0, 0x00000000053806e6, 0x000000002cd6bf98, },
+};
+
+static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = {
+ 0x000000000449559a, 0x000000001eba6ca9, 0x000000002ec18e46, 0x000000000860160b,
+ 0x000000000d321307, 0x000000000bf51120, 0x000000000f662938,
+ 0x000000000932ab3e, 0x000000002f40eef8,
+ 0x000000002e760905,
+};
+
+#endif /* LIMB_BITS */
+
+#endif /* !AVX2 */
+
+#if defined(__AVX2__)
+#define NTT_TRIG_K_MAX 18
+#else
+#define NTT_TRIG_K_MAX 19
+#endif
+
+typedef struct BFNTTState {
+    bf_context_t *ctx;
+    
+    /* used for mul_mod_fast() */
+    limb_t ntt_mods_div[NB_MODS];
+
+    limb_t ntt_proot_pow[NB_MODS][2][NTT_PROOT_2EXP + 1];
+    limb_t ntt_proot_pow_inv[NB_MODS][2][NTT_PROOT_2EXP + 1];
+    NTTLimb *ntt_trig[NB_MODS][2][NTT_TRIG_K_MAX + 1];
+    /* 1/2^n mod m */
+    limb_t ntt_len_inv[NB_MODS][NTT_PROOT_2EXP + 1][2];
+#if defined(__AVX2__)
+    __m256d ntt_mods_cr_vec[NB_MODS * (NB_MODS - 1) / 2];
+    __m256d ntt_mods_vec[NB_MODS];
+    __m256d ntt_mods_inv_vec[NB_MODS];
+#else
+    limb_t ntt_mods_cr_inv[NB_MODS * (NB_MODS - 1) / 2];
+#endif
+} BFNTTState;
+
+static NTTLimb *get_trig(BFNTTState *s, int k, int inverse, int m_idx);
+
+/* add modulo with up to (LIMB_BITS-1) bit modulo */
+static inline limb_t add_mod(limb_t a, limb_t b, limb_t m)
+{
+    limb_t r;
+    r = a + b;
+    if (r >= m)
+        r -= m;
+    return r;
+}
+
+/* sub modulo with up to LIMB_BITS bit modulo */
+static inline limb_t sub_mod(limb_t a, limb_t b, limb_t m)
+{
+    limb_t r;
+    r = a - b;
+    if (r > a)
+        r += m;
+    return r;
+}
+
+/* return (r0+r1*B) mod m 
+   precondition: 0 <= r0+r1*B < 2^(64+NTT_MOD_LOG2_MIN) 
+*/
+static inline limb_t mod_fast(dlimb_t r, 
+                                limb_t m, limb_t m_inv)
+{
+    limb_t a1, q, t0, r1, r0;
+    
+    a1 = r >> NTT_MOD_LOG2_MIN;
+    
+    q = ((dlimb_t)a1 * m_inv) >> LIMB_BITS;
+    r = r - (dlimb_t)q * m - m * 2;
+    r1 = r >> LIMB_BITS;
+    t0 = (slimb_t)r1 >> 1;
+    r += m & t0;
+    r0 = r;
+    r1 = r >> LIMB_BITS;
+    r0 += m & r1;
+    return r0;
+}
+
+/* faster version using precomputed modulo inverse. 
+   precondition: 0 <= a * b < 2^(64+NTT_MOD_LOG2_MIN) */
+static inline limb_t mul_mod_fast(limb_t a, limb_t b, 
+                                    limb_t m, limb_t m_inv)
+{
+    dlimb_t r;
+    r = (dlimb_t)a * (dlimb_t)b;
+    return mod_fast(r, m, m_inv);
+}
+
+static inline limb_t init_mul_mod_fast(limb_t m)
+{
+    dlimb_t t;
+    assert(m < (limb_t)1 << NTT_MOD_LOG2_MAX);
+    assert(m >= (limb_t)1 << NTT_MOD_LOG2_MIN);
+    t = (dlimb_t)1 << (LIMB_BITS + NTT_MOD_LOG2_MIN);
+    return t / m;
+}
+
+/* Faster version used when the multiplier is constant. 0 <= a < 2^64,
+   0 <= b < m. */
+static inline limb_t mul_mod_fast2(limb_t a, limb_t b, 
+                                     limb_t m, limb_t b_inv)
+{
+    limb_t r, q;
+
+    q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS;
+    r = a * b - q * m;
+    if (r >= m)
+        r -= m;
+    return r;
+}
+
+/* Faster version used when the multiplier is constant. 0 <= a < 2^64,
+   0 <= b < m. Let r = a * b mod m. The return value is 'r' or 'r +
+   m'. */
+static inline limb_t mul_mod_fast3(limb_t a, limb_t b, 
+                                     limb_t m, limb_t b_inv)
+{
+    limb_t r, q;
+
+    q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS;
+    r = a * b - q * m;
+    return r;
+}
+
+static inline limb_t init_mul_mod_fast2(limb_t b, limb_t m)
+{
+    return ((dlimb_t)b << LIMB_BITS) / m;
+}
+
+#ifdef __AVX2__
+
+static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m)
+{
+    slimb_t v;
+    v = a;
+    if (v < 0)
+        v += m;
+    if (v >= m)
+        v -= m;
+    return v;
+}
+
+static inline NTTLimb int_to_ntt_limb(limb_t a, limb_t m)
+{
+    return (slimb_t)a;
+}
+
+static inline NTTLimb int_to_ntt_limb2(limb_t a, limb_t m)
+{
+    if (a >= (m / 2))
+        a -= m;
+    return (slimb_t)a;
+}
+
+/* return r + m if r < 0 otherwise r. */
+static inline __m256d ntt_mod1(__m256d r, __m256d m)
+{
+    return _mm256_blendv_pd(r, r + m, r);
+}
+
+/* input: abs(r) < 2 * m. Output: abs(r) < m */
+static inline __m256d ntt_mod(__m256d r, __m256d mf, __m256d m2f)
+{
+    return _mm256_blendv_pd(r, r + m2f, r) - mf;
+}
+
+/* input: abs(a*b) < 2 * m^2, output: abs(r) < m */
+static inline __m256d ntt_mul_mod(__m256d a, __m256d b, __m256d mf,
+                                  __m256d m_inv)
+{
+    __m256d r, q, ab1, ab0, qm0, qm1;
+    ab1 = a * b;
+    q = _mm256_round_pd(ab1 * m_inv, 0); /* round to nearest */
+    qm1 = q * mf;
+    qm0 = _mm256_fmsub_pd(q, mf, qm1); /* low part */
+    ab0 = _mm256_fmsub_pd(a, b, ab1); /* low part */
+    r = (ab1 - qm1) + (ab0 - qm0);
+    return r;
+}
+
+static void *bf_aligned_malloc(bf_context_t *s, size_t size, size_t align)
+{
+    void *ptr;
+    void **ptr1;
+    ptr = bf_malloc(s, size + sizeof(void *) + align - 1);
+    if (!ptr)
+        return NULL;
+    ptr1 = (void **)(((uintptr_t)ptr + sizeof(void *) + align - 1) &
+                     ~(align - 1));
+    ptr1[-1] = ptr;
+    return ptr1;
+}
+
+static void bf_aligned_free(bf_context_t *s, void *ptr)
+{
+    if (!ptr)
+        return;
+    bf_free(s, ((void **)ptr)[-1]);
+}
+
+static void *ntt_malloc(BFNTTState *s, size_t size)
+{
+    return bf_aligned_malloc(s->ctx, size, 64);
+}
+
+static void ntt_free(BFNTTState *s, void *ptr)
+{
+    bf_aligned_free(s->ctx, ptr);
+}
+
+static no_inline void ntt_fft(BFNTTState *s,
+                              NTTLimb *out_buf, NTTLimb *in_buf,
+                              NTTLimb *tmp_buf, int fft_len_log2,
+                              int inverse, int m_idx)
+{
+    limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j;
+    NTTLimb *tab_in, *tab_out, *tmp, *trig;
+    __m256d m_inv, mf, m2f, c, a0, a1, b0, b1;
+    limb_t m;
+    int l;
+    
+    m = ntt_mods[m_idx];
+    
+    m_inv = _mm256_set1_pd(1.0 / (double)m);
+    mf = _mm256_set1_pd(m);
+    m2f = _mm256_set1_pd(m * 2);
+
+    n = (limb_t)1 << fft_len_log2;
+    assert(n >= 8);
+    stride_in = n / 2;
+
+    tab_in = in_buf;
+    tab_out = tmp_buf;
+    trig = get_trig(s, fft_len_log2, inverse, m_idx);
+    p = 0;
+    for(k = 0; k < stride_in; k += 4) {
+        a0 = _mm256_load_pd(&tab_in[k]);
+        a1 = _mm256_load_pd(&tab_in[k + stride_in]);
+        c = _mm256_load_pd(trig);
+        trig += 4;
+        b0 = ntt_mod(a0 + a1, mf, m2f);
+        b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv);
+        a0 = _mm256_permute2f128_pd(b0, b1, 0x20);
+        a1 = _mm256_permute2f128_pd(b0, b1, 0x31);
+        a0 = _mm256_permute4x64_pd(a0, 0xd8);
+        a1 = _mm256_permute4x64_pd(a1, 0xd8);
+        _mm256_store_pd(&tab_out[p], a0);
+        _mm256_store_pd(&tab_out[p + 4], a1);
+        p += 2 * 4;
+    }
+    tmp = tab_in;
+    tab_in = tab_out;
+    tab_out = tmp;
+
+    trig = get_trig(s, fft_len_log2 - 1, inverse, m_idx);
+    p = 0;
+    for(k = 0; k < stride_in; k += 4) {
+        a0 = _mm256_load_pd(&tab_in[k]);
+        a1 = _mm256_load_pd(&tab_in[k + stride_in]);
+        c = _mm256_setr_pd(trig[0], trig[0], trig[1], trig[1]);
+        trig += 2;
+        b0 = ntt_mod(a0 + a1, mf, m2f);
+        b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv);
+        a0 = _mm256_permute2f128_pd(b0, b1, 0x20);
+        a1 = _mm256_permute2f128_pd(b0, b1, 0x31);
+        _mm256_store_pd(&tab_out[p], a0);
+        _mm256_store_pd(&tab_out[p + 4], a1);
+        p += 2 * 4;
+    }
+    tmp = tab_in;
+    tab_in = tab_out;
+    tab_out = tmp;
+    
+    nb_blocks = n / 4;
+    fft_per_block = 4;
+
+    l = fft_len_log2 - 2;
+    while (nb_blocks != 2) {
+        nb_blocks >>= 1;
+        p = 0;
+        k = 0;
+        trig = get_trig(s, l, inverse, m_idx);
+        for(i = 0; i < nb_blocks; i++) {
+            c = _mm256_set1_pd(trig[0]);
+            trig++;
+            for(j = 0; j < fft_per_block; j += 4) {
+                a0 = _mm256_load_pd(&tab_in[k + j]);
+                a1 = _mm256_load_pd(&tab_in[k + j + stride_in]);
+                b0 = ntt_mod(a0 + a1, mf, m2f);
+                b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv);
+                _mm256_store_pd(&tab_out[p + j], b0);
+                _mm256_store_pd(&tab_out[p + j + fft_per_block], b1);
+            }
+            k += fft_per_block;
+            p += 2 * fft_per_block;
+        }
+        fft_per_block <<= 1;
+        l--;
+        tmp = tab_in;
+        tab_in = tab_out;
+        tab_out = tmp;
+    }
+
+    tab_out = out_buf;
+    for(k = 0; k < stride_in; k += 4) {
+        a0 = _mm256_load_pd(&tab_in[k]);
+        a1 = _mm256_load_pd(&tab_in[k + stride_in]);
+        b0 = ntt_mod(a0 + a1, mf, m2f);
+        b1 = ntt_mod(a0 - a1, mf, m2f);
+        _mm256_store_pd(&tab_out[k], b0);
+        _mm256_store_pd(&tab_out[k + stride_in], b1);
+    }
+}
+
+static void ntt_vec_mul(BFNTTState *s,
+                        NTTLimb *tab1, NTTLimb *tab2, limb_t fft_len_log2,
+                        int k_tot, int m_idx)
+{
+    limb_t i, c_inv, n, m;
+    __m256d m_inv, mf, a, b, c;
+    
+    m = ntt_mods[m_idx];
+    c_inv = s->ntt_len_inv[m_idx][k_tot][0];
+    m_inv = _mm256_set1_pd(1.0 / (double)m);
+    mf = _mm256_set1_pd(m);
+    c = _mm256_set1_pd(int_to_ntt_limb(c_inv, m));
+    n = (limb_t)1 << fft_len_log2;
+    for(i = 0; i < n; i += 4) {
+        a = _mm256_load_pd(&tab1[i]);
+        b = _mm256_load_pd(&tab2[i]);
+        a = ntt_mul_mod(a, b, mf, m_inv);
+        a = ntt_mul_mod(a, c, mf, m_inv);
+        _mm256_store_pd(&tab1[i], a);
+    }
+}
+
+static no_inline void mul_trig(NTTLimb *buf,
+                               limb_t n, limb_t c1, limb_t m, limb_t m_inv1)
+{
+    limb_t i, c2, c3, c4;
+    __m256d c, c_mul, a0, mf, m_inv;
+    assert(n >= 2);
+    
+    mf = _mm256_set1_pd(m);
+    m_inv = _mm256_set1_pd(1.0 / (double)m);
+
+    c2 = mul_mod_fast(c1, c1, m, m_inv1);
+    c3 = mul_mod_fast(c2, c1, m, m_inv1);
+    c4 = mul_mod_fast(c2, c2, m, m_inv1);
+    c = _mm256_setr_pd(1, int_to_ntt_limb(c1, m),
+                       int_to_ntt_limb(c2, m), int_to_ntt_limb(c3, m));
+    c_mul = _mm256_set1_pd(int_to_ntt_limb(c4, m));
+    for(i = 0; i < n; i += 4) {
+        a0 = _mm256_load_pd(&buf[i]);
+        a0 = ntt_mul_mod(a0, c, mf, m_inv);
+        _mm256_store_pd(&buf[i], a0);
+        c = ntt_mul_mod(c, c_mul, mf, m_inv);
+    }
+}
+
+#else
+
+static void *ntt_malloc(BFNTTState *s, size_t size)
+{
+    return bf_malloc(s->ctx, size);
+}
+
+static void ntt_free(BFNTTState *s, void *ptr)
+{
+    bf_free(s->ctx, ptr);
+}
+
+static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m)
+{
+    if (a >= m)
+        a -= m;
+    return a;
+}
+
+static inline NTTLimb int_to_ntt_limb(slimb_t a, limb_t m)
+{
+    return a;
+}
+
+static no_inline void ntt_fft(BFNTTState *s, NTTLimb *out_buf, NTTLimb *in_buf,
+                              NTTLimb *tmp_buf, int fft_len_log2,
+                              int inverse, int m_idx)
+{
+    limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j, m, m2;
+    NTTLimb *tab_in, *tab_out, *tmp, a0, a1, b0, b1, c, *trig, c_inv;
+    int l;
+    
+    m = ntt_mods[m_idx];
+    m2 = 2 * m;
+    n = (limb_t)1 << fft_len_log2;
+    nb_blocks = n;
+    fft_per_block = 1;
+    stride_in = n / 2;
+    tab_in = in_buf;
+    tab_out = tmp_buf;
+    l = fft_len_log2;
+    while (nb_blocks != 2) {
+        nb_blocks >>= 1;
+        p = 0;
+        k = 0;
+        trig = get_trig(s, l, inverse, m_idx);
+        for(i = 0; i < nb_blocks; i++) {
+            c = trig[0];
+            c_inv = trig[1];
+            trig += 2;
+            for(j = 0; j < fft_per_block; j++) {
+                a0 = tab_in[k + j];
+                a1 = tab_in[k + j + stride_in];
+                b0 = add_mod(a0, a1, m2);
+                b1 = a0 - a1 + m2;
+                b1 = mul_mod_fast3(b1, c, m, c_inv);
+                tab_out[p + j] = b0;
+                tab_out[p + j + fft_per_block] = b1;
+            }
+            k += fft_per_block;
+            p += 2 * fft_per_block;
+        }
+        fft_per_block <<= 1;
+        l--;
+        tmp = tab_in;
+        tab_in = tab_out;
+        tab_out = tmp;
+    }
+    /* no twiddle in last step */
+    tab_out = out_buf; 
+    for(k = 0; k < stride_in; k++) {
+        a0 = tab_in[k];
+        a1 = tab_in[k + stride_in];
+        b0 = add_mod(a0, a1, m2);
+        b1 = sub_mod(a0, a1, m2);
+        tab_out[k] = b0;
+        tab_out[k + stride_in] = b1;
+    }
+}
+
+static void ntt_vec_mul(BFNTTState *s,
+                        NTTLimb *tab1, NTTLimb *tab2, int fft_len_log2,
+                        int k_tot, int m_idx)
+{
+    limb_t i, norm, norm_inv, a, n, m, m_inv;
+    
+    m = ntt_mods[m_idx];
+    m_inv = s->ntt_mods_div[m_idx];
+    norm = s->ntt_len_inv[m_idx][k_tot][0];
+    norm_inv = s->ntt_len_inv[m_idx][k_tot][1];
+    n = (limb_t)1 << fft_len_log2;
+    for(i = 0; i < n; i++) {
+        a = tab1[i];
+        /* need to reduce the range so that the product is <
+           2^(LIMB_BITS+NTT_MOD_LOG2_MIN) */
+        if (a >= m)
+            a -= m;
+        a = mul_mod_fast(a, tab2[i], m, m_inv);
+        a = mul_mod_fast3(a, norm, m, norm_inv);
+        tab1[i] = a;
+    }
+}
+
+static no_inline void mul_trig(NTTLimb *buf,
+                               limb_t n, limb_t c_mul, limb_t m, limb_t m_inv)
+{
+    limb_t i, c0, c_mul_inv;
+    
+    c0 = 1;
+    c_mul_inv = init_mul_mod_fast2(c_mul, m);
+    for(i = 0; i < n; i++) {
+        buf[i] = mul_mod_fast(buf[i], c0, m, m_inv);
+        c0 = mul_mod_fast2(c0, c_mul, m, c_mul_inv);
+    }
+}
+
+#endif /* !AVX2 */
+
+static no_inline NTTLimb *get_trig(BFNTTState *s,
+                                   int k, int inverse1, int m_idx1)
+{
+    NTTLimb *tab;
+    limb_t i, n2, c, c_mul, m, c_mul_inv;
+    int m_idx, inverse;
+    
+    if (k > NTT_TRIG_K_MAX)
+        return NULL;
+
+    for(;;) {
+        tab = s->ntt_trig[m_idx1][inverse1][k];
+        if (tab)
+            return tab;
+        n2 = (limb_t)1 << (k - 1);
+        for(m_idx = 0; m_idx < NB_MODS; m_idx++) {
+            m = ntt_mods[m_idx];
+            for(inverse = 0; inverse < 2; inverse++) {
+#ifdef __AVX2__
+                tab = ntt_malloc(s, sizeof(NTTLimb) * n2);
+#else
+                tab = ntt_malloc(s, sizeof(NTTLimb) * n2 * 2);
+#endif
+                c = 1;
+                c_mul = s->ntt_proot_pow[m_idx][inverse][k];
+                c_mul_inv = s->ntt_proot_pow_inv[m_idx][inverse][k];
+                for(i = 0; i < n2; i++) {
+#ifdef __AVX2__
+                    tab[i] = int_to_ntt_limb2(c, m);
+#else
+                    tab[2 * i] = int_to_ntt_limb(c, m);
+                    tab[2 * i + 1] = init_mul_mod_fast2(c, m);
+#endif
+                    c = mul_mod_fast2(c, c_mul, m, c_mul_inv);
+                }
+                s->ntt_trig[m_idx][inverse][k] = tab;
+            }
+        }
+    }
+}
+
+void fft_clear_cache(bf_context_t *s1)
+{
+    int m_idx, inverse, k;
+    BFNTTState *s = s1->ntt_state;
+    if (s) {
+        for(m_idx = 0; m_idx < NB_MODS; m_idx++) {
+            for(inverse = 0; inverse < 2; inverse++) {
+                for(k = 0; k < NTT_TRIG_K_MAX + 1; k++) {
+                    if (s->ntt_trig[m_idx][inverse][k]) {
+                        ntt_free(s, s->ntt_trig[m_idx][inverse][k]);
+                        s->ntt_trig[m_idx][inverse][k] = NULL;
+                    }
+                }
+            }
+        }
+#if defined(__AVX2__)
+        bf_aligned_free(s1, s);
+#else
+        bf_free(s1, s);
+#endif
+        s1->ntt_state = NULL;
+    }
+}
+
+#define STRIP_LEN 16
+
+/* dst = buf1, src = buf2 */
+static void ntt_fft_partial(BFNTTState *s, NTTLimb *buf1,
+                            int k1, int k2, limb_t n1, limb_t n2, int inverse,
+                            limb_t m_idx)
+{
+    limb_t i, j, c_mul, c0, m, m_inv, strip_len, l;
+    NTTLimb *buf2, *buf3;
+    
+    buf3 = ntt_malloc(s, sizeof(NTTLimb) * n1);
+    if (k2 == 0) {
+        ntt_fft(s, buf1, buf1, buf3, k1, inverse, m_idx);
+    } else {
+        strip_len = STRIP_LEN;
+        buf2 = ntt_malloc(s, sizeof(NTTLimb) * n1 * strip_len);
+
+        m = ntt_mods[m_idx];
+        m_inv = s->ntt_mods_div[m_idx];
+        c0 = s->ntt_proot_pow[m_idx][inverse][k1 + k2];
+        c_mul = 1;
+        assert((n2 % strip_len) == 0);
+        for(j = 0; j < n2; j += strip_len) {
+            for(i = 0; i < n1; i++) {
+                for(l = 0; l < strip_len; l++) {
+                    buf2[i + l * n1] = buf1[i * n2 + (j + l)];
+                }
+            }
+            for(l = 0; l < strip_len; l++) {
+                if (inverse)
+                    mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv);
+                ntt_fft(s, buf2 + l * n1, buf2 + l * n1, buf3, k1, inverse, m_idx);
+                if (!inverse)
+                    mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv);
+                c_mul = mul_mod_fast(c_mul, c0, m, m_inv);
+            }
+            
+            for(i = 0; i < n1; i++) {
+                for(l = 0; l < strip_len; l++) {
+                    buf1[i * n2 + (j + l)] = buf2[i + l *n1];
+                }
+            }
+        }
+        ntt_free(s, buf2);
+    }
+    ntt_free(s, buf3);
+}
+
+
+/* dst = buf1, src = buf2, tmp = buf3 */
+static void ntt_conv(BFNTTState *s, NTTLimb *buf1, NTTLimb *buf2,
+                     int k, int k_tot, limb_t m_idx)
+{
+    limb_t n1, n2, i;
+    int k1, k2;
+    
+    if (k <= NTT_TRIG_K_MAX) {
+        k1 = k;
+    } else {
+        /* recursive split of the FFT */
+        k1 = bf_min(k / 2, NTT_TRIG_K_MAX);
+    }
+    k2 = k - k1;
+    n1 = (limb_t)1 << k1;
+    n2 = (limb_t)1 << k2;
+    
+    ntt_fft_partial(s, buf1, k1, k2, n1, n2, 0, m_idx);
+    ntt_fft_partial(s, buf2, k1, k2, n1, n2, 0, m_idx);
+    if (k2 == 0) {
+        ntt_vec_mul(s, buf1, buf2, k, k_tot, m_idx);
+    } else {
+        for(i = 0; i < n1; i++) {
+            ntt_conv(s, buf1 + i * n2, buf2 + i * n2, k2, k_tot, m_idx);
+        }
+    }
+    ntt_fft_partial(s, buf1, k1, k2, n1, n2, 1, m_idx);
+}
+
+
+static no_inline void limb_to_ntt(BFNTTState *s,
+                                  NTTLimb *tabr, limb_t fft_len,
+                                  const limb_t *taba, limb_t a_len, int dpl,
+                                  int first_m_idx, int nb_mods)
+{
+    slimb_t i, n;
+    dlimb_t a, b;
+    int j, shift;
+    limb_t base_mask1, a0, a1, a2, r, m, m_inv;
+    
+#if 0
+    for(i = 0; i < a_len; i++) {
+        printf("%" PRId64 ": " FMT_LIMB "\n",
+               (int64_t)i, taba[i]);
+    }
+#endif   
+    memset(tabr, 0, sizeof(NTTLimb) * fft_len * nb_mods);
+    shift = dpl & (LIMB_BITS - 1);
+    if (shift == 0)
+        base_mask1 = -1;
+    else
+        base_mask1 = ((limb_t)1 << shift) - 1;
+    n = bf_min(fft_len, (a_len * LIMB_BITS + dpl - 1) / dpl);
+    for(i = 0; i < n; i++) {
+        a0 = get_bits(taba, a_len, i * dpl);
+        if (dpl <= LIMB_BITS) {
+            a0 &= base_mask1;
+            a = a0;
+        } else {
+            a1 = get_bits(taba, a_len, i * dpl + LIMB_BITS);
+            if (dpl <= (LIMB_BITS + NTT_MOD_LOG2_MIN)) {
+                a = a0 | ((dlimb_t)(a1 & base_mask1) << LIMB_BITS);
+            } else {
+                if (dpl > 2 * LIMB_BITS) {
+                    a2 = get_bits(taba, a_len, i * dpl + LIMB_BITS * 2) &
+                        base_mask1;
+                } else {
+                    a1 &= base_mask1;
+                    a2 = 0;
+                }
+                //            printf("a=0x%016lx%016lx%016lx\n", a2, a1, a0);
+                a = (a0 >> (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) |
+                    ((dlimb_t)a1 << (NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)) |
+                    ((dlimb_t)a2 << (LIMB_BITS + NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN));
+                a0 &= ((limb_t)1 << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) - 1;
+            }
+        }
+        for(j = 0; j < nb_mods; j++) {
+            m = ntt_mods[first_m_idx + j];
+            m_inv = s->ntt_mods_div[first_m_idx + j];
+            r = mod_fast(a, m, m_inv);
+            if (dpl > (LIMB_BITS + NTT_MOD_LOG2_MIN)) {
+                b = ((dlimb_t)r << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | a0;
+                r = mod_fast(b, m, m_inv);
+            }
+            tabr[i + j * fft_len] = int_to_ntt_limb(r, m);
+        }
+    }
+}
+
+#if defined(__AVX2__)
+
+#define VEC_LEN 4
+
+typedef union {
+    __m256d v;
+    double d[4];
+} VecUnion;
+
+static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len,
+                                  const NTTLimb *buf, int fft_len_log2, int dpl,
+                                  int nb_mods)
+{
+    const limb_t *mods = ntt_mods + NB_MODS - nb_mods;
+    const __m256d *mods_cr_vec, *mf, *m_inv;
+    VecUnion y[NB_MODS];
+    limb_t u[NB_MODS], carry[NB_MODS], fft_len, base_mask1, r;
+    slimb_t i, len, pos;
+    int j, k, l, shift, n_limb1, p;
+    dlimb_t t;
+        
+    j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2;
+    mods_cr_vec = s->ntt_mods_cr_vec + j;
+    mf = s->ntt_mods_vec + NB_MODS - nb_mods;
+    m_inv = s->ntt_mods_inv_vec + NB_MODS - nb_mods;
+        
+    shift = dpl & (LIMB_BITS - 1);
+    if (shift == 0)
+        base_mask1 = -1;
+    else
+        base_mask1 = ((limb_t)1 << shift) - 1;
+    n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS;
+    for(j = 0; j < NB_MODS; j++) 
+        carry[j] = 0;
+    for(j = 0; j < NB_MODS; j++) 
+        u[j] = 0; /* avoid warnings */
+    memset(tabr, 0, sizeof(limb_t) * r_len);
+    fft_len = (limb_t)1 << fft_len_log2;
+    len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl);
+    len = (len + VEC_LEN - 1) & ~(VEC_LEN - 1);
+    i = 0;
+    while (i < len) {
+        for(j = 0; j < nb_mods; j++)
+            y[j].v = *(__m256d *)&buf[i + fft_len * j];
+
+        /* Chinese remainder to get mixed radix representation */
+        l = 0;
+        for(j = 0; j < nb_mods - 1; j++) {
+            y[j].v = ntt_mod1(y[j].v, mf[j]);
+            for(k = j + 1; k < nb_mods; k++) {
+                y[k].v = ntt_mul_mod(y[k].v - y[j].v,
+                                     mods_cr_vec[l], mf[k], m_inv[k]);
+                l++;
+            }
+        }
+        y[j].v = ntt_mod1(y[j].v, mf[j]);
+        
+        for(p = 0; p < VEC_LEN; p++) {
+            /* back to normal representation */
+            u[0] = (int64_t)y[nb_mods - 1].d[p];
+            l = 1;
+            for(j = nb_mods - 2; j >= 1; j--) {
+                r = (int64_t)y[j].d[p];
+                for(k = 0; k < l; k++) {
+                    t = (dlimb_t)u[k] * mods[j] + r;
+                    r = t >> LIMB_BITS;
+                    u[k] = t;
+                }
+                u[l] = r;
+                l++;
+            }
+            /* XXX: for nb_mods = 5, l should be 4 */
+            
+            /* last step adds the carry */
+            r = (int64_t)y[0].d[p];
+            for(k = 0; k < l; k++) {
+                t = (dlimb_t)u[k] * mods[j] + r + carry[k];
+                r = t >> LIMB_BITS;
+                u[k] = t;
+            }
+            u[l] = r + carry[l];
+
+#if 0
+            printf("%" PRId64 ": ", i);
+            for(j = nb_mods - 1; j >= 0; j--) {
+                printf(" %019" PRIu64, u[j]);
+            }
+            printf("\n");
+#endif
+            
+            /* write the digits */
+            pos = i * dpl;
+            for(j = 0; j < n_limb1; j++) {
+                put_bits(tabr, r_len, pos, u[j]);
+                pos += LIMB_BITS;
+            }
+            put_bits(tabr, r_len, pos, u[n_limb1] & base_mask1);
+            /* shift by dpl digits and set the carry */
+            if (shift == 0) {
+                for(j = n_limb1 + 1; j < nb_mods; j++)
+                    carry[j - (n_limb1 + 1)] = u[j];
+            } else {
+                for(j = n_limb1; j < nb_mods - 1; j++) {
+                    carry[j - n_limb1] = (u[j] >> shift) |
+                        (u[j + 1] << (LIMB_BITS - shift));
+                }
+                carry[nb_mods - 1 - n_limb1] = u[nb_mods - 1] >> shift;
+            }
+            i++;
+        }
+    }
+}
+#else
+static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len,
+                                  const NTTLimb *buf, int fft_len_log2, int dpl,
+                                  int nb_mods)
+{
+    const limb_t *mods = ntt_mods + NB_MODS - nb_mods;
+    const limb_t *mods_cr, *mods_cr_inv;
+    limb_t y[NB_MODS], u[NB_MODS], carry[NB_MODS], fft_len, base_mask1, r;
+    slimb_t i, len, pos;
+    int j, k, l, shift, n_limb1;
+    dlimb_t t;
+        
+    j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2;
+    mods_cr = ntt_mods_cr + j;
+    mods_cr_inv = s->ntt_mods_cr_inv + j;
+
+    shift = dpl & (LIMB_BITS - 1);
+    if (shift == 0)
+        base_mask1 = -1;
+    else
+        base_mask1 = ((limb_t)1 << shift) - 1;
+    n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS;
+    for(j = 0; j < NB_MODS; j++) 
+        carry[j] = 0;
+    for(j = 0; j < NB_MODS; j++) 
+        u[j] = 0; /* avoid warnings */
+    memset(tabr, 0, sizeof(limb_t) * r_len);
+    fft_len = (limb_t)1 << fft_len_log2;
+    len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl);
+    for(i = 0; i < len; i++) {
+        for(j = 0; j < nb_mods; j++)  {
+            y[j] = ntt_limb_to_int(buf[i + fft_len * j], mods[j]);
+        }
+
+        /* Chinese remainder to get mixed radix representation */
+        l = 0;
+        for(j = 0; j < nb_mods - 1; j++) {
+            for(k = j + 1; k < nb_mods; k++) {
+                limb_t m;
+                m = mods[k];
+                /* Note: there is no overflow in the sub_mod() because
+                   the modulos are sorted by increasing order */
+                y[k] = mul_mod_fast2(y[k] - y[j] + m, 
+                                     mods_cr[l], m, mods_cr_inv[l]);
+                l++;
+            }
+        }
+        
+        /* back to normal representation */
+        u[0] = y[nb_mods - 1];
+        l = 1;
+        for(j = nb_mods - 2; j >= 1; j--) {
+            r = y[j];
+            for(k = 0; k < l; k++) {
+                t = (dlimb_t)u[k] * mods[j] + r;
+                r = t >> LIMB_BITS;
+                u[k] = t;
+            }
+            u[l] = r;
+            l++;
+        }
+        
+        /* last step adds the carry */
+        r = y[0];
+        for(k = 0; k < l; k++) {
+            t = (dlimb_t)u[k] * mods[j] + r + carry[k];
+            r = t >> LIMB_BITS;
+            u[k] = t;
+        }
+        u[l] = r + carry[l];
+
+#if 0
+        printf("%" PRId64 ": ", (int64_t)i);
+        for(j = nb_mods - 1; j >= 0; j--) {
+            printf(" " FMT_LIMB, u[j]);
+        }
+        printf("\n");
+#endif
+        
+        /* write the digits */
+        pos = i * dpl;
+        for(j = 0; j < n_limb1; j++) {
+            put_bits(tabr, r_len, pos, u[j]);
+            pos += LIMB_BITS;
+        }
+        put_bits(tabr, r_len, pos, u[n_limb1] & base_mask1);
+        /* shift by dpl digits and set the carry */
+        if (shift == 0) {
+            for(j = n_limb1 + 1; j < nb_mods; j++)
+                carry[j - (n_limb1 + 1)] = u[j];
+        } else {
+            for(j = n_limb1; j < nb_mods - 1; j++) {
+                carry[j - n_limb1] = (u[j] >> shift) |
+                    (u[j + 1] << (LIMB_BITS - shift));
+            }
+            carry[nb_mods - 1 - n_limb1] = u[nb_mods - 1] >> shift;
+        }
+    }
+}
+#endif
+
+static int ntt_static_init(bf_context_t *s1)
+{
+    BFNTTState *s;
+    int inverse, i, j, k, l;
+    limb_t c, c_inv, c_inv2, m, m_inv;
+
+    if (s1->ntt_state)
+        return 0;
+#if defined(__AVX2__)
+    s = bf_aligned_malloc(s1, sizeof(*s), 64);
+#else
+    s = bf_malloc(s1, sizeof(*s));
+#endif
+    if (!s)
+        return -1;
+    memset(s, 0, sizeof(*s));
+    s1->ntt_state = s;
+    s->ctx = s1;
+    
+    for(j = 0; j < NB_MODS; j++) {
+        m = ntt_mods[j];
+        m_inv = init_mul_mod_fast(m);
+        s->ntt_mods_div[j] = m_inv;
+#if defined(__AVX2__)
+        s->ntt_mods_vec[j] = _mm256_set1_pd(m);
+        s->ntt_mods_inv_vec[j] = _mm256_set1_pd(1.0 / (double)m);
+#endif
+        c_inv2 = (m + 1) / 2; /* 1/2 */
+        c_inv = 1;
+        for(i = 0; i <= NTT_PROOT_2EXP; i++) {
+            s->ntt_len_inv[j][i][0] = c_inv;
+            s->ntt_len_inv[j][i][1] = init_mul_mod_fast2(c_inv, m);
+            c_inv = mul_mod_fast(c_inv, c_inv2, m, m_inv);
+        }
+
+        for(inverse = 0; inverse < 2; inverse++) {
+            c = ntt_proot[inverse][j];
+            for(i = 0; i < NTT_PROOT_2EXP; i++) {
+                s->ntt_proot_pow[j][inverse][NTT_PROOT_2EXP - i] = c;
+                s->ntt_proot_pow_inv[j][inverse][NTT_PROOT_2EXP - i] =
+                    init_mul_mod_fast2(c, m);
+                c = mul_mod_fast(c, c, m, m_inv);
+            }
+        }
+    }
+
+    l = 0;
+    for(j = 0; j < NB_MODS - 1; j++) {
+        for(k = j + 1; k < NB_MODS; k++) {
+#if defined(__AVX2__)
+            s->ntt_mods_cr_vec[l] = _mm256_set1_pd(int_to_ntt_limb2(ntt_mods_cr[l],
+                                                                    ntt_mods[k]));
+#else
+            s->ntt_mods_cr_inv[l] = init_mul_mod_fast2(ntt_mods_cr[l],
+                                                       ntt_mods[k]);
+#endif
+            l++;
+        }
+    }
+    return 0;
+}
+
+int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len)
+{
+    int dpl, fft_len_log2, n_bits, nb_mods, dpl_found, fft_len_log2_found;
+    int int_bits, nb_mods_found;
+    limb_t cost, min_cost;
+    
+    min_cost = -1;
+    dpl_found = 0;
+    nb_mods_found = 4;
+    fft_len_log2_found = 0;
+    for(nb_mods = 3; nb_mods <= NB_MODS; nb_mods++) {
+        int_bits = ntt_int_bits[NB_MODS - nb_mods];
+        dpl = bf_min((int_bits - 4) / 2,
+                     2 * LIMB_BITS + 2 * NTT_MOD_LOG2_MIN - NTT_MOD_LOG2_MAX);
+        for(;;) {
+            fft_len_log2 = ceil_log2((len * LIMB_BITS + dpl - 1) / dpl);
+            if (fft_len_log2 > NTT_PROOT_2EXP)
+                goto next;
+            n_bits = fft_len_log2 + 2 * dpl;
+            if (n_bits <= int_bits) {
+                cost = ((limb_t)(fft_len_log2 + 1) << fft_len_log2) * nb_mods;
+                //                printf("n=%d dpl=%d: cost=%" PRId64 "\n", nb_mods, dpl, (int64_t)cost);
+                if (cost < min_cost) {
+                    min_cost = cost;
+                    dpl_found = dpl;
+                    nb_mods_found = nb_mods;
+                    fft_len_log2_found = fft_len_log2;
+                }
+                break;
+            }
+            dpl--;
+            if (dpl == 0)
+                break;
+        }
+    next: ;
+    }
+    if (!dpl_found)
+        abort();
+    /* limit dpl if possible to reduce fixed cost of limb/NTT conversion */
+    if (dpl_found > (LIMB_BITS + NTT_MOD_LOG2_MIN) &&
+        ((limb_t)(LIMB_BITS + NTT_MOD_LOG2_MIN) << fft_len_log2_found) >=
+        len * LIMB_BITS) {
+        dpl_found = LIMB_BITS + NTT_MOD_LOG2_MIN;
+    }
+    *pnb_mods = nb_mods_found;
+    *pdpl = dpl_found;
+    return fft_len_log2_found;
+}
+
+static no_inline void fft_mul(bf_t *res, limb_t *a_tab, limb_t a_len,
+                              limb_t *b_tab, limb_t b_len, int mul_flags)
+{
+    bf_context_t *s1 = res->ctx;
+    BFNTTState *s;
+    int dpl, fft_len_log2, j, nb_mods, reduced_mem;
+    slimb_t len, fft_len;
+    NTTLimb *buf1, *buf2, *ptr;
+#if defined(USE_MUL_CHECK)
+    limb_t ha, hb, hr, h_ref;
+#endif
+    
+    ntt_static_init(s1);
+    s = s1->ntt_state;
+    
+    /* find the optimal number of digits per limb (dpl) */
+    len = a_len + b_len;
+    fft_len_log2 = bf_get_fft_size(&dpl, &nb_mods, len);
+    fft_len = (uint64_t)1 << fft_len_log2;
+    //    printf("len=%" PRId64 " fft_len_log2=%d dpl=%d\n", len, fft_len_log2, dpl);
+#if defined(USE_MUL_CHECK)
+    ha = mp_mod1(a_tab, a_len, BF_CHKSUM_MOD, 0);
+    hb = mp_mod1(b_tab, b_len, BF_CHKSUM_MOD, 0);
+#endif
+    if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == 0) {
+        bf_resize(res, 0);
+    } else if (mul_flags & FFT_MUL_R_OVERLAP_B) {
+        limb_t *tmp_tab, tmp_len;
+        /* it is better to free 'b' first */
+        tmp_tab = a_tab;
+        a_tab = b_tab;
+        b_tab = tmp_tab;
+        tmp_len = a_len;
+        a_len = b_len;
+        b_len = tmp_len;
+    }
+    buf1 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods);
+    limb_to_ntt(s, buf1, fft_len, a_tab, a_len, dpl,
+                NB_MODS - nb_mods, nb_mods);
+    if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == 
+        FFT_MUL_R_OVERLAP_A) {
+        bf_resize(res, 0);
+    }
+    reduced_mem = (fft_len_log2 >= 14);
+    if (!reduced_mem) {
+        buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods);
+        limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl,
+                    NB_MODS - nb_mods, nb_mods);
+        bf_resize(res, 0); /* in case res == b */
+    } else {
+        buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len);
+    }
+    for(j = 0; j < nb_mods; j++) {
+        if (reduced_mem) {
+            limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl,
+                        NB_MODS - nb_mods + j, 1);
+            ptr = buf2;
+        } else {
+            ptr = buf2 + fft_len * j;
+        }
+        ntt_conv(s, buf1 + fft_len * j, ptr,
+                 fft_len_log2, fft_len_log2, j + NB_MODS - nb_mods);
+    }
+    bf_resize(res, 0); /* in case res == b and reduced mem */
+    ntt_free(s, buf2);
+    bf_resize(res, len);
+    ntt_to_limb(s, res->tab, len, buf1, fft_len_log2, dpl, nb_mods);
+    ntt_free(s, buf1);
+#if defined(USE_MUL_CHECK)
+    hr = mp_mod1(res->tab, len, BF_CHKSUM_MOD, 0);
+    h_ref = mul_mod(ha, hb, BF_CHKSUM_MOD);
+    if (hr != h_ref) {
+        printf("ntt_mul_error: len=%" PRId_LIMB " fft_len_log2=%d dpl=%d nb_mods=%d\n",
+               len, fft_len_log2, dpl, nb_mods);
+        //        printf("ha=0x" FMT_LIMB" hb=0x" FMT_LIMB " hr=0x" FMT_LIMB " expected=0x" FMT_LIMB "\n", ha, hb, hr, h_ref);
+        exit(1);
+    }
+#endif    
+}
+
+#else /* USE_FFT_MUL */
+
+int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len)
+{
+    return 0;
+}
+
+#endif /* !USE_FFT_MUL */
diff --git a/libbf.h b/libbf.h
new file mode 100644
index 0000000..e3ea060
--- /dev/null
+++ b/libbf.h
@@ -0,0 +1,352 @@
+/*
+ * Tiny arbitrary precision floating point library
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef LIBBF_H
+#define LIBBF_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__x86_64__)
+#define LIMB_LOG2_BITS 6
+#else
+#define LIMB_LOG2_BITS 5
+#endif
+
+#define LIMB_BITS (1 << LIMB_LOG2_BITS)
+
+#if LIMB_BITS == 64
+typedef __int128 int128_t;
+typedef unsigned __int128 uint128_t;
+typedef int64_t slimb_t;
+typedef uint64_t limb_t;
+typedef uint128_t dlimb_t;
+#define EXP_MIN INT64_MIN
+#define EXP_MAX INT64_MAX
+
+#else
+
+typedef int32_t slimb_t;
+typedef uint32_t limb_t;
+typedef uint64_t dlimb_t;
+#define EXP_MIN INT32_MIN
+#define EXP_MAX INT32_MAX
+
+#endif
+
+/* in bits */
+#define BF_EXP_BITS_MIN 3
+#define BF_EXP_BITS_MAX (LIMB_BITS - 2)
+#define BF_PREC_MIN 2
+#define BF_PREC_MAX (((limb_t)1 << BF_EXP_BITS_MAX) - 2)
+#define BF_PREC_INF (BF_PREC_MAX + 1) /* infinite precision */
+
+#if LIMB_BITS == 64
+#define BF_CHKSUM_MOD (UINT64_C(975620677) * UINT64_C(9795002197))
+#else
+#define BF_CHKSUM_MOD 975620677U
+#endif
+
+#define BF_EXP_ZERO EXP_MIN
+#define BF_EXP_INF (EXP_MAX - 1)
+#define BF_EXP_NAN EXP_MAX
+
+/* +/-zero is represented with expn = BF_EXP_ZERO and len = 0,
+   +/-infinity is represented with expn = BF_EXP_INF and len = 0,
+   NaN is represented with expn = BF_EXP_NAN and len = 0 (sign is ignored)
+ */
+typedef struct {
+    struct bf_context_t *ctx;
+    int sign;
+    slimb_t expn;
+    limb_t len;
+    limb_t *tab;
+} bf_t;
+
+typedef enum {
+    BF_RNDN, /* round to nearest, ties to even */
+    BF_RNDZ, /* round to zero */
+    BF_RNDD, /* round to -inf */
+    BF_RNDU, /* round to +inf */
+    BF_RNDNA, /* round to nearest, ties away from zero */
+    BF_RNDNU, /* round to nearest, ties to +inf */
+    BF_RNDF, /* faithful rounding (nondeterministic, either RNDD or RNDU,
+                inexact flag is always set)  */
+} bf_rnd_t;
+
+/* allow subnormal numbers (only available if the number of exponent
+   bits is < BF_EXP_BITS_MAX and prec != BF_PREC_INF) */
+#define BF_FLAG_SUBNORMAL (1 << 3)
+
+#define BF_RND_MASK 0x7
+#define BF_EXP_BITS_SHIFT 4
+#define BF_EXP_BITS_MASK 0x3f
+
+/* contains the rounding mode and number of exponents bits */
+typedef uint32_t bf_flags_t;
+
+typedef void *bf_realloc_func_t(void *opaque, void *ptr, size_t size);
+
+typedef struct {
+    bf_t val;
+    limb_t prec;
+} BFConstCache;
+
+typedef struct bf_context_t {
+    void *realloc_opaque;
+    bf_realloc_func_t *realloc_func;
+    BFConstCache log2_cache;
+    BFConstCache pi_cache;
+    struct BFNTTState *ntt_state;
+} bf_context_t;
+
+static inline int bf_get_exp_bits(bf_flags_t flags)
+{
+    return BF_EXP_BITS_MAX - ((flags >> BF_EXP_BITS_SHIFT) & BF_EXP_BITS_MASK);
+}
+
+static inline bf_flags_t bf_set_exp_bits(int n)
+{
+    return (BF_EXP_BITS_MAX - n) << BF_EXP_BITS_SHIFT;
+}
+
+/* returned status */
+#define BF_ST_INVALID_OP  (1 << 0)
+#define BF_ST_DIVIDE_ZERO (1 << 1)
+#define BF_ST_OVERFLOW    (1 << 2)
+#define BF_ST_UNDERFLOW   (1 << 3)
+#define BF_ST_INEXACT     (1 << 4)
+/* not used yet, indicate that a memory allocation error occured. NaN
+   is returned */
+#define BF_ST_MEM_ERROR   (1 << 5) 
+
+#define BF_RADIX_MAX 36 /* maximum radix for bf_atof() and bf_ftoa() */
+
+static inline slimb_t bf_max(slimb_t a, slimb_t b)
+{
+    if (a > b)
+        return a;
+    else
+        return b;
+}
+
+static inline slimb_t bf_min(slimb_t a, slimb_t b)
+{
+    if (a < b)
+        return a;
+    else
+        return b;
+}
+
+void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func,
+                     void *realloc_opaque);
+void bf_context_end(bf_context_t *s);
+/* free memory allocated for the bf cache data */
+void bf_clear_cache(bf_context_t *s);
+
+static inline void *bf_realloc(bf_context_t *s, void *ptr, size_t size)
+{
+    return s->realloc_func(s->realloc_opaque, ptr, size);
+}
+
+void bf_init(bf_context_t *s, bf_t *r);
+
+static inline void bf_delete(bf_t *r)
+{
+    bf_context_t *s = r->ctx;
+    /* we accept to delete a zeroed bf_t structure */
+    if (s) {
+        bf_realloc(s, r->tab, 0);
+    }
+}
+
+static inline void bf_neg(bf_t *r)
+{
+    r->sign ^= 1;
+}
+
+static inline int bf_is_finite(const bf_t *a)
+{
+    return (a->expn < BF_EXP_INF);
+}
+
+static inline int bf_is_nan(const bf_t *a)
+{
+    return (a->expn == BF_EXP_NAN);
+}
+
+static inline int bf_is_zero(const bf_t *a)
+{
+    return (a->expn == BF_EXP_ZERO);
+}
+
+void bf_set_ui(bf_t *r, uint64_t a);
+void bf_set_si(bf_t *r, int64_t a);
+void bf_set_nan(bf_t *r);
+void bf_set_zero(bf_t *r, int is_neg);
+void bf_set_inf(bf_t *r, int is_neg);
+void bf_set(bf_t *r, const bf_t *a);
+void bf_move(bf_t *r, bf_t *a);
+int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode);
+void bf_set_float64(bf_t *a, double d);
+
+int bf_cmpu(const bf_t *a, const bf_t *b);
+int bf_cmp_full(const bf_t *a, const bf_t *b);
+int bf_cmp_eq(const bf_t *a, const bf_t *b);
+int bf_cmp_le(const bf_t *a, const bf_t *b);
+int bf_cmp_lt(const bf_t *a, const bf_t *b);
+int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags);
+int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags);
+int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, bf_flags_t flags);
+int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags);
+int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, bf_flags_t flags);
+int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, 
+              bf_flags_t flags);
+int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags);
+int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags);
+#define BF_DIVREM_EUCLIDIAN BF_RNDF
+int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b,
+              limb_t prec, bf_flags_t flags, int rnd_mode);
+int bf_fmod(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+            bf_flags_t flags);
+int bf_remainder(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+                 bf_flags_t flags);
+int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
+              bf_flags_t flags);
+int bf_pow_ui(bf_t *r, const bf_t *a, limb_t b, limb_t prec,
+              bf_flags_t flags);
+int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b, limb_t prec, bf_flags_t flags);
+int bf_rint(bf_t *r, limb_t prec, bf_flags_t flags);
+int bf_round(bf_t *r, limb_t prec, bf_flags_t flags);
+int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a);
+int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+slimb_t bf_get_exp_min(const bf_t *a);
+void bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b);
+void bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b);
+void bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b);
+
+/* additional flags for bf_atof */
+/* do not accept hex radix prefix (0x or 0X) if radix = 0 or radix = 16 */
+#define BF_ATOF_NO_HEX       (1 << 16)
+/* accept binary (0b or 0B) or octal (0o or 0O) radix prefix if radix = 0 */
+#define BF_ATOF_BIN_OCT      (1 << 17)
+/* Only accept integers (no decimal point, no exponent, no infinity nor NaN */
+#define BF_ATOF_INT_ONLY     (1 << 18)
+/* Do not accept radix prefix after sign  */
+#define BF_ATOF_NO_PREFIX_AFTER_SIGN (1 << 19)
+/* Do not parse NaN and parse case sensitive 'Infinity' */
+#define BF_ATOF_JS_QUIRKS    (1 << 20)
+/* Do not round integers to the indicated precision */
+#define BF_ATOF_INT_PREC_INF (1 << 21)
+/* Support legacy octal syntax for well formed numbers */
+#define BF_ATOF_LEGACY_OCTAL (1 << 22)
+/* accept _ between digits as a digit separator */
+#define BF_ATOF_UNDERSCORE_SEP (1 << 23)
+/* if a 'n' suffix is present, force integer parsing (XXX: remove) */
+#define BF_ATOF_INT_N_SUFFIX   (1 << 24)
+/* if set return NaN if empty number string (instead of 0) */
+#define BF_ATOF_NAN_IF_EMPTY   (1 << 25)
+/* only accept decimal floating point if radix = 0 */
+#define BF_ATOF_ONLY_DEC_FLOAT (1 << 26)
+
+/* additional return flags */
+/* indicate that the parsed number is an integer (only set when the
+   flags BF_ATOF_INT_PREC_INF or BF_ATOF_INT_N_SUFFIX are used) */
+#define BF_ATOF_ST_INTEGER   (1 << 5)
+/* integer parsed as legacy octal */
+#define BF_ATOF_ST_LEGACY_OCTAL (1 << 6)
+
+int bf_atof(bf_t *a, const char *str, const char **pnext, int radix,
+            limb_t prec, bf_flags_t flags);
+/* this version accepts prec = BF_PREC_INF and returns the radix
+   exponent */
+int bf_atof2(bf_t *r, slimb_t *pexponent,
+             const char *str, const char **pnext, int radix,
+             limb_t prec, bf_flags_t flags);
+int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix,
+                     slimb_t expn, limb_t prec, bf_flags_t flags);
+
+#define BF_FTOA_FORMAT_MASK (3 << 16)
+/* fixed format: prec significant digits rounded with (flags &
+   BF_RND_MASK). Exponential notation is used if too many zeros are
+   needed. */
+#define BF_FTOA_FORMAT_FIXED (0 << 16)
+/* fractional format: prec digits after the decimal point rounded with
+   (flags & BF_RND_MASK) */
+#define BF_FTOA_FORMAT_FRAC  (1 << 16)
+/* free format: use as many digits as necessary so that bf_atof()
+   return the same number when using precision 'prec', rounding to
+   nearest and the subnormal+exponent configuration of 'flags'. The
+   result is meaningful only if 'a' is already rounded to the wanted
+   precision.
+   
+   Infinite precision (BF_PREC_INF) is supported when the radix is a
+   power of two. */
+#define BF_FTOA_FORMAT_FREE  (2 << 16)
+/* same as BF_FTOA_FORMAT_FREE but uses the minimum number of digits
+   (takes more computation time). */
+#define BF_FTOA_FORMAT_FREE_MIN (3 << 16)
+
+/* force exponential notation for fixed or free format */
+#define BF_FTOA_FORCE_EXP    (1 << 20)
+/* add 0x prefix for base 16, 0o prefix for base 8 or 0b prefix for
+   base 2 if non zero value */
+#define BF_FTOA_ADD_PREFIX   (1 << 21)
+#define BF_FTOA_JS_QUIRKS    (1 << 22)
+
+size_t bf_ftoa(char **pbuf, const bf_t *a, int radix, limb_t prec,
+               bf_flags_t flags);
+
+/* modulo 2^n instead of saturation. NaN and infinity return 0 */
+#define BF_GET_INT_MOD (1 << 0) 
+int bf_get_int32(int *pres, const bf_t *a, int flags);
+int bf_get_int64(int64_t *pres, const bf_t *a, int flags);
+
+/* the following functions are exported for testing only. */
+void bf_print_str(const char *str, const bf_t *a);
+void bf_resize(bf_t *r, limb_t len);
+int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len);
+void bf_recip(bf_t *r, const bf_t *a, limb_t prec);
+void bf_rsqrt(bf_t *a, const bf_t *x, limb_t prec);
+int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags);
+int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k);
+slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv,
+                          int is_ceil1);
+
+/* transcendental functions */
+int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags);
+int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags);
+int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+#define BF_POW_JS_QUICKS (1 << 16)
+int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags);
+int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x,
+             limb_t prec, bf_flags_t flags);
+int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags);
+
+#endif /* LIBBF_H */
diff --git a/libregexp-opcode.h b/libregexp-opcode.h
new file mode 100644
index 0000000..f90c23b
--- /dev/null
+++ b/libregexp-opcode.h
@@ -0,0 +1,58 @@
+/*
+ * Regular Expression Engine
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifdef DEF
+
+DEF(invalid, 1) /* never used */
+DEF(char, 3)
+DEF(char32, 5)
+DEF(dot, 1)
+DEF(any, 1) /* same as dot but match any character including line terminator */
+DEF(line_start, 1)
+DEF(line_end, 1)
+DEF(goto, 5)
+DEF(split_goto_first, 5)
+DEF(split_next_first, 5)
+DEF(match, 1)
+DEF(save_start, 2) /* save start position */
+DEF(save_end, 2) /* save end position, must come after saved_start */
+DEF(save_reset, 3) /* reset save positions */
+DEF(loop, 5) /* decrement the top the stack and goto if != 0 */
+DEF(push_i32, 5) /* push integer on the stack */
+DEF(drop, 1)
+DEF(word_boundary, 1)
+DEF(not_word_boundary, 1)
+DEF(back_reference, 2)
+DEF(backward_back_reference, 2) /* must come after back_reference */
+DEF(range, 3) /* variable length */
+DEF(range32, 3) /* variable length */
+DEF(lookahead, 5)
+DEF(negative_lookahead, 5)
+DEF(push_char_pos, 1) /* push the character position on the stack */
+DEF(bne_char_pos, 5) /* pop one stack element and jump if equal to the character
+ position */
+DEF(prev, 1) /* go to the previous char */
+DEF(simple_greedy_quant, 17)
+
+#endif /* DEF */
diff --git a/libregexp.c b/libregexp.c
new file mode 100644
index 0000000..4425ce5
--- /dev/null
+++ b/libregexp.c
@@ -0,0 +1,2541 @@
+/*
+ * Regular Expression Engine
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+
+#include "cutils.h"
+#include "libregexp.h"
+
+/*
+  TODO:
+
+  - Add full unicode canonicalize rules for character ranges (not
+    really useful but needed for exact "ignorecase" compatibility).
+
+  - Add a lock step execution mode (=linear time execution guaranteed)
+    when the regular expression is "simple" i.e. no backreference nor
+    complicated lookahead. The opcodes are designed for this execution
+    model.
+*/
+
+#if defined(TEST)
+#define DUMP_REOP
+#endif
+
+typedef enum {
+#define DEF(id, size) REOP_ ## id,
+#include "libregexp-opcode.h"
+#undef DEF
+    REOP_COUNT,
+} REOPCodeEnum;
+
+#define CAPTURE_COUNT_MAX 255
+#define STACK_SIZE_MAX 255
+
+/* unicode code points */
+#define CP_LS   0x2028
+#define CP_PS   0x2029
+
+#define TMP_BUF_SIZE 128
+
+typedef struct {
+    DynBuf byte_code;
+    const uint8_t *buf_ptr;
+    const uint8_t *buf_end;
+    const uint8_t *buf_start;
+    int re_flags;
+    BOOL is_utf16;
+    BOOL ignore_case;
+    BOOL dotall;
+    int capture_count;
+    int total_capture_count; /* -1 = not computed yet */
+    int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */
+    void *mem_opaque;
+    DynBuf group_names;
+    union {
+        char error_msg[TMP_BUF_SIZE];
+        char tmp_buf[TMP_BUF_SIZE];
+    } u;
+} REParseState;
+
+typedef struct {
+#ifdef DUMP_REOP
+    const char *name;
+#endif
+    uint8_t size;
+} REOpCode;
+
+static const REOpCode reopcode_info[REOP_COUNT] = {
+#ifdef DUMP_REOP
+#define DEF(id, size) { #id, size },
+#else
+#define DEF(id, size) { size },
+#endif
+#include "libregexp-opcode.h"
+#undef DEF
+};
+
+#define RE_HEADER_FLAGS         0
+#define RE_HEADER_CAPTURE_COUNT 1
+#define RE_HEADER_STACK_SIZE    2
+
+#define RE_HEADER_LEN 7
+
+static inline int is_digit(int c) {
+    return c >= '0' && c <= '9';
+}
+
+/* insert 'len' bytes at position 'pos' */
+static void dbuf_insert(DynBuf *s, int pos, int len)
+{
+    dbuf_realloc(s, s->size + len);
+    memmove(s->buf + pos + len, s->buf + pos, s->size - pos);
+    s->size += len;
+}
+
+/* canonicalize with the specific JS regexp rules */
+static uint32_t lre_canonicalize(uint32_t c, BOOL is_utf16)
+{
+    uint32_t res[LRE_CC_RES_LEN_MAX];
+    int len;
+    if (is_utf16) {
+        if (likely(c < 128)) {
+            if (c >= 'A' && c <= 'Z')
+                c = c - 'A' + 'a';
+        } else {
+            lre_case_conv(res, c, 2);
+            c = res[0];
+        }
+    } else {
+        if (likely(c < 128)) {
+            if (c >= 'a' && c <= 'z')
+                c = c - 'a' + 'A';
+        } else {
+            /* legacy regexp: to upper case if single char >= 128 */
+            len = lre_case_conv(res, c, FALSE);
+            if (len == 1 && res[0] >= 128)
+                c = res[0];
+        }
+    }
+    return c;
+}
+
+static const uint16_t char_range_d[] = {
+    1,
+    0x0030, 0x0039 + 1,
+};
+
+/* code point ranges for Zs,Zl or Zp property */
+static const uint16_t char_range_s[] = {
+    10,
+    0x0009, 0x000D + 1,
+    0x0020, 0x0020 + 1,
+    0x00A0, 0x00A0 + 1,
+    0x1680, 0x1680 + 1,
+    0x2000, 0x200A + 1,
+    /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */
+    /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */
+    0x2028, 0x2029 + 1,
+    0x202F, 0x202F + 1,
+    0x205F, 0x205F + 1,
+    0x3000, 0x3000 + 1,
+    /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */
+    0xFEFF, 0xFEFF + 1,
+};
+
+BOOL lre_is_space(int c)
+{
+    int i, n, low, high;
+    n = (countof(char_range_s) - 1) / 2;
+    for(i = 0; i < n; i++) {
+        low = char_range_s[2 * i + 1];
+        if (c < low)
+            return FALSE;
+        high = char_range_s[2 * i + 2];
+        if (c < high)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+uint32_t const lre_id_start_table_ascii[4] = {
+    /* $ A-Z _ a-z */
+    0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE
+};
+
+uint32_t const lre_id_continue_table_ascii[4] = {
+    /* $ 0-9 A-Z _ a-z */
+    0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE
+};
+
+
+static const uint16_t char_range_w[] = {
+    4,
+    0x0030, 0x0039 + 1,
+    0x0041, 0x005A + 1,
+    0x005F, 0x005F + 1,
+    0x0061, 0x007A + 1,
+};
+
+#define CLASS_RANGE_BASE 0x40000000
+
+typedef enum {
+    CHAR_RANGE_d,
+    CHAR_RANGE_D,
+    CHAR_RANGE_s,
+    CHAR_RANGE_S,
+    CHAR_RANGE_w,
+    CHAR_RANGE_W,
+} CharRangeEnum;
+
+static const uint16_t *char_range_table[] = {
+    char_range_d,
+    char_range_s,
+    char_range_w,
+};
+
+static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c)
+{
+    BOOL invert;
+    const uint16_t *c_pt;
+    int len, i;
+    
+    invert = c & 1;
+    c_pt = char_range_table[c >> 1];
+    len = *c_pt++;
+    cr_init(cr, s->mem_opaque, lre_realloc);
+    for(i = 0; i < len * 2; i++) {
+        if (cr_add_point(cr, c_pt[i]))
+            goto fail;
+    }
+    if (invert) {
+        if (cr_invert(cr))
+            goto fail;
+    }
+    return 0;
+ fail:
+    cr_free(cr);
+    return -1;
+}
+
+static int cr_canonicalize(CharRange *cr)
+{
+    CharRange a;
+    uint32_t pt[2];
+    int i, ret;
+
+    cr_init(&a, cr->mem_opaque, lre_realloc);
+    pt[0] = 'a';
+    pt[1] = 'z' + 1;
+    ret = cr_op(&a, cr->points, cr->len, pt, 2, CR_OP_INTER);
+    if (ret)
+        goto fail;
+    /* convert to upper case */
+    /* XXX: the generic unicode case would be much more complicated
+       and not really useful */
+    for(i = 0; i < a.len; i++) {
+        a.points[i] += 'A' - 'a';
+    }
+    /* Note: for simplicity we keep the lower case ranges */
+    ret = cr_union1(cr, a.points, a.len);
+ fail:
+    cr_free(&a);
+    return ret;
+}
+
+#ifdef DUMP_REOP
+static __maybe_unused void lre_dump_bytecode(const uint8_t *buf,
+                                                     int buf_len)
+{
+    int pos, len, opcode, bc_len, re_flags, i;
+    uint32_t val;
+    
+    assert(buf_len >= RE_HEADER_LEN);
+
+    re_flags=  buf[0];
+    bc_len = get_u32(buf + 3);
+    assert(bc_len + RE_HEADER_LEN <= buf_len);
+    printf("flags: 0x%x capture_count=%d stack_size=%d\n",
+           re_flags, buf[1], buf[2]);
+    if (re_flags & LRE_FLAG_NAMED_GROUPS) {
+        const char *p;
+        p = (char *)buf + RE_HEADER_LEN + bc_len;
+        printf("named groups: ");
+        for(i = 1; i < buf[1]; i++) {
+            if (i != 1)
+                printf(",");
+            printf("<%s>", p);
+            p += strlen(p) + 1;
+        }
+        printf("\n");
+        assert(p == (char *)(buf + buf_len));
+    }
+    printf("bytecode_len=%d\n", bc_len);
+
+    buf += RE_HEADER_LEN;
+    pos = 0;
+    while (pos < bc_len) {
+        printf("%5u: ", pos);
+        opcode = buf[pos];
+        len = reopcode_info[opcode].size;
+        if (opcode >= REOP_COUNT) {
+            printf(" invalid opcode=0x%02x\n", opcode);
+            break;
+        }
+        if ((pos + len) > bc_len) {
+            printf(" buffer overflow (opcode=0x%02x)\n", opcode);
+            break;
+        }
+        printf("%s", reopcode_info[opcode].name);
+        switch(opcode) {
+        case REOP_char:
+            val = get_u16(buf + pos + 1);
+            if (val >= ' ' && val <= 126)
+                printf(" '%c'", val);
+            else
+                printf(" 0x%04x", val);
+            break;
+        case REOP_char32:
+            val = get_u32(buf + pos + 1);
+            if (val >= ' ' && val <= 126)
+                printf(" '%c'", val);
+            else
+                printf(" 0x%08x", val);
+            break;
+        case REOP_goto:
+        case REOP_split_goto_first:
+        case REOP_split_next_first:
+        case REOP_loop:
+        case REOP_lookahead:
+        case REOP_negative_lookahead:
+        case REOP_bne_char_pos:
+            val = get_u32(buf + pos + 1);
+            val += (pos + 5);
+            printf(" %u", val);
+            break;
+        case REOP_simple_greedy_quant:
+            printf(" %u %u %u %u",
+                   get_u32(buf + pos + 1) + (pos + 17),
+                   get_u32(buf + pos + 1 + 4),
+                   get_u32(buf + pos + 1 + 8),
+                   get_u32(buf + pos + 1 + 12));
+            break;
+        case REOP_save_start:
+        case REOP_save_end:
+        case REOP_back_reference:
+        case REOP_backward_back_reference:
+            printf(" %u", buf[pos + 1]);
+            break;
+        case REOP_save_reset:
+            printf(" %u %u", buf[pos + 1], buf[pos + 2]);
+            break;
+        case REOP_push_i32:
+            val = get_u32(buf + pos + 1);
+            printf(" %d", val);
+            break;
+        case REOP_range:
+            {
+                int n, i;
+                n = get_u16(buf + pos + 1);
+                len += n * 4;
+                for(i = 0; i < n * 2; i++) {
+                    val = get_u16(buf + pos + 3 + i * 2);
+                    printf(" 0x%04x", val);
+                }
+            }
+            break;
+        case REOP_range32:
+            {
+                int n, i;
+                n = get_u16(buf + pos + 1);
+                len += n * 8;
+                for(i = 0; i < n * 2; i++) {
+                    val = get_u32(buf + pos + 3 + i * 4);
+                    printf(" 0x%08x", val);
+                }
+            }
+            break;
+        default:
+            break;
+        }
+        printf("\n");
+        pos += len;
+    }
+}
+#endif
+
+static void re_emit_op(REParseState *s, int op)
+{
+    dbuf_putc(&s->byte_code, op);
+}
+
+/* return the offset of the u32 value */
+static int re_emit_op_u32(REParseState *s, int op, uint32_t val)
+{
+    int pos;
+    dbuf_putc(&s->byte_code, op);
+    pos = s->byte_code.size;
+    dbuf_put_u32(&s->byte_code, val);
+    return pos;
+}
+
+static int re_emit_goto(REParseState *s, int op, uint32_t val)
+{
+    int pos;
+    dbuf_putc(&s->byte_code, op);
+    pos = s->byte_code.size;
+    dbuf_put_u32(&s->byte_code, val - (pos + 4));
+    return pos;
+}
+
+static void re_emit_op_u8(REParseState *s, int op, uint32_t val)
+{
+    dbuf_putc(&s->byte_code, op);
+    dbuf_putc(&s->byte_code, val);
+}
+
+static void re_emit_op_u16(REParseState *s, int op, uint32_t val)
+{
+    dbuf_putc(&s->byte_code, op);
+    dbuf_put_u16(&s->byte_code, val);
+}
+
+static int __attribute__((format(printf, 2, 3))) re_parse_error(REParseState *s, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap);
+    va_end(ap);
+    return -1;
+}
+
+/* Return -1 in case of overflow */
+static int parse_digits(const uint8_t **pp)
+{
+    const uint8_t *p;
+    uint64_t v;
+    int c;
+    
+    p = *pp;
+    v = 0;
+    for(;;) {
+        c = *p;
+        if (c < '0' || c > '9')
+            break;
+        v = v * 10 + c - '0';
+        if (v >= INT32_MAX)
+            return -1;
+        p++;
+    }
+    *pp = p;
+    return v;
+}
+
+static int re_parse_expect(REParseState *s, const uint8_t **pp, int c)
+{
+    const uint8_t *p;
+    p = *pp;
+    if (*p != c)
+        return re_parse_error(s, "expecting '%c'", c);
+    p++;
+    *pp = p;
+    return 0;
+}
+
+/* Parse an escape sequence, *pp points after the '\':
+   allow_utf16 value:
+   0 : no UTF-16 escapes allowed
+   1 : UTF-16 escapes allowed
+   2 : UTF-16 escapes allowed and escapes of surrogate pairs are
+   converted to a unicode character (unicode regexp case).
+
+   Return the unicode char and update *pp if recognized,
+   return -1 if malformed escape,
+   return -2 otherwise. */
+int lre_parse_escape(const uint8_t **pp, int allow_utf16)
+{
+    const uint8_t *p;
+    uint32_t c;
+
+    p = *pp;
+    c = *p++;
+    switch(c) {
+    case 'b':
+        c = '\b';
+        break;
+    case 'f':
+        c = '\f';
+        break;
+    case 'n':
+        c = '\n';
+        break;
+    case 'r':
+        c = '\r';
+        break;
+    case 't':
+        c = '\t';
+        break;
+    case 'v':
+        c = '\v';
+        break;
+    case 'x':
+    case 'u':
+        {
+            int h, n, i;
+            uint32_t c1;
+            
+            if (*p == '{' && allow_utf16) {
+                p++;
+                c = 0;
+                for(;;) {
+                    h = from_hex(*p++);
+                    if (h < 0)
+                        return -1;
+                    c = (c << 4) | h;
+                    if (c > 0x10FFFF)
+                        return -1;
+                    if (*p == '}')
+                        break;
+                }
+                p++;
+            } else {
+                if (c == 'x') {
+                    n = 2;
+                } else {
+                    n = 4;
+                }
+
+                c = 0;
+                for(i = 0; i < n; i++) {
+                    h = from_hex(*p++);
+                    if (h < 0) {
+                        return -1;
+                    }
+                    c = (c << 4) | h;
+                }
+                if (c >= 0xd800 && c < 0xdc00 &&
+                    allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') {
+                    /* convert an escaped surrogate pair into a
+                       unicode char */
+                    c1 = 0;
+                    for(i = 0; i < 4; i++) {
+                        h = from_hex(p[2 + i]);
+                        if (h < 0)
+                            break;
+                        c1 = (c1 << 4) | h;
+                    }
+                    if (i == 4 && c1 >= 0xdc00 && c1 < 0xe000) {
+                        p += 6;
+                        c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
+                    }
+                }
+            }
+        }
+        break;
+    case '0' ... '7':
+        c -= '0';
+        if (allow_utf16 == 2) {
+            /* only accept \0 not followed by digit */
+            if (c != 0 || is_digit(*p))
+                return -1;
+        } else {
+            /* parse a legacy octal sequence */
+            uint32_t v;
+            v = *p - '0';
+            if (v > 7)
+                break;
+            c = (c << 3) | v;
+            p++;
+            if (c >= 32)
+                break;
+            v = *p - '0';
+            if (v > 7)
+                break;
+            c = (c << 3) | v;
+            p++;
+        }
+        break;
+    default:
+        return -2;
+    }
+    *pp = p;
+    return c;
+}
+
+#ifdef CONFIG_ALL_UNICODE
+/* XXX: we use the same chars for name and value */
+static BOOL is_unicode_char(int c)
+{
+    return ((c >= '0' && c <= '9') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c >= 'a' && c <= 'z') ||
+            (c == '_'));
+}
+
+static int parse_unicode_property(REParseState *s, CharRange *cr,
+                                  const uint8_t **pp, BOOL is_inv)
+{
+    const uint8_t *p;
+    char name[64], value[64];
+    char *q;
+    BOOL script_ext;
+    int ret;
+
+    p = *pp;
+    if (*p != '{')
+        return re_parse_error(s, "expecting '{' after \\p");
+    p++;
+    q = name;
+    while (is_unicode_char(*p)) {
+        if ((q - name) > sizeof(name) - 1)
+            goto unknown_property_name;
+        *q++ = *p++;
+    }
+    *q = '\0';
+    q = value;
+    if (*p == '=') {
+        p++;
+        while (is_unicode_char(*p)) {
+            if ((q - value) > sizeof(value) - 1)
+                return re_parse_error(s, "unknown unicode property value");
+            *q++ = *p++;
+        }
+    }
+    *q = '\0';
+    if (*p != '}')
+        return re_parse_error(s, "expecting '}'");
+    p++;
+    //    printf("name=%s value=%s\n", name, value);
+
+    if (!strcmp(name, "Script") || !strcmp(name, "sc")) {
+        script_ext = FALSE;
+        goto do_script;
+    } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) {
+        script_ext = TRUE;
+    do_script:
+        cr_init(cr, s->mem_opaque, lre_realloc);
+        ret = unicode_script(cr, value, script_ext);
+        if (ret) {
+            cr_free(cr);
+            if (ret == -2)
+                return re_parse_error(s, "unknown unicode script");
+            else
+                goto out_of_memory;
+        }
+    } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) {
+        cr_init(cr, s->mem_opaque, lre_realloc);
+        ret = unicode_general_category(cr, value);
+        if (ret) {
+            cr_free(cr);
+            if (ret == -2)
+                return re_parse_error(s, "unknown unicode general category");
+            else
+                goto out_of_memory;
+        }
+    } else if (value[0] == '\0') {
+        cr_init(cr, s->mem_opaque, lre_realloc);
+        ret = unicode_general_category(cr, name);
+        if (ret == -1) {
+            cr_free(cr);
+            goto out_of_memory;
+        }
+        if (ret < 0) {
+            ret = unicode_prop(cr, name);
+            if (ret) {
+                cr_free(cr);
+                if (ret == -2)
+                    goto unknown_property_name;
+                else
+                    goto out_of_memory;
+            }
+        }
+    } else {
+    unknown_property_name:
+        return re_parse_error(s, "unknown unicode property name");
+    }
+
+    if (is_inv) {
+        if (cr_invert(cr)) {
+            cr_free(cr);
+            return -1;
+        }
+    }
+    *pp = p;
+    return 0;
+ out_of_memory:
+    return re_parse_error(s, "out of memory");
+}
+#endif /* CONFIG_ALL_UNICODE */
+
+/* return -1 if error otherwise the character or a class range
+   (CLASS_RANGE_BASE). In case of class range, 'cr' is
+   initialized. Otherwise, it is ignored. */
+static int get_class_atom(REParseState *s, CharRange *cr,
+                          const uint8_t **pp, BOOL inclass)
+{
+    const uint8_t *p;
+    uint32_t c;
+    int ret;
+    
+    p = *pp;
+
+    c = *p;
+    switch(c) {
+    case '\\':
+        p++;
+        if (p >= s->buf_end)
+            goto unexpected_end;
+        c = *p++;
+        switch(c) {
+        case 'd':
+            c = CHAR_RANGE_d;
+            goto class_range;
+        case 'D':
+            c = CHAR_RANGE_D;
+            goto class_range;
+        case 's':
+            c = CHAR_RANGE_s;
+            goto class_range;
+        case 'S':
+            c = CHAR_RANGE_S;
+            goto class_range;
+        case 'w':
+            c = CHAR_RANGE_w;
+            goto class_range;
+        case 'W':
+            c = CHAR_RANGE_W;
+        class_range:
+            if (cr_init_char_range(s, cr, c))
+                return -1;
+            c = CLASS_RANGE_BASE;
+            break;
+        case 'c':
+            c = *p;
+            if ((c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (((c >= '0' && c <= '9') || c == '_') &&
+                 inclass && !s->is_utf16)) {   /* Annex B.1.4 */
+                c &= 0x1f;
+                p++;
+            } else if (s->is_utf16) {
+                goto invalid_escape;
+            } else {
+                /* otherwise return '\' and 'c' */
+                p--;
+                c = '\\';
+            }
+            break;
+#ifdef CONFIG_ALL_UNICODE
+        case 'p':
+        case 'P':
+            if (s->is_utf16) {
+                if (parse_unicode_property(s, cr, &p, (c == 'P')))
+                    return -1;
+                c = CLASS_RANGE_BASE;
+                break;
+            }
+            /* fall thru */
+#endif
+        default:
+            p--;
+            ret = lre_parse_escape(&p, s->is_utf16 * 2);
+            if (ret >= 0) {
+                c = ret;
+            } else {
+                if (ret == -2 && *p != '\0' && strchr("^$\\.*+?()[]{}|/", *p)) {
+                    /* always valid to escape these characters */
+                    goto normal_char;
+                } else if (s->is_utf16) {
+                invalid_escape:
+                    return re_parse_error(s, "invalid escape sequence in regular expression");
+                } else {
+                    /* just ignore the '\' */
+                    goto normal_char;
+                }
+            }
+            break;
+        }
+        break;
+    case '\0':
+        if (p >= s->buf_end) {
+        unexpected_end:
+            return re_parse_error(s, "unexpected end");
+        }
+        /* fall thru */
+    default:
+    normal_char:
+        /* normal char */
+        if (c >= 128) {
+            c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
+            if ((unsigned)c > 0xffff && !s->is_utf16) {
+                /* XXX: should handle non BMP-1 code points */
+                return re_parse_error(s, "malformed unicode char");
+            }
+        } else {
+            p++;
+        }
+        break;
+    }
+    *pp = p;
+    return c;
+}
+
+static int re_emit_range(REParseState *s, const CharRange *cr)
+{
+    int len, i;
+    uint32_t high;
+    
+    len = (unsigned)cr->len / 2;
+    if (len >= 65535)
+        return re_parse_error(s, "too many ranges");
+    if (len == 0) {
+        /* not sure it can really happen. Emit a match that is always
+           false */
+        re_emit_op_u32(s, REOP_char32, -1);
+    } else {
+        high = cr->points[cr->len - 1];
+        if (high == UINT32_MAX)
+            high = cr->points[cr->len - 2];
+        if (high <= 0xffff) {
+            /* can use 16 bit ranges with the conversion that 0xffff =
+               infinity */
+            re_emit_op_u16(s, REOP_range, len);
+            for(i = 0; i < cr->len; i += 2) {
+                dbuf_put_u16(&s->byte_code, cr->points[i]);
+                high = cr->points[i + 1] - 1;
+                if (high == UINT32_MAX - 1)
+                    high = 0xffff;
+                dbuf_put_u16(&s->byte_code, high);
+            }
+        } else {
+            re_emit_op_u16(s, REOP_range32, len);
+            for(i = 0; i < cr->len; i += 2) {
+                dbuf_put_u32(&s->byte_code, cr->points[i]);
+                dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1);
+            }
+        }
+    }
+    return 0;
+}
+
+static int re_parse_char_class(REParseState *s, const uint8_t **pp)
+{
+    const uint8_t *p;
+    uint32_t c1, c2;
+    CharRange cr_s, *cr = &cr_s;
+    CharRange cr1_s, *cr1 = &cr1_s;
+    BOOL invert;
+    
+    cr_init(cr, s->mem_opaque, lre_realloc);
+    p = *pp;
+    p++;    /* skip '[' */
+    invert = FALSE;
+    if (*p == '^') {
+        p++;
+        invert = TRUE;
+    }
+    for(;;) {
+        if (*p == ']')
+            break;
+        c1 = get_class_atom(s, cr1, &p, TRUE);
+        if ((int)c1 < 0)
+            goto fail;
+        if (*p == '-' && p[1] != ']') {
+            const uint8_t *p0 = p + 1;
+            if (c1 >= CLASS_RANGE_BASE) {
+                if (s->is_utf16) {
+                    cr_free(cr1);
+                    goto invalid_class_range;
+                }
+                /* Annex B: match '-' character */
+                goto class_atom;
+            }
+            c2 = get_class_atom(s, cr1, &p0, TRUE);
+            if ((int)c2 < 0)
+                goto fail;
+            if (c2 >= CLASS_RANGE_BASE) {
+                cr_free(cr1);
+                if (s->is_utf16) {
+                    goto invalid_class_range;
+                }
+                /* Annex B: match '-' character */
+                goto class_atom;
+            }
+            p = p0;
+            if (c2 < c1) {
+            invalid_class_range:
+                re_parse_error(s, "invalid class range");
+                goto fail;
+            }
+            if (cr_union_interval(cr, c1, c2))
+                goto memory_error;
+        } else {
+        class_atom:
+            if (c1 >= CLASS_RANGE_BASE) {
+                int ret;
+                ret = cr_union1(cr, cr1->points, cr1->len);
+                cr_free(cr1);
+                if (ret)
+                    goto memory_error;
+            } else {
+                if (cr_union_interval(cr, c1, c1))
+                    goto memory_error;
+            }
+        }
+    }
+    if (s->ignore_case) {
+        if (cr_canonicalize(cr))
+            goto memory_error;
+    }
+    if (invert) {
+        if (cr_invert(cr))
+            goto memory_error;
+    }
+    if (re_emit_range(s, cr))
+        goto fail;
+    cr_free(cr);
+    p++;    /* skip ']' */
+    *pp = p;
+    return 0;
+ memory_error:
+    re_parse_error(s, "out of memory");
+ fail:
+    cr_free(cr);
+    return -1;
+}
+
+/* Return:
+   1 if the opcodes in bc_buf[] always advance the character pointer.
+   0 if the character pointer may not be advanced.
+   -1 if the code may depend on side effects of its previous execution (backreference)
+*/
+static int re_check_advance(const uint8_t *bc_buf, int bc_buf_len)
+{
+    int pos, opcode, ret, len, i;
+    uint32_t val, last;
+    BOOL has_back_reference;
+    uint8_t capture_bitmap[CAPTURE_COUNT_MAX];
+    
+    ret = -2; /* not known yet */
+    pos = 0;
+    has_back_reference = FALSE;
+    memset(capture_bitmap, 0, sizeof(capture_bitmap));
+    
+    while (pos < bc_buf_len) {
+        opcode = bc_buf[pos];
+        len = reopcode_info[opcode].size;
+        switch(opcode) {
+        case REOP_range:
+            val = get_u16(bc_buf + pos + 1);
+            len += val * 4;
+            goto simple_char;
+        case REOP_range32:
+            val = get_u16(bc_buf + pos + 1);
+            len += val * 8;
+            goto simple_char;
+        case REOP_char:
+        case REOP_char32:
+        case REOP_dot:
+        case REOP_any:
+        simple_char:
+            if (ret == -2)
+                ret = 1;
+            break;
+        case REOP_line_start:
+        case REOP_line_end:
+        case REOP_push_i32:
+        case REOP_push_char_pos:
+        case REOP_drop:
+        case REOP_word_boundary:
+        case REOP_not_word_boundary:
+        case REOP_prev:
+            /* no effect */
+            break;
+        case REOP_save_start:
+        case REOP_save_end:
+            val = bc_buf[pos + 1];
+            capture_bitmap[val] |= 1;
+            break;
+        case REOP_save_reset:
+            {
+                val = bc_buf[pos + 1];
+                last = bc_buf[pos + 2];
+                while (val < last)
+                    capture_bitmap[val++] |= 1;
+            }
+            break;
+        case REOP_back_reference:
+        case REOP_backward_back_reference:
+            val = bc_buf[pos + 1];
+            capture_bitmap[val] |= 2;
+            has_back_reference = TRUE;
+            break;
+        default:
+            /* safe behvior: we cannot predict the outcome */
+            if (ret == -2)
+                ret = 0;
+            break;
+        }
+        pos += len;
+    }
+    if (has_back_reference) {
+        /* check if there is back reference which references a capture
+           made in the some code */
+        for(i = 0; i < CAPTURE_COUNT_MAX; i++) {
+            if (capture_bitmap[i] == 3)
+                return -1;
+        }
+    }
+    if (ret == -2)
+        ret = 0;
+    return ret;
+}
+
+/* return -1 if a simple quantifier cannot be used. Otherwise return
+   the number of characters in the atom. */
+static int re_is_simple_quantifier(const uint8_t *bc_buf, int bc_buf_len)
+{
+    int pos, opcode, len, count;
+    uint32_t val;
+    
+    count = 0;
+    pos = 0;
+    while (pos < bc_buf_len) {
+        opcode = bc_buf[pos];
+        len = reopcode_info[opcode].size;
+        switch(opcode) {
+        case REOP_range:
+            val = get_u16(bc_buf + pos + 1);
+            len += val * 4;
+            goto simple_char;
+        case REOP_range32:
+            val = get_u16(bc_buf + pos + 1);
+            len += val * 8;
+            goto simple_char;
+        case REOP_char:
+        case REOP_char32:
+        case REOP_dot:
+        case REOP_any:
+        simple_char:
+            count++;
+            break;
+        case REOP_line_start:
+        case REOP_line_end:
+        case REOP_word_boundary:
+        case REOP_not_word_boundary:
+            break;
+        default:
+            return -1;
+        }
+        pos += len;
+    }
+    return count;
+}
+
+/* '*pp' is the first char after '<' */
+static int re_parse_group_name(char *buf, int buf_size,
+                               const uint8_t **pp, BOOL is_utf16)
+{
+    const uint8_t *p;
+    uint32_t c;
+    char *q;
+
+    p = *pp;
+    q = buf;
+    for(;;) {
+        c = *p;
+        if (c == '\\') {
+            p++;
+            if (*p != 'u')
+                return -1;
+            c = lre_parse_escape(&p, is_utf16 * 2);
+        } else if (c == '>') {
+            break;
+        } else if (c >= 128) {
+            c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
+        } else {
+            p++;
+        }
+        if (c > 0x10FFFF)
+            return -1;
+        if (q == buf) {
+            if (!lre_js_is_ident_first(c))
+                return -1;
+        } else {
+            if (!lre_js_is_ident_next(c))
+                return -1;
+        }
+        if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size)
+            return -1;
+        if (c < 128) {
+            *q++ = c;
+        } else {
+            q += unicode_to_utf8((uint8_t*)q, c);
+        }
+    }
+    if (q == buf)
+        return -1;
+    *q = '\0';
+    p++;
+    *pp = p;
+    return 0;
+}
+
+/* if capture_name = NULL: return the number of captures + 1.
+   Otherwise, return the capture index corresponding to capture_name
+   or -1 if none */
+static int re_parse_captures(REParseState *s, int *phas_named_captures,
+                             const char *capture_name)
+{
+    const uint8_t *p;
+    int capture_index;
+    char name[TMP_BUF_SIZE];
+
+    capture_index = 1;
+    *phas_named_captures = 0;
+    for (p = s->buf_start; p < s->buf_end; p++) {
+        switch (*p) {
+        case '(':
+            if (p[1] == '?') {
+                if (p[2] == '<' && p[3] != '=' && p[3] != '!') {
+                    *phas_named_captures = 1;
+                    /* potential named capture */
+                    if (capture_name) {
+                        p += 3;
+                        if (re_parse_group_name(name, sizeof(name), &p,
+                                                s->is_utf16) == 0) {
+                            if (!strcmp(name, capture_name))
+                                return capture_index;
+                        }
+                    }
+                    capture_index++;
+                }
+            } else {
+                capture_index++;
+            }
+            break;
+        case '\\':
+            p++;
+            break;
+        case '[':
+            for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) {
+                if (*p == '\\')
+                    p++;
+            }
+            break;
+        }
+    }
+    if (capture_name)
+        return -1;
+    else
+        return capture_index;
+}
+
+static int re_count_captures(REParseState *s)
+{
+    if (s->total_capture_count < 0) {
+        s->total_capture_count = re_parse_captures(s, &s->has_named_captures,
+                                                   NULL);
+    }
+    return s->total_capture_count;
+}
+
+static BOOL re_has_named_captures(REParseState *s)
+{
+    if (s->has_named_captures < 0)
+        re_count_captures(s);
+    return s->has_named_captures;
+}
+
+static int find_group_name(REParseState *s, const char *name)
+{
+    const char *p, *buf_end;
+    size_t len, name_len;
+    int capture_index;
+    
+    name_len = strlen(name);
+    p = (char *)s->group_names.buf;
+    buf_end = (char *)s->group_names.buf + s->group_names.size;
+    capture_index = 1;
+    while (p < buf_end) {
+        len = strlen(p);
+        if (len == name_len && memcmp(name, p, name_len) == 0)
+            return capture_index;
+        p += len + 1;
+        capture_index++;
+    }
+    return -1;
+}
+
+static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir);
+
+static int re_parse_term(REParseState *s, BOOL is_backward_dir)
+{
+    const uint8_t *p;
+    int c, last_atom_start, quant_min, quant_max, last_capture_count;
+    BOOL greedy, add_zero_advance_check, is_neg, is_backward_lookahead;
+    CharRange cr_s, *cr = &cr_s;
+    
+    last_atom_start = -1;
+    last_capture_count = 0;
+    p = s->buf_ptr;
+    c = *p;
+    switch(c) {
+    case '^':
+        p++;
+        re_emit_op(s, REOP_line_start);
+        break;
+    case '$':
+        p++;
+        re_emit_op(s, REOP_line_end);
+        break;
+    case '.':
+        p++;
+        last_atom_start = s->byte_code.size;
+        last_capture_count = s->capture_count;
+        if (is_backward_dir)
+            re_emit_op(s, REOP_prev);
+        re_emit_op(s, s->dotall ? REOP_any : REOP_dot);
+        if (is_backward_dir)
+            re_emit_op(s, REOP_prev);
+        break;
+    case '{':
+        /* As an extension (see ES6 annex B), we accept '{' not
+           followed by digits as a normal atom */
+        if (!is_digit(p[1])) {
+            if (s->is_utf16)
+                goto invalid_quant_count;
+            goto parse_class_atom;
+        }
+        /* fall tru */
+    case '*':
+    case '+':
+    case '?':
+        return re_parse_error(s, "nothing to repeat");
+    case '(':
+        if (p[1] == '?') {
+            if (p[2] == ':') {
+                p += 3;
+                last_atom_start = s->byte_code.size;
+                last_capture_count = s->capture_count;
+                s->buf_ptr = p;
+                if (re_parse_disjunction(s, is_backward_dir))
+                    return -1;
+                p = s->buf_ptr;
+                if (re_parse_expect(s, &p, ')'))
+                    return -1;
+            } else if ((p[2] == '=' || p[2] == '!')) {
+                is_neg = (p[2] == '!');
+                is_backward_lookahead = FALSE;
+                p += 3;
+                goto lookahead;
+            } else if (p[2] == '<' &&
+                       (p[3] == '=' || p[3] == '!')) {
+                int pos;
+                is_neg = (p[3] == '!');
+                is_backward_lookahead = TRUE;
+                p += 4;
+                /* lookahead */
+            lookahead:
+                /* Annex B allows lookahead to be used as an atom for
+                   the quantifiers */
+                if (!s->is_utf16 && !is_backward_lookahead)  {
+                    last_atom_start = s->byte_code.size;
+                    last_capture_count = s->capture_count;
+                }
+                pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0);
+                s->buf_ptr = p;
+                if (re_parse_disjunction(s, is_backward_lookahead))
+                    return -1;
+                p = s->buf_ptr;
+                if (re_parse_expect(s, &p, ')'))
+                    return -1;
+                re_emit_op(s, REOP_match);
+                /* jump after the 'match' after the lookahead is successful */
+                put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4));
+            } else if (p[2] == '<') {
+                p += 3;
+                if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf),
+                                        &p, s->is_utf16)) {
+                    return re_parse_error(s, "invalid group name");
+                }
+                if (find_group_name(s, s->u.tmp_buf) > 0) {
+                    return re_parse_error(s, "duplicate group name");
+                }
+                /* group name with a trailing zero */
+                dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf,
+                         strlen(s->u.tmp_buf) + 1);
+                s->has_named_captures = 1;
+                goto parse_capture;
+            } else {
+                return re_parse_error(s, "invalid group");
+            }
+        } else {
+            int capture_index;
+            p++;
+            /* capture without group name */
+            dbuf_putc(&s->group_names, 0);
+        parse_capture:
+            if (s->capture_count >= CAPTURE_COUNT_MAX)
+                return re_parse_error(s, "too many captures");
+            last_atom_start = s->byte_code.size;
+            last_capture_count = s->capture_count;
+            capture_index = s->capture_count++;
+            re_emit_op_u8(s, REOP_save_start + is_backward_dir,
+                          capture_index);
+            
+            s->buf_ptr = p;
+            if (re_parse_disjunction(s, is_backward_dir))
+                return -1;
+            p = s->buf_ptr;
+            
+            re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir,
+                          capture_index);
+            
+            if (re_parse_expect(s, &p, ')'))
+                return -1;
+        }
+        break;
+    case '\\':
+        switch(p[1]) {
+        case 'b':
+        case 'B':
+            re_emit_op(s, REOP_word_boundary + (p[1] != 'b'));
+            p += 2;
+            break;
+        case 'k':
+            {
+                const uint8_t *p1;
+                int dummy_res;
+                
+                p1 = p;
+                if (p1[2] != '<') {
+                    /* annex B: we tolerate invalid group names in non
+                       unicode mode if there is no named capture
+                       definition */
+                    if (s->is_utf16 || re_has_named_captures(s))
+                        return re_parse_error(s, "expecting group name");
+                    else
+                        goto parse_class_atom;
+                }
+                p1 += 3;
+                if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf),
+                                        &p1, s->is_utf16)) {
+                    if (s->is_utf16 || re_has_named_captures(s))
+                        return re_parse_error(s, "invalid group name");
+                    else
+                        goto parse_class_atom;
+                }
+                c = find_group_name(s, s->u.tmp_buf);
+                if (c < 0) {
+                    /* no capture name parsed before, try to look
+                       after (inefficient, but hopefully not common */
+                    c = re_parse_captures(s, &dummy_res, s->u.tmp_buf);
+                    if (c < 0) {
+                        if (s->is_utf16 || re_has_named_captures(s))
+                            return re_parse_error(s, "group name not defined");
+                        else
+                            goto parse_class_atom;
+                    }
+                }
+                p = p1;
+            }
+            goto emit_back_reference;
+        case '0':
+            p += 2;
+            c = 0;
+            if (s->is_utf16) {
+                if (is_digit(*p)) {
+                    return re_parse_error(s, "invalid decimal escape in regular expression");
+                }
+            } else {
+                /* Annex B.1.4: accept legacy octal */
+                if (*p >= '0' && *p <= '7') {
+                    c = *p++ - '0';
+                    if (*p >= '0' && *p <= '7') {
+                        c = (c << 3) + *p++ - '0';
+                    }
+                }
+            }
+            goto normal_char;
+        case '1' ... '9':
+            {
+                const uint8_t *q = ++p;
+                
+                c = parse_digits(&p);
+                if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) {
+                    if (!s->is_utf16) {
+                        /* Annex B.1.4: accept legacy octal */
+                        p = q;
+                        if (*p <= '7') {
+                            c = 0;
+                            if (*p <= '3')
+                                c = *p++ - '0';
+                            if (*p >= '0' && *p <= '7') {
+                                c = (c << 3) + *p++ - '0';
+                                if (*p >= '0' && *p <= '7') {
+                                    c = (c << 3) + *p++ - '0';
+                                }
+                            }
+                        } else {
+                            c = *p++;
+                        }
+                        goto normal_char;
+                    }
+                    return re_parse_error(s, "back reference out of range in reguar expression");
+                }
+            emit_back_reference:
+                last_atom_start = s->byte_code.size;
+                last_capture_count = s->capture_count;
+                re_emit_op_u8(s, REOP_back_reference + is_backward_dir, c);
+            }
+            break;
+        default:
+            goto parse_class_atom;
+        }
+        break;
+    case '[':
+        last_atom_start = s->byte_code.size;
+        last_capture_count = s->capture_count;
+        if (is_backward_dir)
+            re_emit_op(s, REOP_prev);
+        if (re_parse_char_class(s, &p))
+            return -1;
+        if (is_backward_dir)
+            re_emit_op(s, REOP_prev);
+        break;
+    case ']':
+    case '}':
+        if (s->is_utf16)
+            return re_parse_error(s, "syntax error");
+        goto parse_class_atom;
+    default:
+    parse_class_atom:
+        c = get_class_atom(s, cr, &p, FALSE);
+        if ((int)c < 0)
+            return -1;
+    normal_char:
+        last_atom_start = s->byte_code.size;
+        last_capture_count = s->capture_count;
+        if (is_backward_dir)
+            re_emit_op(s, REOP_prev);
+        if (c >= CLASS_RANGE_BASE) {
+            int ret;
+            /* Note: canonicalization is not needed */
+            ret = re_emit_range(s, cr);
+            cr_free(cr);
+            if (ret)
+                return -1;
+        } else {
+            if (s->ignore_case)
+                c = lre_canonicalize(c, s->is_utf16);
+            if (c <= 0xffff)
+                re_emit_op_u16(s, REOP_char, c);
+            else
+                re_emit_op_u32(s, REOP_char32, c);
+        }
+        if (is_backward_dir)
+            re_emit_op(s, REOP_prev);
+        break;
+    }
+
+    /* quantifier */
+    if (last_atom_start >= 0) {
+        c = *p;
+        switch(c) {
+        case '*':
+            p++;
+            quant_min = 0;
+            quant_max = INT32_MAX;
+            goto quantifier;
+        case '+':
+            p++;
+            quant_min = 1;
+            quant_max = INT32_MAX;
+            goto quantifier;
+        case '?':
+            p++;
+            quant_min = 0;
+            quant_max = 1;
+            goto quantifier;
+        case '{':
+            /* As an extension (see ES6 annex B), we accept '{' not
+               followed by digits as a normal atom */
+            if (!is_digit(p[1])) {
+                if (s->is_utf16)
+                    goto invalid_quant_count;
+                break;
+            }
+            p++;
+            quant_min = parse_digits(&p);
+            if (quant_min < 0) {
+            invalid_quant_count:
+                return re_parse_error(s, "invalid repetition count");
+            }
+            quant_max = quant_min;
+            if (*p == ',') {
+                p++;
+                if (is_digit(*p)) {
+                    quant_max = parse_digits(&p);
+                    if (quant_max < 0 || quant_max < quant_min)
+                        goto invalid_quant_count;
+                } else {
+                    quant_max = INT32_MAX; /* infinity */
+                }
+            }
+            if (re_parse_expect(s, &p, '}'))
+                return -1;
+        quantifier:
+            greedy = TRUE;
+            if (*p == '?') {
+                p++;
+                greedy = FALSE;
+            }
+            if (last_atom_start < 0) {
+                return re_parse_error(s, "nothing to repeat");
+            }
+            if (greedy) {
+                int len, pos;
+                
+                if (quant_max > 0) {
+                    /* specific optimization for simple quantifiers */
+                    len = re_is_simple_quantifier(s->byte_code.buf + last_atom_start,
+                                                 s->byte_code.size - last_atom_start);
+                    if (len > 0) {
+                        re_emit_op(s, REOP_match);
+                        
+                        dbuf_insert(&s->byte_code, last_atom_start, 17);
+                        pos = last_atom_start;
+                        s->byte_code.buf[pos++] = REOP_simple_greedy_quant;
+                        put_u32(&s->byte_code.buf[pos],
+                                s->byte_code.size - last_atom_start - 17);
+                        pos += 4;
+                        put_u32(&s->byte_code.buf[pos], quant_min);
+                        pos += 4;
+                        put_u32(&s->byte_code.buf[pos], quant_max);
+                        pos += 4;
+                        put_u32(&s->byte_code.buf[pos], len);
+                        pos += 4;
+                        goto done;
+                    }
+                }
+                
+                add_zero_advance_check = (re_check_advance(s->byte_code.buf + last_atom_start,
+                                                           s->byte_code.size - last_atom_start) == 0);
+            } else {
+                add_zero_advance_check = FALSE;
+            }
+            
+            {
+                int len, pos;
+                len = s->byte_code.size - last_atom_start;
+                if (quant_min == 0) {
+                    /* need to reset the capture in case the atom is
+                       not executed */
+                    if (last_capture_count != s->capture_count) {
+                        dbuf_insert(&s->byte_code, last_atom_start, 3);
+                        s->byte_code.buf[last_atom_start++] = REOP_save_reset;
+                        s->byte_code.buf[last_atom_start++] = last_capture_count;
+                        s->byte_code.buf[last_atom_start++] = s->capture_count - 1;
+                    }
+                    if (quant_max == 0) {
+                        s->byte_code.size = last_atom_start;
+                    } else if (quant_max == 1) {
+                        dbuf_insert(&s->byte_code, last_atom_start, 5);
+                        s->byte_code.buf[last_atom_start] = REOP_split_goto_first +
+                            greedy;
+                        put_u32(s->byte_code.buf + last_atom_start + 1, len);
+                    } else if (quant_max == INT32_MAX) {
+                        dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check);
+                        s->byte_code.buf[last_atom_start] = REOP_split_goto_first +
+                            greedy;
+                        put_u32(s->byte_code.buf + last_atom_start + 1,
+                                len + 5 + add_zero_advance_check);
+                        if (add_zero_advance_check) {
+                            /* avoid infinite loop by stoping the
+                               recursion if no advance was made in the
+                               atom (only works if the atom has no
+                               side effect) */
+                            s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos;
+                            re_emit_goto(s, REOP_bne_char_pos, last_atom_start); 
+                        } else {
+                            re_emit_goto(s, REOP_goto, last_atom_start);
+                        }
+                    } else {
+                        dbuf_insert(&s->byte_code, last_atom_start, 10);
+                        pos = last_atom_start;
+                        s->byte_code.buf[pos++] = REOP_push_i32;
+                        put_u32(s->byte_code.buf + pos, quant_max);
+                        pos += 4;
+                        s->byte_code.buf[pos++] = REOP_split_goto_first + greedy;
+                        put_u32(s->byte_code.buf + pos, len + 5);
+                        re_emit_goto(s, REOP_loop, last_atom_start + 5);
+                        re_emit_op(s, REOP_drop);
+                    }
+                } else if (quant_min == 1 && quant_max == INT32_MAX &&
+                           !add_zero_advance_check) {
+                    re_emit_goto(s, REOP_split_next_first - greedy,
+                                 last_atom_start);
+                } else {
+                    if (quant_min == 1) {
+                        /* nothing to add */
+                    } else {
+                        dbuf_insert(&s->byte_code, last_atom_start, 5);
+                        s->byte_code.buf[last_atom_start] = REOP_push_i32;
+                        put_u32(s->byte_code.buf + last_atom_start + 1,
+                                quant_min);
+                        last_atom_start += 5;
+                        re_emit_goto(s, REOP_loop, last_atom_start);
+                        re_emit_op(s, REOP_drop);
+                    }
+                    if (quant_max == INT32_MAX) {
+                        pos = s->byte_code.size;
+                        re_emit_op_u32(s, REOP_split_goto_first + greedy,
+                                       len + 5 + add_zero_advance_check);
+                        if (add_zero_advance_check)
+                            re_emit_op(s, REOP_push_char_pos);
+                        /* copy the atom */
+                        dbuf_put_self(&s->byte_code, last_atom_start, len);
+                        if (add_zero_advance_check)
+                            re_emit_goto(s, REOP_bne_char_pos, pos);
+                        else
+                            re_emit_goto(s, REOP_goto, pos);
+                    } else if (quant_max > quant_min) {
+                        re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min);
+                        pos = s->byte_code.size;
+                        re_emit_op_u32(s, REOP_split_goto_first + greedy, len + 5);
+                        /* copy the atom */
+                        dbuf_put_self(&s->byte_code, last_atom_start, len);
+                        
+                        re_emit_goto(s, REOP_loop, pos);
+                        re_emit_op(s, REOP_drop);
+                    }
+                }
+                last_atom_start = -1;
+            }
+            break;
+        default:
+            break;
+        }
+    }
+ done:
+    s->buf_ptr = p;
+    return 0;
+}
+
+static int re_parse_alternative(REParseState *s, BOOL is_backward_dir)
+{
+    const uint8_t *p;
+    int ret;
+    size_t start, term_start, end, term_size;
+
+    start = s->byte_code.size;
+    for(;;) {
+        p = s->buf_ptr;
+        if (p >= s->buf_end)
+            break;
+        if (*p == '|' || *p == ')')
+            break;
+        term_start = s->byte_code.size;
+        ret = re_parse_term(s, is_backward_dir);
+        if (ret)
+            return ret;
+        if (is_backward_dir) {
+            /* reverse the order of the terms (XXX: inefficient, but
+               speed is not really critical here) */
+            end = s->byte_code.size;
+            term_size = end - term_start;
+            if (dbuf_realloc(&s->byte_code, end + term_size))
+                return -1;
+            memmove(s->byte_code.buf + start + term_size,
+                    s->byte_code.buf + start,
+                    end - start);
+            memcpy(s->byte_code.buf + start, s->byte_code.buf + end,
+                   term_size);
+        }
+    }
+    return 0;
+}
+    
+static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir)
+{
+    int start, len, pos;
+
+    start = s->byte_code.size;
+    if (re_parse_alternative(s, is_backward_dir))
+        return -1;
+    while (*s->buf_ptr == '|') {
+        s->buf_ptr++;
+
+        len = s->byte_code.size - start;
+
+        /* insert a split before the first alternative */
+        dbuf_insert(&s->byte_code, start, 5);
+        s->byte_code.buf[start] = REOP_split_next_first;
+        put_u32(s->byte_code.buf + start + 1, len + 5);
+
+        pos = re_emit_op_u32(s, REOP_goto, 0);
+
+        if (re_parse_alternative(s, is_backward_dir))
+            return -1;
+        
+        /* patch the goto */
+        len = s->byte_code.size - (pos + 4);
+        put_u32(s->byte_code.buf + pos, len);
+    }
+    return 0;
+}
+
+/* the control flow is recursive so the analysis can be linear */
+static int compute_stack_size(const uint8_t *bc_buf, int bc_buf_len)
+{
+    int stack_size, stack_size_max, pos, opcode, len;
+    uint32_t val;
+    
+    stack_size = 0;
+    stack_size_max = 0;
+    bc_buf += RE_HEADER_LEN;
+    bc_buf_len -= RE_HEADER_LEN;
+    pos = 0;
+    while (pos < bc_buf_len) {
+        opcode = bc_buf[pos];
+        len = reopcode_info[opcode].size;
+        assert(opcode < REOP_COUNT);
+        assert((pos + len) <= bc_buf_len);
+        switch(opcode) {
+        case REOP_push_i32:
+        case REOP_push_char_pos:
+            stack_size++;
+            if (stack_size > stack_size_max) {
+                if (stack_size > STACK_SIZE_MAX)
+                    return -1;
+                stack_size_max = stack_size;
+            }
+            break;
+        case REOP_drop:
+        case REOP_bne_char_pos:
+            assert(stack_size > 0);
+            stack_size--;
+            break;
+        case REOP_range:
+            val = get_u16(bc_buf + pos + 1);
+            len += val * 4;
+            break;
+        case REOP_range32:
+            val = get_u16(bc_buf + pos + 1);
+            len += val * 8;
+            break;
+        }
+        pos += len;
+    }
+    return stack_size_max;
+}
+
+/* 'buf' must be a zero terminated UTF-8 string of length buf_len.
+   Return NULL if error and allocate an error message in *perror_msg,
+   otherwise the compiled bytecode and its length in plen.
+*/
+uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size,
+                     const char *buf, size_t buf_len, int re_flags,
+                     void *opaque)
+{
+    REParseState s_s, *s = &s_s;
+    int stack_size;
+    BOOL is_sticky;
+    
+    memset(s, 0, sizeof(*s));
+    s->mem_opaque = opaque;
+    s->buf_ptr = (const uint8_t *)buf;
+    s->buf_end = s->buf_ptr + buf_len;
+    s->buf_start = s->buf_ptr;
+    s->re_flags = re_flags;
+    s->is_utf16 = ((re_flags & LRE_FLAG_UTF16) != 0);
+    is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0);
+    s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0);
+    s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0);
+    s->capture_count = 1;
+    s->total_capture_count = -1;
+    s->has_named_captures = -1;
+    
+    dbuf_init2(&s->byte_code, opaque, lre_realloc);
+    dbuf_init2(&s->group_names, opaque, lre_realloc);
+
+    dbuf_putc(&s->byte_code, re_flags); /* first element is the flags */
+    dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */
+    dbuf_putc(&s->byte_code, 0); /* stack size */
+    dbuf_put_u32(&s->byte_code, 0); /* bytecode length */
+    
+    if (!is_sticky) {
+        /* iterate thru all positions (about the same as .*?( ... ) )
+           .  We do it without an explicit loop so that lock step
+           thread execution will be possible in an optimized
+           implementation */
+        re_emit_op_u32(s, REOP_split_goto_first, 1 + 5);
+        re_emit_op(s, REOP_any);
+        re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5));
+    }
+    re_emit_op_u8(s, REOP_save_start, 0);
+
+    if (re_parse_disjunction(s, FALSE)) {
+    error:
+        dbuf_free(&s->byte_code);
+        dbuf_free(&s->group_names);
+        pstrcpy(error_msg, error_msg_size, s->u.error_msg);
+        *plen = 0;
+        return NULL;
+    }
+
+    re_emit_op_u8(s, REOP_save_end, 0);
+    
+    re_emit_op(s, REOP_match);
+
+    if (*s->buf_ptr != '\0') {
+        re_parse_error(s, "extraneous characters at the end");
+        goto error;
+    }
+
+    if (dbuf_error(&s->byte_code)) {
+        re_parse_error(s, "out of memory");
+        goto error;
+    }
+    
+    stack_size = compute_stack_size(s->byte_code.buf, s->byte_code.size);
+    if (stack_size < 0) {
+        re_parse_error(s, "too many imbricated quantifiers");
+        goto error;
+    }
+    
+    s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count;
+    s->byte_code.buf[RE_HEADER_STACK_SIZE] = stack_size;
+    put_u32(s->byte_code.buf + 3, s->byte_code.size - RE_HEADER_LEN);
+
+    /* add the named groups if needed */
+    if (s->group_names.size > (s->capture_count - 1)) {
+        dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size);
+        s->byte_code.buf[RE_HEADER_FLAGS] |= LRE_FLAG_NAMED_GROUPS;
+    }
+    dbuf_free(&s->group_names);
+    
+#ifdef DUMP_REOP
+    lre_dump_bytecode(s->byte_code.buf, s->byte_code.size);
+#endif
+    
+    error_msg[0] = '\0';
+    *plen = s->byte_code.size;
+    return s->byte_code.buf;
+}
+
+static BOOL is_line_terminator(uint32_t c)
+{
+    return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS);
+}
+
+static BOOL is_word_char(uint32_t c)
+{
+    return ((c >= '0' && c <= '9') ||
+            (c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c == '_'));
+}
+
+#define GET_CHAR(c, cptr, cbuf_end)                                     \
+    do {                                                                \
+        if (cbuf_type == 0) {                                           \
+            c = *cptr++;                                                \
+        } else {                                                        \
+            uint32_t __c1;                                              \
+            c = *(uint16_t *)cptr;                                      \
+            cptr += 2;                                                  \
+            if (c >= 0xd800 && c < 0xdc00 &&                            \
+                cbuf_type == 2 && cptr < cbuf_end) {                    \
+                __c1 = *(uint16_t *)cptr;                               \
+                if (__c1 >= 0xdc00 && __c1 < 0xe000) {                  \
+                    c = (((c & 0x3ff) << 10) | (__c1 & 0x3ff)) + 0x10000; \
+                    cptr += 2;                                          \
+                }                                                       \
+            }                                                           \
+        }                                                               \
+    } while (0)
+
+#define PEEK_CHAR(c, cptr, cbuf_end)             \
+    do {                                         \
+        if (cbuf_type == 0) {                    \
+            c = cptr[0];                         \
+        } else {                                 \
+            uint32_t __c1;                                              \
+            c = ((uint16_t *)cptr)[0];                                  \
+            if (c >= 0xd800 && c < 0xdc00 &&                            \
+                cbuf_type == 2 && (cptr + 2) < cbuf_end) {              \
+                __c1 = ((uint16_t *)cptr)[1];                           \
+                if (__c1 >= 0xdc00 && __c1 < 0xe000) {                  \
+                    c = (((c & 0x3ff) << 10) | (__c1 & 0x3ff)) + 0x10000; \
+                }                                                       \
+            }                                                           \
+        }                                        \
+    } while (0)
+
+#define PEEK_PREV_CHAR(c, cptr, cbuf_start)                 \
+    do {                                         \
+        if (cbuf_type == 0) {                    \
+            c = cptr[-1];                        \
+        } else {                                 \
+            uint32_t __c1;                                              \
+            c = ((uint16_t *)cptr)[-1];                                 \
+            if (c >= 0xdc00 && c < 0xe000 &&                            \
+                cbuf_type == 2 && (cptr - 4) >= cbuf_start) {              \
+                __c1 = ((uint16_t *)cptr)[-2];                          \
+                if (__c1 >= 0xd800 && __c1 < 0xdc00 ) {                 \
+                    c = (((__c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000; \
+                }                                                       \
+            }                                                           \
+        }                                                               \
+    } while (0)
+
+#define GET_PREV_CHAR(c, cptr, cbuf_start)       \
+    do {                                         \
+        if (cbuf_type == 0) {                    \
+            cptr--;                              \
+            c = cptr[0];                         \
+        } else {                                 \
+            uint32_t __c1;                                              \
+            cptr -= 2;                                                  \
+            c = ((uint16_t *)cptr)[0];                                 \
+            if (c >= 0xdc00 && c < 0xe000 &&                            \
+                cbuf_type == 2 && cptr > cbuf_start) {                  \
+                __c1 = ((uint16_t *)cptr)[-1];                          \
+                if (__c1 >= 0xd800 && __c1 < 0xdc00 ) {                 \
+                    cptr -= 2;                                          \
+                    c = (((__c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000; \
+                }                                                       \
+            }                                                           \
+        }                                                               \
+    } while (0)
+
+#define PREV_CHAR(cptr, cbuf_start)       \
+    do {                                  \
+        if (cbuf_type == 0) {             \
+            cptr--;                       \
+        } else {                          \
+            cptr -= 2;                          \
+            if (cbuf_type == 2) {                                       \
+                c = ((uint16_t *)cptr)[0];                              \
+                if (c >= 0xdc00 && c < 0xe000 && cptr > cbuf_start) {   \
+                    c = ((uint16_t *)cptr)[-1];                         \
+                    if (c >= 0xd800 && c < 0xdc00)                      \
+                        cptr -= 2;                                      \
+                }                                                       \
+            }                                                           \
+        }                                                               \
+    } while (0)
+
+typedef uintptr_t StackInt;
+
+typedef enum {
+    RE_EXEC_STATE_SPLIT,
+    RE_EXEC_STATE_LOOKAHEAD,
+    RE_EXEC_STATE_NEGATIVE_LOOKAHEAD,
+    RE_EXEC_STATE_GREEDY_QUANT,
+} REExecStateEnum;
+
+typedef struct REExecState {
+    REExecStateEnum type : 8;
+    uint8_t stack_len;
+    size_t count; /* only used for RE_EXEC_STATE_GREEDY_QUANT */
+    const uint8_t *cptr;
+    const uint8_t *pc;
+    void *buf[0];
+} REExecState;
+
+typedef struct {
+    const uint8_t *cbuf;
+    const uint8_t *cbuf_end;
+    /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */
+    int cbuf_type; 
+    int capture_count;
+    int stack_size_max;
+    BOOL multi_line;
+    BOOL ignore_case;
+    BOOL is_utf16;
+    void *opaque; /* used for stack overflow check */
+
+    size_t state_size;
+    uint8_t *state_stack;
+    size_t state_stack_size;
+    size_t state_stack_len;
+} REExecContext;
+
+static int push_state(REExecContext *s,
+                      uint8_t **capture,
+                      StackInt *stack, size_t stack_len,
+                      const uint8_t *pc, const uint8_t *cptr,
+                      REExecStateEnum type, size_t count)
+{
+    REExecState *rs;
+    uint8_t *new_stack;
+    size_t new_size, i, n;
+    StackInt *stack_buf;
+
+    if (unlikely((s->state_stack_len + 1) > s->state_stack_size)) {
+        /* reallocate the stack */
+        new_size = s->state_stack_size * 3 / 2;
+        if (new_size < 8)
+            new_size = 8;
+        new_stack = lre_realloc(s->opaque, s->state_stack, new_size * s->state_size);
+        if (!new_stack)
+            return -1;
+        s->state_stack_size = new_size;
+        s->state_stack = new_stack;
+    }
+    rs = (REExecState *)(s->state_stack + s->state_stack_len * s->state_size);
+    s->state_stack_len++;
+    rs->type = type;
+    rs->count = count;
+    rs->stack_len = stack_len;
+    rs->cptr = cptr;
+    rs->pc = pc;
+    n = 2 * s->capture_count;
+    for(i = 0; i < n; i++)
+        rs->buf[i] = capture[i];
+    stack_buf = (StackInt *)(rs->buf + n);
+    for(i = 0; i < stack_len; i++)
+        stack_buf[i] = stack[i];
+    return 0;
+}
+
+/* return 1 if match, 0 if not match or -1 if error. */
+static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture,
+                                   StackInt *stack, int stack_len,
+                                   const uint8_t *pc, const uint8_t *cptr,
+                                   BOOL no_recurse)
+{
+    int opcode, ret;
+    int cbuf_type;
+    uint32_t val, c;
+    const uint8_t *cbuf_end;
+    
+    cbuf_type = s->cbuf_type;
+    cbuf_end = s->cbuf_end;
+
+    for(;;) {
+        //        printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN)));
+        opcode = *pc++;
+        switch(opcode) {
+        case REOP_match:
+            {
+                REExecState *rs;
+                if (no_recurse)
+                    return (intptr_t)cptr;
+                ret = 1;
+                goto recurse;
+            no_match:
+                if (no_recurse)
+                    return 0;
+                ret = 0;
+            recurse:
+                for(;;) {
+                    if (s->state_stack_len == 0)
+                        return ret;
+                    rs = (REExecState *)(s->state_stack +
+                                         (s->state_stack_len - 1) * s->state_size);
+                    if (rs->type == RE_EXEC_STATE_SPLIT) {
+                        if (!ret) {
+                        pop_state:
+                            memcpy(capture, rs->buf,
+                                   sizeof(capture[0]) * 2 * s->capture_count);
+                        pop_state1:
+                            pc = rs->pc;
+                            cptr = rs->cptr;
+                            stack_len = rs->stack_len;
+                            memcpy(stack, rs->buf + 2 * s->capture_count,
+                                   stack_len * sizeof(stack[0]));
+                            s->state_stack_len--;
+                            break;
+                        }
+                    } else if (rs->type == RE_EXEC_STATE_GREEDY_QUANT) {
+                        if (!ret) {
+                            uint32_t char_count, i;
+                            memcpy(capture, rs->buf,
+                                   sizeof(capture[0]) * 2 * s->capture_count);
+                            stack_len = rs->stack_len;
+                            memcpy(stack, rs->buf + 2 * s->capture_count,
+                                   stack_len * sizeof(stack[0]));
+                            pc = rs->pc;
+                            cptr = rs->cptr;
+                            /* go backward */
+                            char_count = get_u32(pc + 12);
+                            for(i = 0; i < char_count; i++) {
+                                PREV_CHAR(cptr, s->cbuf);
+                            }
+                            pc = (pc + 16) + (int)get_u32(pc);
+                            rs->cptr = cptr;
+                            rs->count--;
+                            if (rs->count == 0) {
+                                s->state_stack_len--;
+                            }
+                            break;
+                        }
+                    } else {
+                        ret = ((rs->type == RE_EXEC_STATE_LOOKAHEAD && ret) ||
+                               (rs->type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD && !ret));
+                        if (ret) {
+                            /* keep the capture in case of positive lookahead */
+                            if (rs->type == RE_EXEC_STATE_LOOKAHEAD)
+                                goto pop_state1;
+                            else
+                                goto pop_state;
+                        }
+                    }
+                    s->state_stack_len--;
+                }
+            }
+            break;
+        case REOP_char32:
+            val = get_u32(pc);
+            pc += 4;
+            goto test_char;
+        case REOP_char:
+            val = get_u16(pc);
+            pc += 2;
+        test_char:
+            if (cptr >= cbuf_end)
+                goto no_match;
+            GET_CHAR(c, cptr, cbuf_end);
+            if (s->ignore_case) {
+                c = lre_canonicalize(c, s->is_utf16);
+            }
+            if (val != c)
+                goto no_match;
+            break;
+        case REOP_split_goto_first:
+        case REOP_split_next_first:
+            {
+                const uint8_t *pc1;
+                
+                val = get_u32(pc);
+                pc += 4;
+                if (opcode == REOP_split_next_first) {
+                    pc1 = pc + (int)val;
+                } else {
+                    pc1 = pc;
+                    pc = pc + (int)val;
+                }
+                ret = push_state(s, capture, stack, stack_len,
+                                 pc1, cptr, RE_EXEC_STATE_SPLIT, 0);
+                if (ret < 0)
+                    return -1;
+                break;
+            }
+        case REOP_lookahead:
+        case REOP_negative_lookahead:
+            val = get_u32(pc);
+            pc += 4;
+            ret = push_state(s, capture, stack, stack_len,
+                             pc + (int)val, cptr,
+                             RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead,
+                             0);
+            if (ret < 0)
+                return -1;
+            break;
+            
+        case REOP_goto:
+            val = get_u32(pc);
+            pc += 4 + (int)val;
+            break;
+        case REOP_line_start:
+            if (cptr == s->cbuf)
+                break;
+            if (!s->multi_line)
+                goto no_match;
+            PEEK_PREV_CHAR(c, cptr, s->cbuf);
+            if (!is_line_terminator(c))
+                goto no_match;
+            break;
+        case REOP_line_end:
+            if (cptr == cbuf_end)
+                break;
+            if (!s->multi_line)
+                goto no_match;
+            PEEK_CHAR(c, cptr, cbuf_end);
+            if (!is_line_terminator(c))
+                goto no_match;
+            break;
+        case REOP_dot:
+            if (cptr == cbuf_end)
+                goto no_match;
+            GET_CHAR(c, cptr, cbuf_end);
+            if (is_line_terminator(c))
+                goto no_match;
+            break;
+        case REOP_any:
+            if (cptr == cbuf_end)
+                goto no_match;
+            GET_CHAR(c, cptr, cbuf_end);
+            break;
+        case REOP_save_start:
+        case REOP_save_end:
+            val = *pc++;
+            assert(val < s->capture_count);
+            capture[2 * val + opcode - REOP_save_start] = (uint8_t *)cptr;
+            break;
+        case REOP_save_reset:
+            {
+                uint32_t val2;
+                val = pc[0];
+                val2 = pc[1];
+                pc += 2;
+                assert(val2 < s->capture_count);
+                while (val <= val2) {
+                    capture[2 * val] = NULL;
+                    capture[2 * val + 1] = NULL;
+                    val++;
+                }
+            }
+            break;
+        case REOP_push_i32:
+            val = get_u32(pc);
+            pc += 4;
+            stack[stack_len++] = val;
+            break;
+        case REOP_drop:
+            stack_len--;
+            break;
+        case REOP_loop:
+            val = get_u32(pc);
+            pc += 4;
+            if (--stack[stack_len - 1] != 0) {
+                pc += (int)val;
+            }
+            break;
+        case REOP_push_char_pos:
+            stack[stack_len++] = (uintptr_t)cptr;
+            break;
+        case REOP_bne_char_pos:
+            val = get_u32(pc);
+            pc += 4;
+            if (stack[--stack_len] != (uintptr_t)cptr)
+                pc += (int)val;
+            break;
+        case REOP_word_boundary:
+        case REOP_not_word_boundary:
+            {
+                BOOL v1, v2;
+                /* char before */
+                if (cptr == s->cbuf) {
+                    v1 = FALSE;
+                } else {
+                    PEEK_PREV_CHAR(c, cptr, s->cbuf);
+                    v1 = is_word_char(c);
+                }
+                /* current char */
+                if (cptr >= cbuf_end) {
+                    v2 = FALSE;
+                } else {
+                    PEEK_CHAR(c, cptr, cbuf_end);
+                    v2 = is_word_char(c);
+                }
+                if (v1 ^ v2 ^ (REOP_not_word_boundary - opcode))
+                    goto no_match;
+            }
+            break;
+        case REOP_back_reference:
+        case REOP_backward_back_reference:
+            {
+                const uint8_t *cptr1, *cptr1_end, *cptr1_start;
+                uint32_t c1, c2;
+                
+                val = *pc++;
+                if (val >= s->capture_count)
+                    goto no_match;
+                cptr1_start = capture[2 * val];
+                cptr1_end = capture[2 * val + 1];
+                if (!cptr1_start || !cptr1_end)
+                    break;
+                if (opcode == REOP_back_reference) {
+                    cptr1 = cptr1_start;
+                    while (cptr1 < cptr1_end) {
+                        if (cptr >= cbuf_end)
+                            goto no_match;
+                        GET_CHAR(c1, cptr1, cptr1_end);
+                        GET_CHAR(c2, cptr, cbuf_end);
+                        if (s->ignore_case) {
+                            c1 = lre_canonicalize(c1, s->is_utf16);
+                            c2 = lre_canonicalize(c2, s->is_utf16);
+                        }
+                        if (c1 != c2)
+                            goto no_match;
+                    }
+                } else {
+                    cptr1 = cptr1_end;
+                    while (cptr1 > cptr1_start) {
+                        if (cptr == s->cbuf)
+                            goto no_match;
+                        GET_PREV_CHAR(c1, cptr1, cptr1_start);
+                        GET_PREV_CHAR(c2, cptr, s->cbuf);
+                        if (s->ignore_case) {
+                            c1 = lre_canonicalize(c1, s->is_utf16);
+                            c2 = lre_canonicalize(c2, s->is_utf16);
+                        }
+                        if (c1 != c2)
+                            goto no_match;
+                    }
+                }
+            }
+            break;
+        case REOP_range:
+            {
+                int n;
+                uint32_t low, high, idx_min, idx_max, idx;
+                
+                n = get_u16(pc); /* n must be >= 1 */
+                pc += 2;
+                if (cptr >= cbuf_end)
+                    goto no_match;
+                GET_CHAR(c, cptr, cbuf_end);
+                if (s->ignore_case) {
+                    c = lre_canonicalize(c, s->is_utf16);
+                }
+                idx_min = 0;
+                low = get_u16(pc + 0 * 4);
+                if (c < low)
+                    goto no_match;
+                idx_max = n - 1;
+                high = get_u16(pc + idx_max * 4 + 2);
+                /* 0xffff in for last value means +infinity */
+                if (unlikely(c >= 0xffff) && high == 0xffff)
+                    goto range_match;
+                if (c > high)
+                    goto no_match;
+                while (idx_min <= idx_max) {
+                    idx = (idx_min + idx_max) / 2;
+                    low = get_u16(pc + idx * 4);
+                    high = get_u16(pc + idx * 4 + 2);
+                    if (c < low)
+                        idx_max = idx - 1;
+                    else if (c > high)
+                        idx_min = idx + 1;
+                    else
+                        goto range_match;
+                }
+                goto no_match;
+            range_match:
+                pc += 4 * n;
+            }
+            break;
+        case REOP_range32:
+            {
+                int n;
+                uint32_t low, high, idx_min, idx_max, idx;
+                
+                n = get_u16(pc); /* n must be >= 1 */
+                pc += 2;
+                if (cptr >= cbuf_end)
+                    goto no_match;
+                GET_CHAR(c, cptr, cbuf_end);
+                if (s->ignore_case) {
+                    c = lre_canonicalize(c, s->is_utf16);
+                }
+                idx_min = 0;
+                low = get_u32(pc + 0 * 8);
+                if (c < low)
+                    goto no_match;
+                idx_max = n - 1;
+                high = get_u32(pc + idx_max * 8 + 4);
+                if (c > high)
+                    goto no_match;
+                while (idx_min <= idx_max) {
+                    idx = (idx_min + idx_max) / 2;
+                    low = get_u32(pc + idx * 8);
+                    high = get_u32(pc + idx * 8 + 4);
+                    if (c < low)
+                        idx_max = idx - 1;
+                    else if (c > high)
+                        idx_min = idx + 1;
+                    else
+                        goto range32_match;
+                }
+                goto no_match;
+            range32_match:
+                pc += 8 * n;
+            }
+            break;
+        case REOP_prev:
+            /* go to the previous char */
+            if (cptr == s->cbuf)
+                goto no_match;
+            PREV_CHAR(cptr, s->cbuf);
+            break;
+        case REOP_simple_greedy_quant:
+            {
+                uint32_t next_pos, quant_min, quant_max;
+                size_t q;
+                intptr_t res;
+                const uint8_t *pc1;
+                
+                next_pos = get_u32(pc);
+                quant_min = get_u32(pc + 4);
+                quant_max = get_u32(pc + 8);
+                pc += 16;
+                pc1 = pc;
+                pc += (int)next_pos;
+                
+                q = 0;
+                for(;;) {
+                    res = lre_exec_backtrack(s, capture, stack, stack_len,
+                                             pc1, cptr, TRUE);
+                    if (res == -1)
+                        return res;
+                    if (!res)
+                        break;
+                    cptr = (uint8_t *)res;
+                    q++;
+                    if (q >= quant_max && quant_max != INT32_MAX)
+                        break;
+                }
+                if (q < quant_min)
+                    goto no_match;
+                if (q > quant_min) {
+                    /* will examine all matches down to quant_min */
+                    ret = push_state(s, capture, stack, stack_len,
+                                     pc1 - 16, cptr,
+                                     RE_EXEC_STATE_GREEDY_QUANT,
+                                     q - quant_min);
+                    if (ret < 0)
+                        return -1;
+                }
+            }
+            break;
+        default:
+            abort();
+        }
+    }
+}
+
+/* Return 1 if match, 0 if not match or -1 if error. cindex is the
+   starting position of the match and must be such as 0 <= cindex <=
+   clen. */
+int lre_exec(uint8_t **capture,
+             const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen,
+             int cbuf_type, void *opaque)
+{
+    REExecContext s_s, *s = &s_s;
+    int re_flags, i, alloca_size, ret;
+    StackInt *stack_buf;
+    
+    re_flags = bc_buf[RE_HEADER_FLAGS];
+    s->multi_line = (re_flags & LRE_FLAG_MULTILINE) != 0;
+    s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0;
+    s->is_utf16 = (re_flags & LRE_FLAG_UTF16) != 0;
+    s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT];
+    s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE];
+    s->cbuf = cbuf;
+    s->cbuf_end = cbuf + (clen << cbuf_type);
+    s->cbuf_type = cbuf_type;
+    if (s->cbuf_type == 1 && s->is_utf16)
+        s->cbuf_type = 2;
+    s->opaque = opaque;
+
+    s->state_size = sizeof(REExecState) +
+        s->capture_count * sizeof(capture[0]) * 2 +
+        s->stack_size_max * sizeof(stack_buf[0]);
+    s->state_stack = NULL;
+    s->state_stack_len = 0;
+    s->state_stack_size = 0;
+    
+    for(i = 0; i < s->capture_count * 2; i++)
+        capture[i] = NULL;
+    alloca_size = s->stack_size_max * sizeof(stack_buf[0]);
+    stack_buf = alloca(alloca_size);
+    ret = lre_exec_backtrack(s, capture, stack_buf, 0, bc_buf + RE_HEADER_LEN,
+                             cbuf + (cindex << cbuf_type), FALSE);
+    lre_realloc(s->opaque, s->state_stack, 0);
+    return ret;
+}
+
+int lre_get_capture_count(const uint8_t *bc_buf)
+{
+    return bc_buf[RE_HEADER_CAPTURE_COUNT];
+}
+
+int lre_get_flags(const uint8_t *bc_buf)
+{
+    return bc_buf[RE_HEADER_FLAGS];
+}
+
+#ifdef TEST
+
+BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size)
+{
+    return FALSE;
+}
+
+void *lre_realloc(void *opaque, void *ptr, size_t size)
+{
+    return realloc(ptr, size);
+}
+
+int main(int argc, char **argv)
+{
+    int len, ret, i;
+    uint8_t *bc;
+    char error_msg[64];
+    uint8_t *capture[CAPTURE_COUNT_MAX * 2];
+    const char *input;
+    int input_len, capture_count;
+    
+    if (argc < 3) {
+        printf("usage: %s regexp input\n", argv[0]);
+        exit(1);
+    }
+    bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1],
+                     strlen(argv[1]), 0, NULL);
+    if (!bc) {
+        fprintf(stderr, "error: %s\n", error_msg);
+        exit(1);
+    }
+
+    input = argv[2];
+    input_len = strlen(input);
+    
+    ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL);
+    printf("ret=%d\n", ret);
+    if (ret == 1) {
+        capture_count = lre_get_capture_count(bc);
+        for(i = 0; i < 2 * capture_count; i++) {
+            uint8_t *ptr;
+            ptr = capture[i];
+            printf("%d: ", i);
+            if (!ptr)
+                printf("<nil>");
+            else
+                printf("%u", (int)(ptr - (uint8_t *)input));
+            printf("\n");
+        }
+    }
+    return 0;
+}
+#endif
diff --git a/libregexp.h b/libregexp.h
new file mode 100644
index 0000000..cd0b24f
--- /dev/null
+++ b/libregexp.h
@@ -0,0 +1,91 @@
+/*
+ * Regular Expression Engine
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef LIBREGEXP_H
+#define LIBREGEXP_H
+
+#include <stddef.h>
+
+#include "libunicode.h"
+
+#define LRE_BOOL  int       /* for documentation purposes */
+
+#define LRE_FLAG_GLOBAL     (1 << 0)
+#define LRE_FLAG_IGNORECASE (1 << 1)
+#define LRE_FLAG_MULTILINE  (1 << 2)
+#define LRE_FLAG_DOTALL     (1 << 3)
+#define LRE_FLAG_UTF16      (1 << 4)
+#define LRE_FLAG_STICKY     (1 << 5)
+
+#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */
+
+uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size,
+                     const char *buf, size_t buf_len, int re_flags,
+                     void *opaque);
+int lre_get_capture_count(const uint8_t *bc_buf);
+int lre_get_flags(const uint8_t *bc_buf);
+int lre_exec(uint8_t **capture,
+             const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen,
+             int cbuf_type, void *opaque);
+
+int lre_parse_escape(const uint8_t **pp, int allow_utf16);
+LRE_BOOL lre_is_space(int c);
+
+/* must be provided by the user */
+LRE_BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size); 
+void *lre_realloc(void *opaque, void *ptr, size_t size);
+
+/* JS identifier test */
+extern uint32_t const lre_id_start_table_ascii[4];
+extern uint32_t const lre_id_continue_table_ascii[4];
+
+static inline int lre_js_is_ident_first(int c)
+{
+    if ((uint32_t)c < 128) {
+        return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1;
+    } else {
+#ifdef CONFIG_ALL_UNICODE
+        return lre_is_id_start(c);
+#else
+        return !lre_is_space(c);
+#endif
+    }
+}
+
+static inline int lre_js_is_ident_next(int c)
+{
+    if ((uint32_t)c < 128) {
+        return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1;
+    } else {
+        /* ZWNJ and ZWJ are accepted in identifiers */
+#ifdef CONFIG_ALL_UNICODE
+        return lre_is_id_continue(c) || c == 0x200C || c == 0x200D;
+#else
+        return !lre_is_space(c) || c == 0x200C || c == 0x200D;
+#endif
+    }
+}
+
+#undef LRE_BOOL
+
+#endif /* LIBREGEXP_H */
diff --git a/libunicode-table.h b/libunicode-table.h
new file mode 100644
index 0000000..521f2f3
--- /dev/null
+++ b/libunicode-table.h
@@ -0,0 +1,4313 @@
+/* Compressed unicode tables */
+/* Automatically generated file - do not edit */
+
+#include <stdint.h>
+
+static const uint32_t case_conv_table1[359] = {
+    0x00209a30, 0x00309a00, 0x005a8173, 0x00601730,
+    0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700,
+    0x007f8100, 0x00803040, 0x009801c3, 0x00988190,
+    0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40,
+    0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100,
+    0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240,
+    0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130,
+    0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130,
+    0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240,
+    0x00cd0100, 0x00ce0130, 0x00ce8130, 0x00cf0100,
+    0x00cf8130, 0x00d00640, 0x00d30130, 0x00d38240,
+    0x00d48130, 0x00d60240, 0x00d70130, 0x00d78240,
+    0x00d88230, 0x00d98440, 0x00db8130, 0x00dc0240,
+    0x00de0240, 0x00df8100, 0x00e20350, 0x00e38350,
+    0x00e50350, 0x00e69040, 0x00ee8100, 0x00ef1240,
+    0x00f801b4, 0x00f88350, 0x00fa0240, 0x00fb0130,
+    0x00fb8130, 0x00fc2840, 0x01100130, 0x01111240,
+    0x011d0131, 0x011d8240, 0x011e8130, 0x011f0131,
+    0x011f8201, 0x01208240, 0x01218130, 0x01220130,
+    0x01228130, 0x01230a40, 0x01280101, 0x01288101,
+    0x01290101, 0x01298100, 0x012a0100, 0x012b0200,
+    0x012c8100, 0x012d8100, 0x012e0101, 0x01300100,
+    0x01308101, 0x01318100, 0x01328101, 0x01330101,
+    0x01340100, 0x01348100, 0x01350101, 0x01358101,
+    0x01360101, 0x01378100, 0x01388101, 0x01390100,
+    0x013a8100, 0x013e8101, 0x01400100, 0x01410101,
+    0x01418100, 0x01438101, 0x01440100, 0x01448100,
+    0x01450200, 0x01460100, 0x01490100, 0x014e8101,
+    0x014f0101, 0x01a28173, 0x01b80440, 0x01bb0240,
+    0x01bd8300, 0x01bf8130, 0x01c30130, 0x01c40330,
+    0x01c60130, 0x01c70230, 0x01c801d0, 0x01c89130,
+    0x01d18930, 0x01d60100, 0x01d68300, 0x01d801d3,
+    0x01d89100, 0x01e10173, 0x01e18900, 0x01e60100,
+    0x01e68200, 0x01e78130, 0x01e80173, 0x01e88173,
+    0x01ea8173, 0x01eb0173, 0x01eb8100, 0x01ec1840,
+    0x01f80173, 0x01f88173, 0x01f90100, 0x01f98100,
+    0x01fa01a0, 0x01fa8173, 0x01fb8240, 0x01fc8130,
+    0x01fd0240, 0x01fe8330, 0x02001030, 0x02082030,
+    0x02182000, 0x02281000, 0x02302240, 0x02453640,
+    0x02600130, 0x02608e40, 0x02678100, 0x02686040,
+    0x0298a630, 0x02b0a600, 0x02c381b5, 0x08502631,
+    0x08638131, 0x08668131, 0x08682b00, 0x087e8300,
+    0x09d05011, 0x09f80610, 0x09fc0620, 0x0e400174,
+    0x0e408174, 0x0e410174, 0x0e418174, 0x0e420174,
+    0x0e428174, 0x0e430174, 0x0e438180, 0x0e440180,
+    0x0e482b30, 0x0e5e8330, 0x0ebc8101, 0x0ebe8101,
+    0x0ec70101, 0x0f007e40, 0x0f3f1840, 0x0f4b01b5,
+    0x0f4b81b6, 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7,
+    0x0f4d8180, 0x0f4f0130, 0x0f506040, 0x0f800800,
+    0x0f840830, 0x0f880600, 0x0f8c0630, 0x0f900800,
+    0x0f940830, 0x0f980800, 0x0f9c0830, 0x0fa00600,
+    0x0fa40630, 0x0fa801b0, 0x0fa88100, 0x0fa901d3,
+    0x0fa98100, 0x0faa01d3, 0x0faa8100, 0x0fab01d3,
+    0x0fab8100, 0x0fac8130, 0x0fad8130, 0x0fae8130,
+    0x0faf8130, 0x0fb00800, 0x0fb40830, 0x0fb80200,
+    0x0fb90400, 0x0fbb0200, 0x0fbc0201, 0x0fbd0201,
+    0x0fbe0201, 0x0fc008b7, 0x0fc40867, 0x0fc808b8,
+    0x0fcc0868, 0x0fd008b8, 0x0fd40868, 0x0fd80200,
+    0x0fd901b9, 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1,
+    0x0fdb81d7, 0x0fdc0230, 0x0fdd0230, 0x0fde0161,
+    0x0fdf0173, 0x0fe101b9, 0x0fe181b2, 0x0fe201ba,
+    0x0fe301b2, 0x0fe381d8, 0x0fe40430, 0x0fe60162,
+    0x0fe80200, 0x0fe901d0, 0x0fe981d0, 0x0feb01b0,
+    0x0feb81d0, 0x0fec0230, 0x0fed0230, 0x0ff00201,
+    0x0ff101d3, 0x0ff181d3, 0x0ff201ba, 0x0ff28101,
+    0x0ff301b0, 0x0ff381d3, 0x0ff40230, 0x0ff50230,
+    0x0ff60131, 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb,
+    0x0ffb01b2, 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230,
+    0x0ffe0162, 0x109301a0, 0x109501a0, 0x109581a0,
+    0x10990131, 0x10a70101, 0x10b01031, 0x10b81001,
+    0x10c18240, 0x125b1a31, 0x12681a01, 0x16002f31,
+    0x16182f01, 0x16300240, 0x16310130, 0x16318130,
+    0x16320130, 0x16328100, 0x16330100, 0x16338640,
+    0x16368130, 0x16370130, 0x16378130, 0x16380130,
+    0x16390240, 0x163a8240, 0x163f0230, 0x16406440,
+    0x16758440, 0x16790240, 0x16802600, 0x16938100,
+    0x16968100, 0x53202e40, 0x53401c40, 0x53910e40,
+    0x53993e40, 0x53bc8440, 0x53be8130, 0x53bf0a40,
+    0x53c58240, 0x53c68130, 0x53c80440, 0x53ca0101,
+    0x53cb1440, 0x53d50130, 0x53d58130, 0x53d60130,
+    0x53d68130, 0x53d70130, 0x53d80130, 0x53d88130,
+    0x53d90130, 0x53d98131, 0x53da0c40, 0x53e10240,
+    0x53e20131, 0x53e28130, 0x53e30130, 0x55a98101,
+    0x55b85020, 0x7d8001b2, 0x7d8081b2, 0x7d8101b2,
+    0x7d8181da, 0x7d8201da, 0x7d8281b3, 0x7d8301b3,
+    0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, 0x7d8b01bc,
+    0x7d8b81bb, 0x7f909a31, 0x7fa09a01, 0x82002831,
+    0x82142801, 0x82582431, 0x826c2401, 0x86403331,
+    0x86603301, 0x8c502031, 0x8c602001, 0xb7202031,
+    0xb7302001, 0xf4802231, 0xf4912201,
+};
+
+static const uint8_t case_conv_table2[359] = {
+    0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04,
+    0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00,
+    0x08, 0x00, 0x53, 0x4a, 0x51, 0x00, 0x52, 0x00,
+    0x53, 0x00, 0x3a, 0x54, 0x55, 0x00, 0x57, 0x59,
+    0x3f, 0x5d, 0x5c, 0x00, 0x46, 0x61, 0x63, 0x42,
+    0x64, 0x00, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00,
+    0x6c, 0x00, 0x6e, 0x00, 0x00, 0x40, 0x00, 0x00,
+    0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, 0x20,
+    0x35, 0x00, 0x27, 0x00, 0x21, 0x00, 0x24, 0x22,
+    0x2a, 0x00, 0x13, 0x6b, 0x6d, 0x00, 0x26, 0x24,
+    0x27, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x3e, 0x1e,
+    0x3f, 0x1f, 0x39, 0x3d, 0x22, 0x21, 0x41, 0x1e,
+    0x40, 0x25, 0x25, 0x26, 0x28, 0x20, 0x2a, 0x49,
+    0x2c, 0x43, 0x2e, 0x4b, 0x30, 0x4c, 0x32, 0x44,
+    0x42, 0x99, 0x00, 0x00, 0x95, 0x8f, 0x7d, 0x7e,
+    0x83, 0x84, 0x12, 0x80, 0x82, 0x76, 0x77, 0x12,
+    0x7b, 0xa3, 0x7c, 0x78, 0x79, 0x8a, 0x92, 0x98,
+    0xa6, 0xa0, 0x85, 0x00, 0x9a, 0xa1, 0x93, 0x75,
+    0x33, 0x95, 0x00, 0x8e, 0x00, 0x74, 0x99, 0x98,
+    0x97, 0x96, 0x00, 0x00, 0x9e, 0x00, 0x9c, 0x00,
+    0xa1, 0xa0, 0x15, 0x2e, 0x2f, 0x30, 0xb4, 0xb5,
+    0x4c, 0xaa, 0xa9, 0x12, 0x14, 0x1e, 0x21, 0x22,
+    0x22, 0x2a, 0x34, 0x35, 0xa6, 0xa7, 0x36, 0x1f,
+    0x4a, 0x00, 0x00, 0x97, 0x01, 0x5a, 0xda, 0x1d,
+    0x36, 0x05, 0x00, 0xc4, 0xc3, 0xc6, 0xc5, 0xc8,
+    0xc7, 0xca, 0xc9, 0xcc, 0xcb, 0xc4, 0xd5, 0x45,
+    0xd6, 0x42, 0xd7, 0x46, 0xd8, 0xce, 0xd0, 0xd2,
+    0xd4, 0xda, 0xd9, 0xee, 0xf6, 0xfe, 0x0e, 0x07,
+    0x0f, 0x80, 0x9f, 0x00, 0x21, 0x80, 0xa3, 0xed,
+    0x00, 0xc0, 0x40, 0xc6, 0x60, 0xe7, 0xdb, 0xe6,
+    0x99, 0xc0, 0x00, 0x00, 0x06, 0x60, 0xdc, 0x29,
+    0xfd, 0x15, 0x12, 0x06, 0x16, 0xf8, 0xdd, 0x06,
+    0x15, 0x12, 0x84, 0x08, 0xc6, 0x16, 0xff, 0xdf,
+    0x03, 0xc0, 0x40, 0x00, 0x46, 0x60, 0xde, 0xe0,
+    0x6d, 0x37, 0x38, 0x39, 0x15, 0x14, 0x17, 0x16,
+    0x00, 0x1a, 0x19, 0x1c, 0x1b, 0x00, 0x5f, 0xb7,
+    0x65, 0x44, 0x47, 0x00, 0x4f, 0x62, 0x4e, 0x50,
+    0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0xa3, 0xa4,
+    0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x00,
+    0x00, 0x5a, 0x00, 0x48, 0x00, 0x5b, 0x56, 0x58,
+    0x60, 0x5e, 0x70, 0x69, 0x6f, 0x4b, 0x00, 0x00,
+    0x3b, 0x67, 0xb8, 0x45, 0xa8, 0x8a, 0x8b, 0x8c,
+    0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, 0x6f,
+    0xb2, 0x5a, 0x59, 0x5c, 0x5b, 0x5e, 0x5d, 0x60,
+    0x5f, 0x62, 0x61, 0x64, 0x63, 0x66, 0x65,
+};
+
+static const uint16_t case_conv_ext[58] = {
+    0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391,
+    0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307,
+    0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331,
+    0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80,
+    0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca,
+    0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546,
+    0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9,
+    0x006b, 0x00e5,
+};
+
+static const uint8_t unicode_prop_Cased1_table[172] = {
+    0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3,
+    0x80, 0x8c, 0x80, 0x8d, 0x81, 0x8d, 0x02, 0x80,
+    0xe1, 0x80, 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01,
+    0x11, 0x00, 0x01, 0x04, 0x08, 0x01, 0x08, 0x30,
+    0x08, 0x01, 0x15, 0x20, 0x00, 0x39, 0x99, 0x31,
+    0x9d, 0x84, 0x40, 0x94, 0x80, 0xd6, 0x82, 0xa6,
+    0x80, 0x41, 0x62, 0x80, 0xa6, 0x80, 0x57, 0x76,
+    0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb,
+    0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f,
+    0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28,
+    0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b,
+    0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79,
+    0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94,
+    0x05, 0x80, 0x98, 0x80, 0xc7, 0x82, 0x43, 0x34,
+    0xa2, 0x06, 0x80, 0x8b, 0x61, 0x28, 0x97, 0xd4,
+    0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b,
+    0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80,
+    0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, 0x53,
+    0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98,
+    0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98,
+    0x80, 0x9e, 0x80, 0x98, 0x07, 0x59, 0x63, 0x99,
+    0x85, 0x99, 0x85, 0x99,
+};
+
+static const uint8_t unicode_prop_Cased1_index[18] = {
+    0xb9, 0x02, 0xe0, 0xa0, 0x1e, 0x40, 0x9e, 0xa6,
+    0x40, 0xba, 0xd4, 0x01, 0x89, 0xd7, 0x01, 0x8a,
+    0xf1, 0x01,
+};
+
+static const uint8_t unicode_prop_Case_Ignorable_table[678] = {
+    0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80,
+    0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6,
+    0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40,
+    0xfa, 0x86, 0x40, 0xce, 0x80, 0xb6, 0xac, 0x00,
+    0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, 0x89,
+    0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, 0x80,
+    0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, 0x9d,
+    0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, 0x97,
+    0x97, 0xaa, 0x82, 0xf6, 0xaf, 0xb6, 0x00, 0x03,
+    0x3b, 0x02, 0x86, 0x89, 0x81, 0x8c, 0x80, 0x8e,
+    0x80, 0xb9, 0x03, 0x1f, 0x80, 0x93, 0x81, 0x99,
+    0x01, 0x81, 0xb8, 0x03, 0x0b, 0x09, 0x12, 0x80,
+    0x9d, 0x0a, 0x80, 0x8a, 0x81, 0xb8, 0x03, 0x20,
+    0x0b, 0x80, 0x93, 0x81, 0x95, 0x28, 0x80, 0xb9,
+    0x01, 0x00, 0x1f, 0x07, 0x80, 0x8a, 0x81, 0x9d,
+    0x80, 0xbc, 0x80, 0x8b, 0x80, 0xb1, 0x02, 0x80,
+    0xb8, 0x14, 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c,
+    0x80, 0xb9, 0x01, 0x05, 0x04, 0x81, 0x93, 0x81,
+    0x9b, 0x81, 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81,
+    0xe5, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a,
+    0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x85, 0xc9,
+    0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04,
+    0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5,
+    0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f,
+    0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd,
+    0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c,
+    0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37,
+    0x01, 0x8a, 0x10, 0x20, 0xac, 0x83, 0xb3, 0x80,
+    0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88,
+    0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00,
+    0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07,
+    0x8e, 0xc0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80,
+    0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82,
+    0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81,
+    0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28,
+    0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xde, 0x04,
+    0x41, 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82,
+    0x8c, 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89,
+    0x01, 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c,
+    0x80, 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81,
+    0xf0, 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f,
+    0xae, 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24,
+    0x80, 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15,
+    0x80, 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1,
+    0x18, 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d,
+    0xa1, 0xcd, 0x80, 0x96, 0x82, 0xec, 0x0f, 0x02,
+    0x03, 0x80, 0x98, 0x81, 0x40, 0x9c, 0x81, 0x99,
+    0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, 0xad,
+    0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, 0x94,
+    0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, 0x80,
+    0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, 0x0c,
+    0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, 0x80,
+    0xe3, 0x84, 0x40, 0x84, 0x01, 0x03, 0x80, 0x60,
+    0x4f, 0x2f, 0x80, 0x40, 0x92, 0x8f, 0x42, 0x3d,
+    0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, 0x80, 0x40,
+    0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00,
+    0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, 0x80, 0x94,
+    0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, 0x80, 0x40,
+    0x94, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0xa7,
+    0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83,
+    0x42, 0x1d, 0x8a, 0x40, 0xaf, 0x80, 0xb5, 0x8e,
+    0xb7, 0x82, 0xb0, 0x19, 0x09, 0x80, 0x8e, 0x80,
+    0xb1, 0x82, 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b,
+    0x81, 0xb3, 0x88, 0x89, 0x83, 0xe1, 0x11, 0x00,
+    0x0d, 0x80, 0x40, 0x9f, 0x02, 0x87, 0x94, 0x81,
+    0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0x40, 0xc2,
+    0x39, 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03,
+    0x08, 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a,
+    0x81, 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01,
+    0x28, 0x80, 0xe4, 0x11, 0x18, 0x84, 0x41, 0x02,
+    0x88, 0x01, 0x41, 0x98, 0x19, 0x0b, 0x80, 0x9f,
+    0x89, 0xa7, 0x29, 0x1f, 0x80, 0x88, 0x29, 0x82,
+    0xad, 0x8c, 0x01, 0x41, 0x95, 0x30, 0x28, 0x80,
+    0xd1, 0x95, 0x0e, 0x01, 0x01, 0xf9, 0x2a, 0x00,
+    0x08, 0x30, 0x80, 0xc7, 0x0a, 0x00, 0x80, 0x41,
+    0x5a, 0x81, 0x55, 0x3a, 0x88, 0x60, 0x36, 0xb6,
+    0x84, 0xba, 0x86, 0x88, 0x83, 0x44, 0x0a, 0x80,
+    0xbe, 0x90, 0xbf, 0x08, 0x80, 0x60, 0x4c, 0xb8,
+    0x08, 0x83, 0x54, 0xc2, 0x82, 0x88, 0x8f, 0x0e,
+    0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, 0xba, 0xb6,
+    0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e,
+    0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0x41,
+    0x04, 0x8d, 0x41, 0xad, 0x83, 0x45, 0xdf, 0x86,
+    0xec, 0x87, 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00,
+    0x80, 0x9d, 0xdf, 0xff, 0x40, 0xef,
+};
+
+static const uint8_t unicode_prop_Case_Ignorable_index[66] = {
+    0xc0, 0x05, 0x00, 0x2e, 0x08, 0x20, 0x52, 0x0a,
+    0x00, 0x05, 0x0c, 0x00, 0x4f, 0x0e, 0x20, 0x75,
+    0x10, 0x20, 0x44, 0x18, 0x00, 0x43, 0x1b, 0x00,
+    0x00, 0x1e, 0x00, 0x7e, 0x2c, 0x00, 0x7e, 0xa6,
+    0x40, 0x83, 0xa9, 0x20, 0xf7, 0xaa, 0x00, 0x41,
+    0xff, 0x20, 0x28, 0x0d, 0x01, 0x3f, 0x12, 0x41,
+    0xde, 0x15, 0x21, 0x5c, 0x1a, 0x01, 0xf5, 0x6a,
+    0x21, 0x37, 0xda, 0x01, 0x02, 0x00, 0x2e, 0xf0,
+    0x01, 0x0e,
+};
+
+static const uint8_t unicode_prop_ID_Start_table[1024] = {
+    0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03,
+    0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83,
+    0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20,
+    0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80,
+    0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80,
+    0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac,
+    0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81,
+    0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8,
+    0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95,
+    0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a,
+    0xb4, 0x94, 0x07, 0xc5, 0xb5, 0x10, 0x91, 0x06,
+    0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, 0x95, 0x06,
+    0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, 0x08, 0x82,
+    0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, 0x95, 0x06,
+    0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, 0x92, 0x82,
+    0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, 0x01, 0x04,
+    0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, 0x80, 0x8a,
+    0x39, 0x09, 0x95, 0x06, 0x01, 0x04, 0x10, 0x9d,
+    0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, 0x2a, 0x10,
+    0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, 0x8b, 0x95,
+    0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, 0x8f, 0x10,
+    0x99, 0x14, 0x81, 0x9d, 0x03, 0x38, 0x10, 0x96,
+    0x80, 0x89, 0x04, 0x10, 0x9f, 0x00, 0x81, 0x8e,
+    0x81, 0x91, 0x38, 0x10, 0xa8, 0x08, 0x8f, 0x04,
+    0x17, 0x82, 0x97, 0x2c, 0x91, 0x82, 0x97, 0x80,
+    0x88, 0x00, 0x0e, 0xb9, 0xaf, 0x01, 0x8b, 0x86,
+    0xb9, 0x08, 0x00, 0x20, 0x97, 0x00, 0x80, 0x89,
+    0x01, 0x88, 0x01, 0x20, 0x80, 0x94, 0x83, 0x9f,
+    0x80, 0xbe, 0x38, 0xa3, 0x9a, 0x84, 0xf2, 0xaa,
+    0x93, 0x80, 0x8f, 0x2b, 0x1a, 0x02, 0x0e, 0x13,
+    0x8c, 0x8b, 0x80, 0x90, 0xa5, 0x00, 0x20, 0x81,
+    0xaa, 0x80, 0x41, 0x4c, 0x03, 0x0e, 0x00, 0x03,
+    0x81, 0xa8, 0x03, 0x81, 0xa0, 0x03, 0x0e, 0x00,
+    0x03, 0x81, 0x8e, 0x80, 0xb8, 0x03, 0x81, 0xc2,
+    0xa4, 0x8f, 0x8f, 0xd5, 0x0d, 0x82, 0x42, 0x6b,
+    0x81, 0x90, 0x80, 0x99, 0x84, 0xca, 0x82, 0x8a,
+    0x86, 0x8c, 0x03, 0x8d, 0x91, 0x8d, 0x91, 0x8d,
+    0x8c, 0x02, 0x8e, 0xb3, 0xa2, 0x03, 0x80, 0xc2,
+    0xd8, 0x86, 0xa8, 0x00, 0x84, 0xc5, 0x89, 0x9e,
+    0xb0, 0x9d, 0x0c, 0x8a, 0xab, 0x83, 0x99, 0xb5,
+    0x96, 0x88, 0xb4, 0xd1, 0x80, 0xdc, 0xae, 0x90,
+    0x86, 0xb6, 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x99,
+    0xa3, 0xa8, 0x82, 0x89, 0xa3, 0x81, 0x88, 0x86,
+    0xaa, 0x0a, 0xa8, 0x18, 0x28, 0x0a, 0x04, 0x40,
+    0xbf, 0xbf, 0x41, 0x15, 0x0d, 0x81, 0xa5, 0x0d,
+    0x0f, 0x00, 0x00, 0x00, 0x80, 0x9e, 0x81, 0xb4,
+    0x06, 0x00, 0x12, 0x06, 0x13, 0x0d, 0x83, 0x8c,
+    0x22, 0x06, 0xf3, 0x80, 0x8c, 0x80, 0x8f, 0x8c,
+    0xe4, 0x03, 0x01, 0x89, 0x00, 0x0d, 0x28, 0x00,
+    0x00, 0x80, 0x8f, 0x0b, 0x24, 0x18, 0x90, 0xa8,
+    0x4a, 0x76, 0xae, 0x80, 0xae, 0x80, 0x40, 0x84,
+    0x2b, 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7,
+    0x30, 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30,
+    0x30, 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98,
+    0x88, 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9,
+    0x03, 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9a, 0xb4,
+    0x8f, 0x41, 0xff, 0x59, 0xb5, 0xc9, 0x60, 0x51,
+    0xef, 0x8f, 0x44, 0x8c, 0xc2, 0xad, 0x81, 0x41,
+    0x0c, 0x82, 0x8f, 0x89, 0x81, 0x93, 0xae, 0x8f,
+    0x9e, 0x81, 0xcf, 0xa6, 0x88, 0x81, 0xe6, 0x81,
+    0xb4, 0x0c, 0xaf, 0x8a, 0x02, 0x03, 0x80, 0x96,
+    0x9c, 0xb3, 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81,
+    0x8a, 0x9b, 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae,
+    0x9b, 0x80, 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8,
+    0x96, 0x10, 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1,
+    0x00, 0x11, 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a,
+    0x32, 0x8b, 0x29, 0x29, 0x85, 0x88, 0x30, 0x30,
+    0xaa, 0x80, 0x8b, 0x87, 0xf2, 0x9c, 0x60, 0x2b,
+    0xa3, 0x8b, 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03,
+    0x41, 0x6d, 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24,
+    0x00, 0x89, 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01,
+    0x80, 0xeb, 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81,
+    0xb5, 0xa7, 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3,
+    0x99, 0x85, 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d,
+    0x0a, 0xa2, 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01,
+    0x80, 0x8e, 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4,
+    0x41, 0x0a, 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c,
+    0x9d, 0x84, 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f,
+    0x04, 0xa9, 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3,
+    0x83, 0xa7, 0x87, 0xb3, 0x40, 0x9b, 0x41, 0x36,
+    0x88, 0x95, 0x89, 0x87, 0x40, 0x97, 0x29, 0x00,
+    0xab, 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88,
+    0x9e, 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99,
+    0xc5, 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10,
+    0x9c, 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b,
+    0x9a, 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91,
+    0xed, 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3,
+    0x41, 0xdb, 0x9c, 0x89, 0x07, 0x95, 0x40, 0x99,
+    0x96, 0x8b, 0xb4, 0xca, 0xac, 0x9f, 0x98, 0x99,
+    0xa3, 0x9c, 0x80, 0x8a, 0xa2, 0x10, 0x8b, 0xaf,
+    0x8d, 0x83, 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80,
+    0x98, 0xd3, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89,
+    0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01,
+    0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x40, 0x9d,
+    0xb4, 0x91, 0x83, 0x93, 0x80, 0x9f, 0xaf, 0x93,
+    0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, 0xa3,
+    0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, 0xc6,
+    0x9a, 0x40, 0xe4, 0xab, 0xf3, 0xbf, 0x9e, 0x80,
+    0x40, 0x9f, 0x39, 0xa6, 0x8f, 0x00, 0x80, 0x9b,
+    0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, 0xad,
+    0x92, 0x80, 0xa1, 0xb8, 0x41, 0x06, 0x88, 0x80,
+    0xa4, 0x90, 0x80, 0xb0, 0x9d, 0xef, 0x30, 0x08,
+    0xa5, 0x94, 0x80, 0x98, 0x28, 0x08, 0x9f, 0x8d,
+    0x80, 0x41, 0x46, 0x92, 0x41, 0x0c, 0x43, 0x99,
+    0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0xbb, 0x44,
+    0x2e, 0x4f, 0xd0, 0x42, 0x46, 0x60, 0x21, 0xb8,
+    0x42, 0x38, 0x86, 0x9e, 0xf0, 0x9d, 0x91, 0xaf,
+    0x8f, 0x83, 0x9e, 0x94, 0x84, 0x92, 0x42, 0xaf,
+    0xbf, 0xff, 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08,
+    0x80, 0x9b, 0x57, 0xf7, 0x87, 0x42, 0xf2, 0x60,
+    0x25, 0x0c, 0x41, 0x1e, 0xb0, 0x82, 0x90, 0x1f,
+    0x41, 0x8b, 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82,
+    0x88, 0x86, 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6,
+    0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06,
+    0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03,
+    0x04, 0x00, 0x16, 0x80, 0x41, 0x53, 0x81, 0x98,
+    0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e,
+    0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e,
+    0x80, 0x98, 0x07, 0x49, 0x33, 0xac, 0x89, 0x86,
+    0x8f, 0x80, 0x41, 0x70, 0xab, 0x45, 0x13, 0x40,
+    0xc4, 0xba, 0xc3, 0x30, 0x44, 0xb3, 0x18, 0x9a,
+    0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00,
+    0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08,
+    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06,
+    0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22,
+    0x04, 0x80, 0x90, 0x51, 0x43, 0x60, 0xa6, 0xd6,
+    0xa8, 0x50, 0x34, 0x8a, 0x40, 0xdd, 0x81, 0x56,
+    0x81, 0x8d, 0x5d, 0x30, 0x4c, 0x1e, 0x42, 0x1d,
+};
+
+static const uint8_t unicode_prop_ID_Start_index[96] = {
+    0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xb1, 0x09,
+    0x00, 0xba, 0x0a, 0x00, 0xd1, 0x0b, 0x20, 0x62,
+    0x0d, 0x40, 0x01, 0x0f, 0x20, 0x5e, 0x12, 0x00,
+    0xf9, 0x16, 0x00, 0x17, 0x1a, 0x20, 0xc0, 0x1d,
+    0x20, 0x9d, 0x20, 0x00, 0x68, 0x2d, 0x00, 0x00,
+    0x32, 0x20, 0xc0, 0xa7, 0x20, 0x29, 0xaa, 0x00,
+    0xa4, 0xd7, 0x20, 0xc8, 0xfd, 0x20, 0x75, 0x01,
+    0x01, 0x37, 0x07, 0x01, 0x36, 0x0a, 0x21, 0xf7,
+    0x0f, 0x21, 0xa9, 0x12, 0x01, 0x30, 0x16, 0x21,
+    0x8a, 0x1a, 0x01, 0x9a, 0x23, 0x01, 0x80, 0x6e,
+    0x21, 0x89, 0xbc, 0x21, 0xc1, 0xd6, 0x01, 0xc5,
+    0xe8, 0x21, 0x73, 0xee, 0x01, 0x1e, 0xfa, 0x02,
+};
+
+static const uint8_t unicode_prop_ID_Continue1_table[607] = {
+    0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47,
+    0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08,
+    0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf,
+    0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89,
+    0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89,
+    0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02,
+    0x04, 0xaa, 0x82, 0xf6, 0x8e, 0x80, 0xa0, 0xb5,
+    0x10, 0x91, 0x06, 0x89, 0x09, 0x89, 0x90, 0x82,
+    0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, 0x80, 0x89,
+    0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, 0x00, 0x23,
+    0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, 0x8a, 0x82,
+    0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, 0x09, 0x89,
+    0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x17,
+    0x81, 0x89, 0x09, 0x89, 0x91, 0x80, 0xba, 0x22,
+    0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, 0x8f, 0x84,
+    0xb8, 0x30, 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89,
+    0x90, 0x82, 0xb7, 0x00, 0x30, 0x10, 0x1e, 0x81,
+    0x8a, 0x09, 0x89, 0x8f, 0x83, 0xb6, 0x08, 0x30,
+    0x10, 0x83, 0x88, 0x80, 0x89, 0x09, 0x89, 0x91,
+    0x81, 0xc5, 0x03, 0x28, 0x00, 0x3d, 0x89, 0x09,
+    0xbc, 0x01, 0x86, 0x8b, 0x38, 0x89, 0xd6, 0x01,
+    0x88, 0x8a, 0x29, 0x89, 0xbd, 0x0d, 0x89, 0x8a,
+    0x00, 0x00, 0x03, 0x81, 0xb0, 0x93, 0x01, 0x84,
+    0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe3, 0x93, 0x80,
+    0x89, 0x8b, 0x1b, 0x10, 0x11, 0x32, 0x83, 0x8c,
+    0x8b, 0x80, 0x8e, 0x42, 0xbe, 0x82, 0x88, 0x88,
+    0x43, 0x9f, 0x82, 0x9c, 0x82, 0x9c, 0x81, 0x9d,
+    0x81, 0xbf, 0x9f, 0x88, 0x01, 0x89, 0xa0, 0x11,
+    0x89, 0x40, 0x8e, 0x80, 0xf5, 0x8b, 0x83, 0x8b,
+    0x89, 0x89, 0xff, 0x8a, 0xbb, 0x84, 0xb8, 0x89,
+    0x80, 0x9c, 0x81, 0x8a, 0x85, 0x89, 0x95, 0x8d,
+    0xc1, 0x84, 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88,
+    0x8b, 0x82, 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d,
+    0xaf, 0x93, 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10,
+    0x94, 0x18, 0x28, 0x0a, 0x40, 0xc5, 0xb9, 0x04,
+    0x42, 0x3e, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18,
+    0x82, 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80,
+    0xdf, 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0x60,
+    0x75, 0x84, 0x89, 0xc4, 0x03, 0x89, 0x9f, 0x81,
+    0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, 0x96,
+    0x84, 0xd7, 0x81, 0xb1, 0x91, 0x89, 0x89, 0x85,
+    0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, 0x8c, 0xab,
+    0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, 0x80, 0x89,
+    0x89, 0xae, 0x8d, 0x8b, 0x07, 0x09, 0x89, 0xa0,
+    0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, 0x80, 0xa8,
+    0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, 0x89, 0x60,
+    0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, 0x8f, 0x8f,
+    0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, 0xa4, 0x80,
+    0x42, 0xbc, 0x80, 0x40, 0xe1, 0x80, 0x40, 0x94,
+    0x84, 0x41, 0x24, 0x89, 0x45, 0x56, 0x10, 0x0c,
+    0x83, 0xa7, 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42,
+    0x3c, 0x1f, 0x89, 0x42, 0x0b, 0x8a, 0x40, 0xae,
+    0x82, 0xb4, 0x8e, 0x9e, 0x89, 0x8e, 0x83, 0xac,
+    0x8a, 0xb4, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89,
+    0x21, 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b,
+    0x82, 0x89, 0xd1, 0x8b, 0x28, 0x40, 0x9f, 0x8b,
+    0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82,
+    0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0x40, 0xbf,
+    0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89,
+    0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90,
+    0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0xd2, 0x8e,
+    0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, 0xa4, 0x89,
+    0x40, 0xe6, 0x31, 0x32, 0x80, 0x9b, 0x89, 0xa7,
+    0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, 0x41,
+    0x94, 0x38, 0x87, 0x8f, 0x89, 0xb7, 0x95, 0x80,
+    0x8d, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x07, 0x89,
+    0xaf, 0x20, 0x08, 0x27, 0x89, 0x41, 0x48, 0x83,
+    0x60, 0x4b, 0x68, 0x89, 0x40, 0x85, 0x84, 0xba,
+    0x86, 0x98, 0x89, 0x43, 0xf4, 0x00, 0xb6, 0x33,
+    0x60, 0x4d, 0x09, 0x81, 0x54, 0xc5, 0x22, 0x2f,
+    0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x45,
+    0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, 0xb1, 0x38,
+    0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, 0x4f, 0x30,
+    0x90, 0x0e, 0x01, 0x04, 0x41, 0x04, 0x86, 0x88,
+    0x89, 0x41, 0xa1, 0x8d, 0x45, 0xd5, 0x86, 0xec,
+    0x34, 0x89, 0x6c, 0x17, 0xa5, 0x40, 0xef,
+};
+
+static const uint8_t unicode_prop_ID_Continue1_index[57] = {
+    0xfa, 0x06, 0x00, 0x84, 0x09, 0x00, 0xf0, 0x0a,
+    0x00, 0x70, 0x0c, 0x00, 0xf4, 0x0d, 0x00, 0x4a,
+    0x10, 0x20, 0x1a, 0x18, 0x20, 0x74, 0x1b, 0x00,
+    0xe2, 0x20, 0x00, 0x28, 0xa8, 0x20, 0x7e, 0xaa,
+    0x20, 0x40, 0xff, 0x00, 0x03, 0x10, 0x21, 0xeb,
+    0x12, 0x01, 0x41, 0x16, 0x01, 0x40, 0x1c, 0x61,
+    0x37, 0x6b, 0x21, 0x76, 0xda, 0x01, 0xf0, 0x01,
+    0x0e,
+};
+
+#ifdef CONFIG_ALL_UNICODE
+
+static const uint8_t unicode_cc_table[831] = {
+    0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00,
+    0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03,
+    0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03,
+    0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2,
+    0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00,
+    0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea,
+    0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0,
+    0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00,
+    0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05,
+    0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00,
+    0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00,
+    0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc,
+    0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e,
+    0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00,
+    0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0,
+    0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81,
+    0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2,
+    0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00,
+    0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0,
+    0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc,
+    0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0,
+    0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc,
+    0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4,
+    0xaa, 0x02, 0xdc, 0xb0, 0x46, 0x00, 0xdc, 0xcd,
+    0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00,
+    0xdc, 0xc2, 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00,
+    0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00,
+    0x07, 0x8f, 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc,
+    0xc1, 0xb0, 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09,
+    0xaf, 0xc0, 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00,
+    0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09,
+    0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0,
+    0x4e, 0x00, 0x09, 0xb0, 0x4e, 0x00, 0x09, 0x86,
+    0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, 0x07,
+    0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, 0x8f,
+    0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, 0x3c,
+    0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, 0xb0,
+    0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, 0x7a,
+    0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, 0x80,
+    0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, 0x41,
+    0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, 0x81,
+    0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, 0xc1,
+    0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, 0x07,
+    0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, 0xb2,
+    0x9e, 0xc2, 0xb3, 0x83, 0x00, 0x09, 0x9e, 0x00,
+    0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, 0xb0,
+    0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, 0xc0,
+    0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, 0xb0,
+    0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, 0xdc,
+    0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, 0xb0,
+    0x45, 0x00, 0x07, 0x8e, 0x00, 0x09, 0xa5, 0xc0,
+    0x00, 0xdc, 0xc6, 0xb0, 0x05, 0x01, 0x09, 0xb0,
+    0x09, 0x00, 0x07, 0x8a, 0x01, 0x09, 0xb0, 0x12,
+    0x00, 0x07, 0xb0, 0x67, 0xc2, 0x41, 0x00, 0x04,
+    0xdc, 0xc1, 0x03, 0xdc, 0xc0, 0x41, 0x00, 0x05,
+    0x01, 0x83, 0x00, 0xdc, 0x85, 0xc0, 0x82, 0xc1,
+    0xb0, 0x95, 0xc1, 0x00, 0xdc, 0xc6, 0x00, 0xdc,
+    0xc1, 0x00, 0xea, 0x00, 0xd6, 0x00, 0xdc, 0x00,
+    0xca, 0xe4, 0x00, 0xe8, 0x01, 0xe4, 0x00, 0xdc,
+    0x80, 0xc0, 0x00, 0xe9, 0x00, 0xdc, 0xc0, 0x00,
+    0xdc, 0xb2, 0x9f, 0xc1, 0x01, 0x01, 0xc3, 0x02,
+    0x01, 0xc1, 0x83, 0xc0, 0x82, 0x01, 0x01, 0xc0,
+    0x00, 0xdc, 0xc0, 0x01, 0x01, 0x03, 0xdc, 0xc0,
+    0xb8, 0x03, 0xcd, 0xc2, 0xb0, 0x5c, 0x00, 0x09,
+    0xb0, 0x2f, 0xdf, 0xb1, 0xf9, 0x00, 0xda, 0x00,
+    0xe4, 0x00, 0xe8, 0x00, 0xde, 0x01, 0xe0, 0xb0,
+    0x38, 0x01, 0x08, 0xb8, 0x6d, 0xa3, 0xc0, 0x83,
+    0xc9, 0x9f, 0xc1, 0xb0, 0x1f, 0xc1, 0xb0, 0xe3,
+    0x00, 0x09, 0xb0, 0x8c, 0x00, 0x09, 0x9a, 0xd1,
+    0xb0, 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0,
+    0x2e, 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe,
+    0xc0, 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84,
+    0xc1, 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0,
+    0xc5, 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a,
+    0xb2, 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c,
+    0x00, 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64,
+    0xc4, 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7,
+    0xc0, 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09,
+    0xb0, 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3,
+    0xb1, 0xed, 0x01, 0xdc, 0xc2, 0x00, 0xdc, 0xc0,
+    0x03, 0xdc, 0xb0, 0xc4, 0x00, 0x09, 0xb0, 0x07,
+    0x00, 0x09, 0xb0, 0x08, 0x00, 0x09, 0x00, 0x07,
+    0xb0, 0x14, 0xc2, 0xaf, 0x01, 0x09, 0xb0, 0x0d,
+    0x00, 0x07, 0xb0, 0x1b, 0x00, 0x09, 0x88, 0x00,
+    0x07, 0xb0, 0x39, 0x00, 0x09, 0x00, 0x07, 0xb0,
+    0x81, 0x00, 0x07, 0x00, 0x09, 0xb0, 0x1f, 0x01,
+    0x07, 0x8f, 0x00, 0x09, 0x97, 0xc6, 0x82, 0xc4,
+    0xb0, 0x9c, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96,
+    0xc0, 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0,
+    0xca, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00,
+    0x09, 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0,
+    0x42, 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00,
+    0x07, 0xb1, 0x74, 0x00, 0x09, 0xb0, 0x22, 0x00,
+    0x09, 0x91, 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09,
+    0xb1, 0x74, 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07,
+    0x80, 0x01, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb8,
+    0x45, 0x27, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb8,
+    0x49, 0x36, 0x00, 0x01, 0xb8, 0x0c, 0x95, 0x01,
+    0xd8, 0x02, 0x01, 0x82, 0x00, 0xe2, 0x04, 0xd8,
+    0x87, 0x07, 0xdc, 0x81, 0xc4, 0x01, 0xdc, 0x9d,
+    0xc3, 0xb0, 0x63, 0xc2, 0xb8, 0x05, 0x8a, 0xc6,
+    0x80, 0xd0, 0x81, 0xc6, 0x80, 0xc1, 0x80, 0xc4,
+    0xb0, 0xd4, 0xc6, 0xb1, 0x84, 0xc3, 0xb5, 0xaf,
+    0x06, 0xdc, 0xb0, 0x3c, 0xc5, 0x00, 0x07,
+};
+
+static const uint8_t unicode_cc_index[78] = {
+    0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05,
+    0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0xe2,
+    0x08, 0x00, 0x53, 0x09, 0x00, 0xcd, 0x0b, 0x20,
+    0x38, 0x0e, 0x00, 0x73, 0x0f, 0x20, 0x5d, 0x13,
+    0x20, 0x60, 0x1a, 0x20, 0xe6, 0x1b, 0x20, 0xfa,
+    0x1c, 0x00, 0x00, 0x1e, 0x20, 0x80, 0x2d, 0x00,
+    0x06, 0xa8, 0x00, 0xbe, 0xaa, 0x00, 0x76, 0x03,
+    0x01, 0x4d, 0x0f, 0x01, 0xcb, 0x11, 0x21, 0x5e,
+    0x14, 0x01, 0x3b, 0x18, 0x21, 0xf0, 0x6a, 0x41,
+    0xaa, 0xd1, 0x01, 0x4b, 0xe9, 0x01,
+};
+
+static const uint32_t unicode_decomp_table1[687] = {
+    0x00280081, 0x002a0097, 0x002a8081, 0x002bc097,
+    0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097,
+    0x002e4115, 0x002f0199, 0x00302016, 0x00400842,
+    0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117,
+    0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342,
+    0x005240bf, 0x00530342, 0x00550942, 0x005a0842,
+    0x005e0096, 0x005e4342, 0x005fc081, 0x00680142,
+    0x006bc142, 0x00710185, 0x0071c317, 0x00734844,
+    0x00778344, 0x00798342, 0x007b02be, 0x007c4197,
+    0x007d0142, 0x007e0444, 0x00800e42, 0x00878142,
+    0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283,
+    0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097,
+    0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080,
+    0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad,
+    0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081,
+    0x0100023e, 0x0101c0be, 0x010301be, 0x010640be,
+    0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be,
+    0x011d8144, 0x01304144, 0x01340244, 0x01358144,
+    0x01368344, 0x01388344, 0x013a8644, 0x013e0144,
+    0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184,
+    0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084,
+    0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084,
+    0x028cc084, 0x028d8084, 0x029641ae, 0x02978084,
+    0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084,
+    0x02f281ae, 0x03120084, 0x03300084, 0x0331c122,
+    0x0332812e, 0x035281ae, 0x03768084, 0x037701ae,
+    0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081,
+    0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084,
+    0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e,
+    0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084,
+    0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084,
+    0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081,
+    0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120,
+    0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783,
+    0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942,
+    0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085,
+    0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122,
+    0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122,
+    0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e,
+    0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122,
+    0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be,
+    0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be,
+    0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be,
+    0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820,
+    0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080,
+    0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117,
+    0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080,
+    0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080,
+    0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080,
+    0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080,
+    0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080,
+    0x07ff8097, 0x0800011e, 0x08008495, 0x08044081,
+    0x0805c097, 0x08090081, 0x08094097, 0x08098099,
+    0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085,
+    0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3,
+    0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215,
+    0x0820051f, 0x08228583, 0x08254415, 0x082a0097,
+    0x08400119, 0x08408081, 0x0840c0bf, 0x08414119,
+    0x0841c081, 0x084240bf, 0x0842852d, 0x08454081,
+    0x08458097, 0x08464295, 0x08480097, 0x08484099,
+    0x08488097, 0x08490081, 0x08498080, 0x084a0081,
+    0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081,
+    0x084ec099, 0x084f0283, 0x08514295, 0x08540119,
+    0x0854809b, 0x0854c619, 0x0857c097, 0x08580081,
+    0x08584097, 0x08588099, 0x0858c097, 0x08590081,
+    0x08594097, 0x08598099, 0x0859c09b, 0x085a0097,
+    0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295,
+    0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081,
+    0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097,
+    0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215,
+    0x08624099, 0x0866813e, 0x086b80be, 0x087341be,
+    0x088100be, 0x088240be, 0x088300be, 0x088901be,
+    0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1,
+    0x089040be, 0x089100be, 0x0891c1be, 0x089801be,
+    0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144,
+    0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244,
+    0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523,
+    0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b,
+    0x092204a1, 0x09244525, 0x0926c099, 0x09270d25,
+    0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3,
+    0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be,
+    0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081,
+    0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383,
+    0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081,
+    0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122,
+    0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122,
+    0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085,
+    0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122,
+    0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084,
+    0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d,
+    0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703,
+    0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9,
+    0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089,
+    0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203,
+    0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d,
+    0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3,
+    0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523,
+    0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097,
+    0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad,
+    0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3,
+    0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1,
+    0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1,
+    0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7,
+    0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3,
+    0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3,
+    0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5,
+    0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1,
+    0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5,
+    0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1,
+    0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1,
+    0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3,
+    0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133,
+    0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1,
+    0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1,
+    0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099,
+    0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127,
+    0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099,
+    0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205,
+    0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1,
+    0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097,
+    0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f,
+    0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b,
+    0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117,
+    0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099,
+    0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525,
+    0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103,
+    0x29dc0081, 0x29fe0103, 0x2ad70203, 0x3e401482,
+    0x3e4a7f82, 0x3e6a3f82, 0x3e8aa102, 0x3e9b0110,
+    0x3e9c2f82, 0x3eb3c590, 0x3ec00197, 0x3ec0c119,
+    0x3ec1413f, 0x3ec4c2af, 0x3ec74184, 0x3ec804ad,
+    0x3eca4081, 0x3eca8304, 0x3ecc03a0, 0x3ece02a0,
+    0x3ecf8084, 0x3ed00120, 0x3ed0c120, 0x3ed184ae,
+    0x3ed3c085, 0x3ed4312d, 0x3ef4cbad, 0x3efa892f,
+    0x3eff022d, 0x3f002f2f, 0x3f1782a5, 0x3f18c0b1,
+    0x3f1907af, 0x3f1cffaf, 0x3f3c81a5, 0x3f3d64af,
+    0x3f542031, 0x3f649b31, 0x3f7c0131, 0x3f7c83b3,
+    0x3f7e40b1, 0x3f7e80bd, 0x3f7ec0bb, 0x3f7f00b3,
+    0x3f840503, 0x3f8c01ad, 0x3f8cc315, 0x3f8e462d,
+    0x3f91cc03, 0x3f97c695, 0x3f9c01af, 0x3f9d0085,
+    0x3f9d852f, 0x3fa03aad, 0x3fbd442f, 0x3fc06f1f,
+    0x3fd7c11f, 0x3fd85fad, 0x3fe80081, 0x3fe84f1f,
+    0x3ff0831f, 0x3ff2831f, 0x3ff4831f, 0x3ff6819f,
+    0x3ff80783, 0x44268192, 0x442ac092, 0x444b8112,
+    0x44d2c112, 0x452ec212, 0x456e8112, 0x74578392,
+    0x746ec312, 0x75000d1f, 0x75068d1f, 0x750d0d1f,
+    0x7513839f, 0x7515891f, 0x751a0d1f, 0x75208d1f,
+    0x75271015, 0x752f439f, 0x7531459f, 0x75340d1f,
+    0x753a8d1f, 0x75410395, 0x7543441f, 0x7545839f,
+    0x75478d1f, 0x754e0795, 0x7552839f, 0x75548d1f,
+    0x755b0d1f, 0x75618d1f, 0x75680d1f, 0x756e8d1f,
+    0x75750d1f, 0x757b8d1f, 0x75820d1f, 0x75888d1f,
+    0x758f0d1f, 0x75958d1f, 0x759c0d1f, 0x75a28d1f,
+    0x75a90103, 0x75aa089f, 0x75ae4081, 0x75ae839f,
+    0x75b04081, 0x75b08c9f, 0x75b6c081, 0x75b7032d,
+    0x75b8889f, 0x75bcc081, 0x75bd039f, 0x75bec081,
+    0x75bf0c9f, 0x75c54081, 0x75c5832d, 0x75c7089f,
+    0x75cb4081, 0x75cb839f, 0x75cd4081, 0x75cd8c9f,
+    0x75d3c081, 0x75d4032d, 0x75d5889f, 0x75d9c081,
+    0x75da039f, 0x75dbc081, 0x75dc0c9f, 0x75e24081,
+    0x75e2832d, 0x75e4089f, 0x75e84081, 0x75e8839f,
+    0x75ea4081, 0x75ea8c9f, 0x75f0c081, 0x75f1042d,
+    0x75f3851f, 0x75f6051f, 0x75f8851f, 0x75fb051f,
+    0x75fd851f, 0x7b80022d, 0x7b814dad, 0x7b884203,
+    0x7b89c081, 0x7b8a452d, 0x7b8d0403, 0x7b908081,
+    0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, 0x7ba84483,
+    0x7baac8ad, 0x7c400097, 0x7c404521, 0x7c440d25,
+    0x7c4a8087, 0x7c4ac115, 0x7c4b4117, 0x7c4c0d1f,
+    0x7c528217, 0x7c538099, 0x7c53c097, 0x7c5a8197,
+    0x7c640097, 0x7c80012f, 0x7c808081, 0x7c841603,
+    0x7c9004c1, 0x7c940103, 0xbe0001ac, 0xbe00d110,
+    0xbe0947ac, 0xbe0d3910, 0xbe29872c, 0xbe2d022c,
+    0xbe2e3790, 0xbe49ff90, 0xbe69bc10,
+};
+
+static const uint16_t unicode_decomp_table2[687] = {
+    0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008,
+    0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3,
+    0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8,
+    0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c,
+    0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192,
+    0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7,
+    0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218,
+    0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b,
+    0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275,
+    0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9,
+    0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1,
+    0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317,
+    0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341,
+    0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363,
+    0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385,
+    0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc,
+    0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1,
+    0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c,
+    0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614,
+    0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e,
+    0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9,
+    0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705,
+    0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737,
+    0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a,
+    0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c,
+    0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010,
+    0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db,
+    0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801,
+    0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830,
+    0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e,
+    0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069,
+    0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049,
+    0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4,
+    0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076,
+    0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7,
+    0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9,
+    0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923,
+    0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956,
+    0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978,
+    0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993,
+    0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f,
+    0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020,
+    0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1,
+    0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39,
+    0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59,
+    0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec,
+    0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a,
+    0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc,
+    0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c,
+    0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e,
+    0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca,
+    0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04,
+    0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45,
+    0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76,
+    0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad,
+    0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5,
+    0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a,
+    0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f,
+    0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1,
+    0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2,
+    0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153,
+    0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181,
+    0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab,
+    0xa76f, 0x11af, 0x11b3, 0x11bb, 0x120d, 0x130b, 0x1409, 0x148d,
+    0x1492, 0x1550, 0x1569, 0x156f, 0x1575, 0x157b, 0x1587, 0x1593,
+    0x002b, 0x159e, 0x15b6, 0x15ba, 0x15be, 0x15c2, 0x15c6, 0x15ca,
+    0x15de, 0x15e2, 0x1646, 0x165f, 0x1685, 0x168b, 0x1749, 0x174f,
+    0x1754, 0x1774, 0x1874, 0x187a, 0x190e, 0x19d0, 0x1a74, 0x1a7c,
+    0x1a9a, 0x1a9f, 0x1ab3, 0x1abd, 0x1ac3, 0x1ad7, 0x1adc, 0x1ae2,
+    0x1af0, 0x1b20, 0x1b2d, 0x1b35, 0x1b39, 0x1b4f, 0x1bc6, 0x1bd8,
+    0x1bda, 0x1bdc, 0x3164, 0x1c1d, 0x1c1f, 0x1c21, 0x1c23, 0x1c25,
+    0x1c27, 0x1c45, 0x1c53, 0x1c58, 0x1c61, 0x1c6a, 0x1c7c, 0x1c85,
+    0x1ca5, 0x1cc0, 0x1cc2, 0x1cc4, 0x1cc6, 0x1cc8, 0x1cca, 0x1ccc,
+    0x1cce, 0x1cee, 0x1cf0, 0x1cf2, 0x1cf4, 0x1cf6, 0x1cfd, 0x1cff,
+    0x1d01, 0x1d03, 0x1d12, 0x1d14, 0x1d16, 0x1d18, 0x1d1a, 0x1d1c,
+    0x1d1e, 0x1d20, 0x1d22, 0x1d24, 0x1d26, 0x1d28, 0x1d2a, 0x1d2c,
+    0x1d2e, 0x1d32, 0x03f4, 0x1d34, 0x2207, 0x1d36, 0x2202, 0x1d38,
+    0x1d40, 0x03f4, 0x1d42, 0x2207, 0x1d44, 0x2202, 0x1d46, 0x1d4e,
+    0x03f4, 0x1d50, 0x2207, 0x1d52, 0x2202, 0x1d54, 0x1d5c, 0x03f4,
+    0x1d5e, 0x2207, 0x1d60, 0x2202, 0x1d62, 0x1d6a, 0x03f4, 0x1d6c,
+    0x2207, 0x1d6e, 0x2202, 0x1d70, 0x1d7a, 0x1d7c, 0x1d7e, 0x1d80,
+    0x1d82, 0x1d84, 0x1d8a, 0x1da7, 0x062d, 0x1daf, 0x1dbb, 0x062c,
+    0x1dcb, 0x1e3b, 0x1e47, 0x1e5a, 0x1e6c, 0x1e7f, 0x1e81, 0x1e85,
+    0x1e8b, 0x1e91, 0x1e93, 0x1e97, 0x1e99, 0x1ea1, 0x1ea4, 0x1ea6,
+    0x1eac, 0x1eae, 0x30b5, 0x1eb4, 0x1f0c, 0x1f22, 0x1f26, 0x1f2b,
+    0x1f78, 0x1f89, 0x208a, 0x209a, 0x20a0, 0x219a, 0x22b8,
+};
+
+static const uint8_t unicode_decomp_data[9158] = {
+    0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81,
+    0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31,
+    0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41,
+    0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41,
+    0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45,
+    0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49,
+    0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e,
+    0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f,
+    0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55,
+    0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59,
+    0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61,
+    0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61,
+    0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65,
+    0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69,
+    0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e,
+    0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f,
+    0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75,
+    0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79,
+    0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41,
+    0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43,
+    0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45,
+    0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47,
+    0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48,
+    0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49,
+    0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a,
+    0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c,
+    0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e,
+    0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e,
+    0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81,
+    0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82,
+    0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c,
+    0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a,
+    0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82,
+    0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c,
+    0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01,
+    0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01,
+    0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a,
+    0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49,
+    0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c,
+    0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00,
+    0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26,
+    0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b,
+    0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01,
+    0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a,
+    0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a,
+    0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81,
+    0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f,
+    0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f,
+    0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f,
+    0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6,
+    0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45,
+    0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84,
+    0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00,
+    0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72,
+    0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77,
+    0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20,
+    0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63,
+    0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95,
+    0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20,
+    0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03,
+    0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99,
+    0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81,
+    0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03,
+    0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07,
+    0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00,
+    0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07,
+    0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00,
+    0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1,
+    0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba,
+    0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98,
+    0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04,
+    0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06,
+    0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80,
+    0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04,
+    0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00,
+    0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88,
+    0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04,
+    0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10,
+    0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86,
+    0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04,
+    0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e,
+    0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88,
+    0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04,
+    0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65,
+    0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00,
+    0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27,
+    0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23,
+    0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00,
+    0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54,
+    0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c,
+    0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00,
+    0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c,
+    0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe,
+    0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc,
+    0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c,
+    0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00,
+    0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c,
+    0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09,
+    0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92,
+    0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09,
+    0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf,
+    0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2,
+    0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08,
+    0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca,
+    0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f,
+    0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2,
+    0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42,
+    0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51,
+    0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b,
+    0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71,
+    0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41,
+    0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80,
+    0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80,
+    0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7,
+    0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7,
+    0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5,
+    0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35,
+    0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35,
+    0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35,
+    0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35,
+    0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35,
+    0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35,
+    0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35,
+    0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35,
+    0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00,
+    0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47,
+    0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52,
+    0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61,
+    0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62,
+    0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b,
+    0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b,
+    0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54,
+    0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74,
+    0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76,
+    0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4,
+    0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72,
+    0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3,
+    0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52,
+    0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c,
+    0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65,
+    0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b,
+    0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f,
+    0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73,
+    0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82,
+    0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a,
+    0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a,
+    0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8,
+    0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42,
+    0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81,
+    0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00,
+    0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12,
+    0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad,
+    0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00,
+    0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48,
+    0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7,
+    0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00,
+    0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b,
+    0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84,
+    0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87,
+    0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1,
+    0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88,
+    0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00,
+    0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52,
+    0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1,
+    0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01,
+    0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54,
+    0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1,
+    0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00,
+    0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a,
+    0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80,
+    0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3,
+    0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82,
+    0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88,
+    0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02,
+    0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00,
+    0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2,
+    0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82,
+    0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01,
+    0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45,
+    0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83,
+    0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00,
+    0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49,
+    0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3,
+    0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00,
+    0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc,
+    0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80,
+    0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01,
+    0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf,
+    0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89,
+    0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00,
+    0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59,
+    0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f,
+    0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91,
+    0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f,
+    0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03,
+    0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03,
+    0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81,
+    0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f,
+    0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21,
+    0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2,
+    0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f,
+    0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29,
+    0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2,
+    0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f,
+    0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31,
+    0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2,
+    0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f,
+    0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39,
+    0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2,
+    0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f,
+    0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03,
+    0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03,
+    0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81,
+    0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00,
+    0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59,
+    0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2,
+    0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f,
+    0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61,
+    0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2,
+    0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f,
+    0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69,
+    0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2,
+    0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03,
+    0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5,
+    0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45,
+    0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45,
+    0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70,
+    0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5,
+    0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f,
+    0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91,
+    0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20,
+    0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f,
+    0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00,
+    0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5,
+    0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03,
+    0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf,
+    0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84,
+    0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca,
+    0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe,
+    0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2,
+    0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03,
+    0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5,
+    0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5,
+    0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85,
+    0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03,
+    0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9,
+    0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80,
+    0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94,
+    0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e,
+    0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20,
+    0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35,
+    0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00,
+    0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f,
+    0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69,
+    0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b,
+    0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29,
+    0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f,
+    0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c,
+    0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61,
+    0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43,
+    0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00,
+    0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20,
+    0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50,
+    0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45,
+    0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42,
+    0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f,
+    0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3,
+    0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44,
+    0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31,
+    0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0,
+    0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32,
+    0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35,
+    0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0,
+    0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37,
+    0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49,
+    0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49,
+    0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49,
+    0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69,
+    0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76,
+    0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69,
+    0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c,
+    0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21,
+    0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0,
+    0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8,
+    0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22,
+    0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25,
+    0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22,
+    0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e,
+    0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43,
+    0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00,
+    0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00,
+    0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c,
+    0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8,
+    0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22,
+    0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86,
+    0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8,
+    0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22,
+    0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03,
+    0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00,
+    0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00,
+    0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00,
+    0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00,
+    0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30,
+    0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41,
+    0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00,
+    0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+    0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e,
+    0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0,
+    0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57,
+    0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e,
+    0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6,
+    0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f,
+    0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15,
+    0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80,
+    0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38,
+    0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5,
+    0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a,
+    0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b,
+    0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73,
+    0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b,
+    0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97,
+    0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5,
+    0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20,
+    0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb,
+    0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14,
+    0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36,
+    0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59,
+    0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89,
+    0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f,
+    0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92,
+    0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf,
+    0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3,
+    0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74,
+    0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8,
+    0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd,
+    0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33,
+    0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00,
+    0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f,
+    0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c,
+    0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2,
+    0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55,
+    0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70,
+    0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b,
+    0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49,
+    0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77,
+    0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9,
+    0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62,
+    0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3,
+    0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf,
+    0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8,
+    0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f,
+    0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5,
+    0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00,
+    0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e,
+    0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12,
+    0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45,
+    0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99,
+    0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99,
+    0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99,
+    0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99,
+    0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99,
+    0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99,
+    0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99,
+    0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99,
+    0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99,
+    0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99,
+    0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8,
+    0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac,
+    0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3,
+    0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09,
+    0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01,
+    0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08,
+    0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13,
+    0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22,
+    0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45,
+    0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d,
+    0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56,
+    0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75,
+    0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59,
+    0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00,
+    0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11,
+    0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11,
+    0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11,
+    0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11,
+    0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11,
+    0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11,
+    0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11,
+    0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11,
+    0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11,
+    0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11,
+    0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11,
+    0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11,
+    0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11,
+    0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00,
+    0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e,
+    0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e,
+    0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67,
+    0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91,
+    0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67,
+    0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c,
+    0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54,
+    0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c,
+    0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81,
+    0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65,
+    0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31,
+    0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00,
+    0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c,
+    0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00,
+    0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06,
+    0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c,
+    0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e,
+    0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63,
+    0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e,
+    0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e,
+    0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e,
+    0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c,
+    0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65,
+    0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54,
+    0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52,
+    0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90,
+    0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98,
+    0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e,
+    0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53,
+    0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76,
+    0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59,
+    0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00,
+    0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00,
+    0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72,
+    0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30,
+    0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d,
+    0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d,
+    0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b,
+    0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d,
+    0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48,
+    0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4,
+    0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b,
+    0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39,
+    0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30,
+    0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f,
+    0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11,
+    0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03,
+    0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58,
+    0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e,
+    0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30,
+    0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30,
+    0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad,
+    0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13,
+    0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00,
+    0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40,
+    0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40,
+    0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30,
+    0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b,
+    0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e,
+    0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02,
+    0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11,
+    0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c,
+    0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00,
+    0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f,
+    0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12,
+    0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a,
+    0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16,
+    0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38,
+    0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2,
+    0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30,
+    0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20,
+    0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28,
+    0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30,
+    0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00,
+    0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d,
+    0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22,
+    0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01,
+    0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30,
+    0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23,
+    0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14,
+    0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a,
+    0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30,
+    0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47,
+    0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c,
+    0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30,
+    0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c,
+    0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35,
+    0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2,
+    0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44,
+    0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28,
+    0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00,
+    0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec,
+    0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16,
+    0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30,
+    0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9,
+    0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68,
+    0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61,
+    0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64,
+    0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55,
+    0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c,
+    0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb,
+    0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e,
+    0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc,
+    0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41,
+    0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00,
+    0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c,
+    0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03,
+    0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b,
+    0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a,
+    0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48,
+    0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13,
+    0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13,
+    0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc,
+    0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d,
+    0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f,
+    0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63,
+    0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00,
+    0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00,
+    0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22,
+    0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00,
+    0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d,
+    0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64,
+    0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61,
+    0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2,
+    0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc,
+    0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56,
+    0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00,
+    0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70,
+    0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57,
+    0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00,
+    0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9,
+    0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63,
+    0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43,
+    0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61,
+    0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d,
+    0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f,
+    0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c,
+    0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d,
+    0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72,
+    0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41,
+    0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00,
+    0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00,
+    0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65,
+    0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x26,
+    0x01, 0x53, 0x01, 0x27, 0xa7, 0x37, 0xab, 0x6b,
+    0x02, 0x52, 0xab, 0x48, 0x8c, 0xf4, 0x66, 0xca,
+    0x8e, 0xc8, 0x8c, 0xd1, 0x6e, 0x32, 0x4e, 0xe5,
+    0x53, 0x9c, 0x9f, 0x9c, 0x9f, 0x51, 0x59, 0xd1,
+    0x91, 0x87, 0x55, 0x48, 0x59, 0xf6, 0x61, 0x69,
+    0x76, 0x85, 0x7f, 0x3f, 0x86, 0xba, 0x87, 0xf8,
+    0x88, 0x8f, 0x90, 0x02, 0x6a, 0x1b, 0x6d, 0xd9,
+    0x70, 0xde, 0x73, 0x3d, 0x84, 0x6a, 0x91, 0xf1,
+    0x99, 0x82, 0x4e, 0x75, 0x53, 0x04, 0x6b, 0x1b,
+    0x72, 0x2d, 0x86, 0x1e, 0x9e, 0x50, 0x5d, 0xeb,
+    0x6f, 0xcd, 0x85, 0x64, 0x89, 0xc9, 0x62, 0xd8,
+    0x81, 0x1f, 0x88, 0xca, 0x5e, 0x17, 0x67, 0x6a,
+    0x6d, 0xfc, 0x72, 0xce, 0x90, 0x86, 0x4f, 0xb7,
+    0x51, 0xde, 0x52, 0xc4, 0x64, 0xd3, 0x6a, 0x10,
+    0x72, 0xe7, 0x76, 0x01, 0x80, 0x06, 0x86, 0x5c,
+    0x86, 0xef, 0x8d, 0x32, 0x97, 0x6f, 0x9b, 0xfa,
+    0x9d, 0x8c, 0x78, 0x7f, 0x79, 0xa0, 0x7d, 0xc9,
+    0x83, 0x04, 0x93, 0x7f, 0x9e, 0xd6, 0x8a, 0xdf,
+    0x58, 0x04, 0x5f, 0x60, 0x7c, 0x7e, 0x80, 0x62,
+    0x72, 0xca, 0x78, 0xc2, 0x8c, 0xf7, 0x96, 0xd8,
+    0x58, 0x62, 0x5c, 0x13, 0x6a, 0xda, 0x6d, 0x0f,
+    0x6f, 0x2f, 0x7d, 0x37, 0x7e, 0x4b, 0x96, 0xd2,
+    0x52, 0x8b, 0x80, 0xdc, 0x51, 0xcc, 0x51, 0x1c,
+    0x7a, 0xbe, 0x7d, 0xf1, 0x83, 0x75, 0x96, 0x80,
+    0x8b, 0xcf, 0x62, 0x02, 0x6a, 0xfe, 0x8a, 0x39,
+    0x4e, 0xe7, 0x5b, 0x12, 0x60, 0x87, 0x73, 0x70,
+    0x75, 0x17, 0x53, 0xfb, 0x78, 0xbf, 0x4f, 0xa9,
+    0x5f, 0x0d, 0x4e, 0xcc, 0x6c, 0x78, 0x65, 0x22,
+    0x7d, 0xc3, 0x53, 0x5e, 0x58, 0x01, 0x77, 0x49,
+    0x84, 0xaa, 0x8a, 0xba, 0x6b, 0xb0, 0x8f, 0x88,
+    0x6c, 0xfe, 0x62, 0xe5, 0x82, 0xa0, 0x63, 0x65,
+    0x75, 0xae, 0x4e, 0x69, 0x51, 0xc9, 0x51, 0x81,
+    0x68, 0xe7, 0x7c, 0x6f, 0x82, 0xd2, 0x8a, 0xcf,
+    0x91, 0xf5, 0x52, 0x42, 0x54, 0x73, 0x59, 0xec,
+    0x5e, 0xc5, 0x65, 0xfe, 0x6f, 0x2a, 0x79, 0xad,
+    0x95, 0x6a, 0x9a, 0x97, 0x9e, 0xce, 0x9e, 0x9b,
+    0x52, 0xc6, 0x66, 0x77, 0x6b, 0x62, 0x8f, 0x74,
+    0x5e, 0x90, 0x61, 0x00, 0x62, 0x9a, 0x64, 0x23,
+    0x6f, 0x49, 0x71, 0x89, 0x74, 0xca, 0x79, 0xf4,
+    0x7d, 0x6f, 0x80, 0x26, 0x8f, 0xee, 0x84, 0x23,
+    0x90, 0x4a, 0x93, 0x17, 0x52, 0xa3, 0x52, 0xbd,
+    0x54, 0xc8, 0x70, 0xc2, 0x88, 0xaa, 0x8a, 0xc9,
+    0x5e, 0xf5, 0x5f, 0x7b, 0x63, 0xae, 0x6b, 0x3e,
+    0x7c, 0x75, 0x73, 0xe4, 0x4e, 0xf9, 0x56, 0xe7,
+    0x5b, 0xba, 0x5d, 0x1c, 0x60, 0xb2, 0x73, 0x69,
+    0x74, 0x9a, 0x7f, 0x46, 0x80, 0x34, 0x92, 0xf6,
+    0x96, 0x48, 0x97, 0x18, 0x98, 0x8b, 0x4f, 0xae,
+    0x79, 0xb4, 0x91, 0xb8, 0x96, 0xe1, 0x60, 0x86,
+    0x4e, 0xda, 0x50, 0xee, 0x5b, 0x3f, 0x5c, 0x99,
+    0x65, 0x02, 0x6a, 0xce, 0x71, 0x42, 0x76, 0xfc,
+    0x84, 0x7c, 0x90, 0x8d, 0x9f, 0x88, 0x66, 0x2e,
+    0x96, 0x89, 0x52, 0x7b, 0x67, 0xf3, 0x67, 0x41,
+    0x6d, 0x9c, 0x6e, 0x09, 0x74, 0x59, 0x75, 0x6b,
+    0x78, 0x10, 0x7d, 0x5e, 0x98, 0x6d, 0x51, 0x2e,
+    0x62, 0x78, 0x96, 0x2b, 0x50, 0x19, 0x5d, 0xea,
+    0x6d, 0x2a, 0x8f, 0x8b, 0x5f, 0x44, 0x61, 0x17,
+    0x68, 0x87, 0x73, 0x86, 0x96, 0x29, 0x52, 0x0f,
+    0x54, 0x65, 0x5c, 0x13, 0x66, 0x4e, 0x67, 0xa8,
+    0x68, 0xe5, 0x6c, 0x06, 0x74, 0xe2, 0x75, 0x79,
+    0x7f, 0xcf, 0x88, 0xe1, 0x88, 0xcc, 0x91, 0xe2,
+    0x96, 0x3f, 0x53, 0xba, 0x6e, 0x1d, 0x54, 0xd0,
+    0x71, 0x98, 0x74, 0xfa, 0x85, 0xa3, 0x96, 0x57,
+    0x9c, 0x9f, 0x9e, 0x97, 0x67, 0xcb, 0x6d, 0xe8,
+    0x81, 0xcb, 0x7a, 0x20, 0x7b, 0x92, 0x7c, 0xc0,
+    0x72, 0x99, 0x70, 0x58, 0x8b, 0xc0, 0x4e, 0x36,
+    0x83, 0x3a, 0x52, 0x07, 0x52, 0xa6, 0x5e, 0xd3,
+    0x62, 0xd6, 0x7c, 0x85, 0x5b, 0x1e, 0x6d, 0xb4,
+    0x66, 0x3b, 0x8f, 0x4c, 0x88, 0x4d, 0x96, 0x8b,
+    0x89, 0xd3, 0x5e, 0x40, 0x51, 0xc0, 0x55, 0x00,
+    0x00, 0x00, 0x00, 0x5a, 0x58, 0x00, 0x00, 0x74,
+    0x66, 0x00, 0x00, 0x00, 0x00, 0xde, 0x51, 0x2a,
+    0x73, 0xca, 0x76, 0x3c, 0x79, 0x5e, 0x79, 0x65,
+    0x79, 0x8f, 0x79, 0x56, 0x97, 0xbe, 0x7c, 0xbd,
+    0x7f, 0x00, 0x00, 0x12, 0x86, 0x00, 0x00, 0xf8,
+    0x8a, 0x00, 0x00, 0x00, 0x00, 0x38, 0x90, 0xfd,
+    0x90, 0xef, 0x98, 0xfc, 0x98, 0x28, 0x99, 0xb4,
+    0x9d, 0xde, 0x90, 0xb7, 0x96, 0xae, 0x4f, 0xe7,
+    0x50, 0x4d, 0x51, 0xc9, 0x52, 0xe4, 0x52, 0x51,
+    0x53, 0x9d, 0x55, 0x06, 0x56, 0x68, 0x56, 0x40,
+    0x58, 0xa8, 0x58, 0x64, 0x5c, 0x6e, 0x5c, 0x94,
+    0x60, 0x68, 0x61, 0x8e, 0x61, 0xf2, 0x61, 0x4f,
+    0x65, 0xe2, 0x65, 0x91, 0x66, 0x85, 0x68, 0x77,
+    0x6d, 0x1a, 0x6e, 0x22, 0x6f, 0x6e, 0x71, 0x2b,
+    0x72, 0x22, 0x74, 0x91, 0x78, 0x3e, 0x79, 0x49,
+    0x79, 0x48, 0x79, 0x50, 0x79, 0x56, 0x79, 0x5d,
+    0x79, 0x8d, 0x79, 0x8e, 0x79, 0x40, 0x7a, 0x81,
+    0x7a, 0xc0, 0x7b, 0xf4, 0x7d, 0x09, 0x7e, 0x41,
+    0x7e, 0x72, 0x7f, 0x05, 0x80, 0xed, 0x81, 0x79,
+    0x82, 0x79, 0x82, 0x57, 0x84, 0x10, 0x89, 0x96,
+    0x89, 0x01, 0x8b, 0x39, 0x8b, 0xd3, 0x8c, 0x08,
+    0x8d, 0xb6, 0x8f, 0x38, 0x90, 0xe3, 0x96, 0xff,
+    0x97, 0x3b, 0x98, 0x75, 0x60, 0xee, 0x42, 0x18,
+    0x82, 0x02, 0x26, 0x4e, 0xb5, 0x51, 0x68, 0x51,
+    0x80, 0x4f, 0x45, 0x51, 0x80, 0x51, 0xc7, 0x52,
+    0xfa, 0x52, 0x9d, 0x55, 0x55, 0x55, 0x99, 0x55,
+    0xe2, 0x55, 0x5a, 0x58, 0xb3, 0x58, 0x44, 0x59,
+    0x54, 0x59, 0x62, 0x5a, 0x28, 0x5b, 0xd2, 0x5e,
+    0xd9, 0x5e, 0x69, 0x5f, 0xad, 0x5f, 0xd8, 0x60,
+    0x4e, 0x61, 0x08, 0x61, 0x8e, 0x61, 0x60, 0x61,
+    0xf2, 0x61, 0x34, 0x62, 0xc4, 0x63, 0x1c, 0x64,
+    0x52, 0x64, 0x56, 0x65, 0x74, 0x66, 0x17, 0x67,
+    0x1b, 0x67, 0x56, 0x67, 0x79, 0x6b, 0xba, 0x6b,
+    0x41, 0x6d, 0xdb, 0x6e, 0xcb, 0x6e, 0x22, 0x6f,
+    0x1e, 0x70, 0x6e, 0x71, 0xa7, 0x77, 0x35, 0x72,
+    0xaf, 0x72, 0x2a, 0x73, 0x71, 0x74, 0x06, 0x75,
+    0x3b, 0x75, 0x1d, 0x76, 0x1f, 0x76, 0xca, 0x76,
+    0xdb, 0x76, 0xf4, 0x76, 0x4a, 0x77, 0x40, 0x77,
+    0xcc, 0x78, 0xb1, 0x7a, 0xc0, 0x7b, 0x7b, 0x7c,
+    0x5b, 0x7d, 0xf4, 0x7d, 0x3e, 0x7f, 0x05, 0x80,
+    0x52, 0x83, 0xef, 0x83, 0x79, 0x87, 0x41, 0x89,
+    0x86, 0x89, 0x96, 0x89, 0xbf, 0x8a, 0xf8, 0x8a,
+    0xcb, 0x8a, 0x01, 0x8b, 0xfe, 0x8a, 0xed, 0x8a,
+    0x39, 0x8b, 0x8a, 0x8b, 0x08, 0x8d, 0x38, 0x8f,
+    0x72, 0x90, 0x99, 0x91, 0x76, 0x92, 0x7c, 0x96,
+    0xe3, 0x96, 0x56, 0x97, 0xdb, 0x97, 0xff, 0x97,
+    0x0b, 0x98, 0x3b, 0x98, 0x12, 0x9b, 0x9c, 0x9f,
+    0x4a, 0x28, 0x44, 0x28, 0xd5, 0x33, 0x9d, 0x3b,
+    0x18, 0x40, 0x39, 0x40, 0x49, 0x52, 0xd0, 0x5c,
+    0xd3, 0x7e, 0x43, 0x9f, 0x8e, 0x9f, 0x2a, 0xa0,
+    0x02, 0x66, 0x66, 0x66, 0x69, 0x66, 0x6c, 0x66,
+    0x66, 0x69, 0x66, 0x66, 0x6c, 0x7f, 0x01, 0x74,
+    0x73, 0x00, 0x74, 0x65, 0x05, 0x0f, 0x11, 0x0f,
+    0x00, 0x0f, 0x06, 0x19, 0x11, 0x0f, 0x08, 0xd9,
+    0x05, 0xb4, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf2,
+    0x05, 0xb7, 0x05, 0xd0, 0x05, 0x12, 0x00, 0x03,
+    0x04, 0x0b, 0x0c, 0x0d, 0x18, 0x1a, 0xe9, 0x05,
+    0xc1, 0x05, 0xe9, 0x05, 0xc2, 0x05, 0x49, 0xfb,
+    0xc1, 0x05, 0x49, 0xfb, 0xc2, 0x05, 0xd0, 0x05,
+    0xb7, 0x05, 0xd0, 0x05, 0xb8, 0x05, 0xd0, 0x05,
+    0xbc, 0x05, 0xd8, 0x05, 0xbc, 0x05, 0xde, 0x05,
+    0xbc, 0x05, 0xe0, 0x05, 0xbc, 0x05, 0xe3, 0x05,
+    0xbc, 0x05, 0xb9, 0x05, 0x2d, 0x03, 0x2e, 0x03,
+    0x2f, 0x03, 0x30, 0x03, 0x31, 0x03, 0x1c, 0x00,
+    0x18, 0x06, 0x22, 0x06, 0x2b, 0x06, 0xd0, 0x05,
+    0xdc, 0x05, 0x71, 0x06, 0x00, 0x00, 0x0a, 0x0a,
+    0x0a, 0x0a, 0x0d, 0x0d, 0x0d, 0x0d, 0x0f, 0x0f,
+    0x0f, 0x0f, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e,
+    0x0e, 0x0e, 0x08, 0x08, 0x08, 0x08, 0x33, 0x33,
+    0x33, 0x33, 0x35, 0x35, 0x35, 0x35, 0x13, 0x13,
+    0x13, 0x13, 0x12, 0x12, 0x12, 0x12, 0x15, 0x15,
+    0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x1c, 0x1c,
+    0x1b, 0x1b, 0x1d, 0x1d, 0x17, 0x17, 0x27, 0x27,
+    0x20, 0x20, 0x38, 0x38, 0x38, 0x38, 0x3e, 0x3e,
+    0x3e, 0x3e, 0x42, 0x42, 0x42, 0x42, 0x40, 0x40,
+    0x40, 0x40, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a,
+    0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x4d, 0x4d,
+    0x4d, 0x4d, 0x61, 0x61, 0x62, 0x62, 0x49, 0x06,
+    0x64, 0x64, 0x64, 0x64, 0x7e, 0x7e, 0x7d, 0x7d,
+    0x7f, 0x7f, 0x2e, 0x82, 0x82, 0x7c, 0x7c, 0x80,
+    0x80, 0x87, 0x87, 0x87, 0x87, 0x00, 0x00, 0x26,
+    0x06, 0x00, 0x01, 0x00, 0x01, 0x00, 0xaf, 0x00,
+    0xaf, 0x00, 0x22, 0x00, 0x22, 0x00, 0xa1, 0x00,
+    0xa1, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa2, 0x00,
+    0xa2, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00,
+    0x23, 0x00, 0x23, 0x00, 0x23, 0xcc, 0x06, 0x00,
+    0x00, 0x00, 0x00, 0x26, 0x06, 0x00, 0x06, 0x00,
+    0x07, 0x00, 0x1f, 0x00, 0x23, 0x00, 0x24, 0x02,
+    0x06, 0x02, 0x07, 0x02, 0x08, 0x02, 0x1f, 0x02,
+    0x23, 0x02, 0x24, 0x04, 0x06, 0x04, 0x07, 0x04,
+    0x08, 0x04, 0x1f, 0x04, 0x23, 0x04, 0x24, 0x05,
+    0x06, 0x05, 0x1f, 0x05, 0x23, 0x05, 0x24, 0x06,
+    0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, 0x1f, 0x08,
+    0x06, 0x08, 0x07, 0x08, 0x1f, 0x0d, 0x06, 0x0d,
+    0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f,
+    0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, 0x08, 0x10,
+    0x1f, 0x11, 0x07, 0x11, 0x1f, 0x12, 0x1f, 0x13,
+    0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b,
+    0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1b,
+    0x23, 0x1b, 0x24, 0x1c, 0x07, 0x1c, 0x1f, 0x1c,
+    0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x06, 0x1d,
+    0x07, 0x1d, 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1d,
+    0x23, 0x1d, 0x24, 0x1e, 0x06, 0x1e, 0x07, 0x1e,
+    0x08, 0x1e, 0x1f, 0x1e, 0x23, 0x1e, 0x24, 0x1f,
+    0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x1f,
+    0x23, 0x1f, 0x24, 0x20, 0x06, 0x20, 0x07, 0x20,
+    0x08, 0x20, 0x1f, 0x20, 0x23, 0x20, 0x24, 0x21,
+    0x06, 0x21, 0x1f, 0x21, 0x23, 0x21, 0x24, 0x24,
+    0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24,
+    0x23, 0x24, 0x24, 0x0a, 0x4a, 0x0b, 0x4a, 0x23,
+    0x4a, 0x20, 0x00, 0x4c, 0x06, 0x51, 0x06, 0x51,
+    0x06, 0xff, 0x00, 0x1f, 0x26, 0x06, 0x00, 0x0b,
+    0x00, 0x0c, 0x00, 0x1f, 0x00, 0x20, 0x00, 0x23,
+    0x00, 0x24, 0x02, 0x0b, 0x02, 0x0c, 0x02, 0x1f,
+    0x02, 0x20, 0x02, 0x23, 0x02, 0x24, 0x04, 0x0b,
+    0x04, 0x0c, 0x04, 0x1f, 0x26, 0x06, 0x04, 0x20,
+    0x04, 0x23, 0x04, 0x24, 0x05, 0x0b, 0x05, 0x0c,
+    0x05, 0x1f, 0x05, 0x20, 0x05, 0x23, 0x05, 0x24,
+    0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x23, 0x1c, 0x24,
+    0x1d, 0x01, 0x1d, 0x1e, 0x1d, 0x1f, 0x1d, 0x23,
+    0x1d, 0x24, 0x1e, 0x1f, 0x1e, 0x23, 0x1e, 0x24,
+    0x1f, 0x01, 0x1f, 0x1f, 0x20, 0x0b, 0x20, 0x0c,
+    0x20, 0x1f, 0x20, 0x20, 0x20, 0x23, 0x20, 0x24,
+    0x23, 0x4a, 0x24, 0x0b, 0x24, 0x0c, 0x24, 0x1f,
+    0x24, 0x20, 0x24, 0x23, 0x24, 0x24, 0x00, 0x06,
+    0x00, 0x07, 0x00, 0x08, 0x00, 0x1f, 0x00, 0x21,
+    0x02, 0x06, 0x02, 0x07, 0x02, 0x08, 0x02, 0x1f,
+    0x02, 0x21, 0x04, 0x06, 0x04, 0x07, 0x04, 0x08,
+    0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x06, 0x07,
+    0x06, 0x1f, 0x07, 0x06, 0x07, 0x1f, 0x08, 0x06,
+    0x08, 0x1f, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08,
+    0x0d, 0x1f, 0x0f, 0x07, 0x0f, 0x08, 0x0f, 0x1f,
+    0x10, 0x06, 0x10, 0x07, 0x10, 0x08, 0x10, 0x1f,
+    0x11, 0x07, 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f,
+    0x14, 0x06, 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07,
+    0x1b, 0x08, 0x1b, 0x1f, 0x1c, 0x07, 0x1c, 0x1f,
+    0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e,
+    0x1d, 0x1f, 0x1e, 0x06, 0x1e, 0x07, 0x1e, 0x08,
+    0x1e, 0x1f, 0x1e, 0x21, 0x1f, 0x06, 0x1f, 0x07,
+    0x1f, 0x08, 0x1f, 0x1f, 0x20, 0x06, 0x20, 0x07,
+    0x20, 0x08, 0x20, 0x1f, 0x20, 0x21, 0x21, 0x06,
+    0x21, 0x1f, 0x21, 0x4a, 0x24, 0x06, 0x24, 0x07,
+    0x24, 0x08, 0x24, 0x1f, 0x24, 0x21, 0x00, 0x1f,
+    0x00, 0x21, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x1f,
+    0x04, 0x21, 0x05, 0x1f, 0x05, 0x21, 0x0d, 0x1f,
+    0x0d, 0x21, 0x0e, 0x1f, 0x0e, 0x21, 0x1d, 0x1e,
+    0x1d, 0x1f, 0x1e, 0x1f, 0x20, 0x1f, 0x20, 0x21,
+    0x24, 0x1f, 0x24, 0x21, 0x40, 0x06, 0x4e, 0x06,
+    0x51, 0x06, 0x27, 0x06, 0x10, 0x22, 0x10, 0x23,
+    0x12, 0x22, 0x12, 0x23, 0x13, 0x22, 0x13, 0x23,
+    0x0c, 0x22, 0x0c, 0x23, 0x0d, 0x22, 0x0d, 0x23,
+    0x06, 0x22, 0x06, 0x23, 0x05, 0x22, 0x05, 0x23,
+    0x07, 0x22, 0x07, 0x23, 0x0e, 0x22, 0x0e, 0x23,
+    0x0f, 0x22, 0x0f, 0x23, 0x0d, 0x05, 0x0d, 0x06,
+    0x0d, 0x07, 0x0d, 0x1e, 0x0d, 0x0a, 0x0c, 0x0a,
+    0x0e, 0x0a, 0x0f, 0x0a, 0x10, 0x22, 0x10, 0x23,
+    0x12, 0x22, 0x12, 0x23, 0x13, 0x22, 0x13, 0x23,
+    0x0c, 0x22, 0x0c, 0x23, 0x0d, 0x22, 0x0d, 0x23,
+    0x06, 0x22, 0x06, 0x23, 0x05, 0x22, 0x05, 0x23,
+    0x07, 0x22, 0x07, 0x23, 0x0e, 0x22, 0x0e, 0x23,
+    0x0f, 0x22, 0x0f, 0x23, 0x0d, 0x05, 0x0d, 0x06,
+    0x0d, 0x07, 0x0d, 0x1e, 0x0d, 0x0a, 0x0c, 0x0a,
+    0x0e, 0x0a, 0x0f, 0x0a, 0x0d, 0x05, 0x0d, 0x06,
+    0x0d, 0x07, 0x0d, 0x1e, 0x0c, 0x20, 0x0d, 0x20,
+    0x10, 0x1e, 0x0c, 0x05, 0x0c, 0x06, 0x0c, 0x07,
+    0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x10, 0x1e,
+    0x11, 0x1e, 0x00, 0x24, 0x00, 0x24, 0x2a, 0x06,
+    0x00, 0x02, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x03,
+    0x02, 0x00, 0x03, 0x1b, 0x00, 0x04, 0x1b, 0x00,
+    0x1b, 0x02, 0x00, 0x1b, 0x03, 0x00, 0x1b, 0x04,
+    0x02, 0x1b, 0x03, 0x02, 0x1b, 0x03, 0x03, 0x1b,
+    0x20, 0x03, 0x1b, 0x1f, 0x09, 0x03, 0x02, 0x09,
+    0x02, 0x03, 0x09, 0x02, 0x1f, 0x09, 0x1b, 0x03,
+    0x09, 0x1b, 0x03, 0x09, 0x1b, 0x02, 0x09, 0x1b,
+    0x1b, 0x09, 0x1b, 0x1b, 0x0b, 0x03, 0x03, 0x0b,
+    0x03, 0x03, 0x0b, 0x1b, 0x1b, 0x0a, 0x03, 0x1b,
+    0x0a, 0x03, 0x1b, 0x0a, 0x02, 0x20, 0x0a, 0x1b,
+    0x04, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x1b, 0x0a,
+    0x1b, 0x1b, 0x0c, 0x03, 0x1f, 0x0c, 0x04, 0x1b,
+    0x0c, 0x04, 0x1b, 0x0d, 0x1b, 0x03, 0x0d, 0x1b,
+    0x03, 0x0d, 0x1b, 0x1b, 0x0d, 0x1b, 0x20, 0x0f,
+    0x02, 0x1b, 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1b,
+    0x0f, 0x1b, 0x1f, 0x10, 0x1b, 0x1b, 0x10, 0x1b,
+    0x20, 0x10, 0x1b, 0x1f, 0x17, 0x04, 0x1b, 0x17,
+    0x04, 0x1b, 0x18, 0x1b, 0x03, 0x18, 0x1b, 0x1b,
+    0x1a, 0x03, 0x1b, 0x1a, 0x03, 0x20, 0x1a, 0x03,
+    0x1f, 0x1a, 0x02, 0x02, 0x1a, 0x02, 0x02, 0x1a,
+    0x04, 0x1b, 0x1a, 0x04, 0x1b, 0x1a, 0x1b, 0x03,
+    0x1a, 0x1b, 0x03, 0x1b, 0x03, 0x02, 0x1b, 0x03,
+    0x1b, 0x1b, 0x03, 0x20, 0x1b, 0x02, 0x03, 0x1b,
+    0x02, 0x1b, 0x1b, 0x04, 0x02, 0x1b, 0x04, 0x1b,
+    0x28, 0x06, 0x1d, 0x04, 0x06, 0x1f, 0x1d, 0x04,
+    0x1f, 0x1d, 0x1d, 0x1e, 0x05, 0x1d, 0x1e, 0x05,
+    0x21, 0x1e, 0x04, 0x1d, 0x1e, 0x04, 0x1d, 0x1e,
+    0x04, 0x21, 0x1e, 0x1d, 0x22, 0x1e, 0x1d, 0x21,
+    0x22, 0x1d, 0x1d, 0x22, 0x1d, 0x1d, 0x00, 0x06,
+    0x22, 0x02, 0x04, 0x22, 0x02, 0x04, 0x21, 0x02,
+    0x06, 0x22, 0x02, 0x06, 0x21, 0x02, 0x1d, 0x22,
+    0x02, 0x1d, 0x21, 0x04, 0x1d, 0x22, 0x04, 0x05,
+    0x21, 0x04, 0x1d, 0x21, 0x0b, 0x06, 0x21, 0x0d,
+    0x05, 0x22, 0x0c, 0x05, 0x22, 0x0e, 0x05, 0x22,
+    0x1c, 0x04, 0x22, 0x1c, 0x1d, 0x22, 0x22, 0x05,
+    0x22, 0x22, 0x04, 0x22, 0x22, 0x1d, 0x22, 0x1d,
+    0x1d, 0x22, 0x1a, 0x1d, 0x22, 0x1e, 0x05, 0x22,
+    0x1a, 0x1d, 0x05, 0x1c, 0x05, 0x1d, 0x11, 0x1d,
+    0x22, 0x1b, 0x1d, 0x22, 0x1e, 0x04, 0x05, 0x1d,
+    0x06, 0x22, 0x1c, 0x04, 0x1d, 0x1b, 0x1d, 0x1d,
+    0x1c, 0x04, 0x1d, 0x1e, 0x04, 0x05, 0x04, 0x05,
+    0x22, 0x05, 0x04, 0x22, 0x1d, 0x04, 0x22, 0x19,
+    0x1d, 0x22, 0x00, 0x05, 0x22, 0x1b, 0x1d, 0x1d,
+    0x11, 0x04, 0x1d, 0x0d, 0x1d, 0x1d, 0x0b, 0x06,
+    0x22, 0x1e, 0x04, 0x22, 0x35, 0x06, 0x00, 0x0f,
+    0x9d, 0x0d, 0x0f, 0x9d, 0x27, 0x06, 0x00, 0x1d,
+    0x1d, 0x20, 0x00, 0x1c, 0x01, 0x0a, 0x1e, 0x06,
+    0x1e, 0x08, 0x0e, 0x1d, 0x12, 0x1e, 0x0a, 0x0c,
+    0x21, 0x1d, 0x12, 0x1d, 0x23, 0x20, 0x21, 0x0c,
+    0x1d, 0x1e, 0x35, 0x06, 0x00, 0x0f, 0x14, 0x27,
+    0x06, 0x0e, 0x1d, 0x22, 0xff, 0x00, 0x1d, 0x1d,
+    0x20, 0xff, 0x12, 0x1d, 0x23, 0x20, 0xff, 0x21,
+    0x0c, 0x1d, 0x1e, 0x27, 0x06, 0x05, 0x1d, 0xff,
+    0x05, 0x1d, 0x00, 0x1d, 0x20, 0x27, 0x06, 0x0a,
+    0xa5, 0x00, 0x1d, 0x2c, 0x00, 0x01, 0x30, 0x02,
+    0x30, 0x3a, 0x00, 0x3b, 0x00, 0x21, 0x00, 0x3f,
+    0x00, 0x16, 0x30, 0x17, 0x30, 0x26, 0x20, 0x13,
+    0x20, 0x12, 0x01, 0x00, 0x5f, 0x5f, 0x28, 0x29,
+    0x7b, 0x7d, 0x08, 0x30, 0x0c, 0x0d, 0x08, 0x09,
+    0x02, 0x03, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07,
+    0x5b, 0x00, 0x5d, 0x00, 0x3e, 0x20, 0x3e, 0x20,
+    0x3e, 0x20, 0x3e, 0x20, 0x5f, 0x00, 0x5f, 0x00,
+    0x5f, 0x00, 0x2c, 0x00, 0x01, 0x30, 0x2e, 0x00,
+    0x00, 0x00, 0x3b, 0x00, 0x3a, 0x00, 0x3f, 0x00,
+    0x21, 0x00, 0x14, 0x20, 0x28, 0x00, 0x29, 0x00,
+    0x7b, 0x00, 0x7d, 0x00, 0x14, 0x30, 0x15, 0x30,
+    0x23, 0x26, 0x2a, 0x2b, 0x2d, 0x3c, 0x3e, 0x3d,
+    0x00, 0x5c, 0x24, 0x25, 0x40, 0x40, 0x06, 0xff,
+    0x0b, 0x00, 0x0b, 0xff, 0x0c, 0x20, 0x00, 0x4d,
+    0x06, 0x40, 0x06, 0xff, 0x0e, 0x00, 0x0e, 0xff,
+    0x0f, 0x00, 0x0f, 0xff, 0x10, 0x00, 0x10, 0xff,
+    0x11, 0x00, 0x11, 0xff, 0x12, 0x00, 0x12, 0x21,
+    0x06, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03,
+    0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06,
+    0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09,
+    0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b,
+    0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d,
+    0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10,
+    0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13,
+    0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15,
+    0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17,
+    0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19,
+    0x19, 0x19, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21,
+    0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23,
+    0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25,
+    0x25, 0x25, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27,
+    0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x22, 0x06,
+    0x22, 0x00, 0x22, 0x00, 0x22, 0x01, 0x22, 0x01,
+    0x22, 0x03, 0x22, 0x03, 0x22, 0x05, 0x22, 0x05,
+    0x21, 0x00, 0x85, 0x29, 0x01, 0x30, 0x01, 0x0b,
+    0x0c, 0x00, 0xfa, 0xf1, 0xa0, 0xa2, 0xa4, 0xa6,
+    0xa8, 0xe2, 0xe4, 0xe6, 0xc2, 0xfb, 0xa1, 0xa3,
+    0xa5, 0xa7, 0xa9, 0xaa, 0xac, 0xae, 0xb0, 0xb2,
+    0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc3,
+    0xc5, 0xc7, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+    0xd1, 0xd4, 0xd7, 0xda, 0xdd, 0xde, 0xdf, 0xe0,
+    0xe1, 0xe3, 0xe5, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
+    0xec, 0xee, 0xf2, 0x98, 0x99, 0x31, 0x31, 0x4f,
+    0x31, 0x55, 0x31, 0x5b, 0x31, 0x61, 0x31, 0xa2,
+    0x00, 0xa3, 0x00, 0xac, 0x00, 0xaf, 0x00, 0xa6,
+    0x00, 0xa5, 0x00, 0xa9, 0x20, 0x00, 0x00, 0x02,
+    0x25, 0x90, 0x21, 0x91, 0x21, 0x92, 0x21, 0x93,
+    0x21, 0xa0, 0x25, 0xcb, 0x25, 0x99, 0x10, 0xba,
+    0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, 0xba,
+    0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, 0x05,
+    0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, 0x11,
+    0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, 0x57,
+    0x13, 0x55, 0xb9, 0x14, 0xba, 0x14, 0xb9, 0x14,
+    0xb0, 0x14, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x14,
+    0xbd, 0x14, 0x55, 0x50, 0xb8, 0x15, 0xaf, 0x15,
+    0xb9, 0x15, 0xaf, 0x15, 0x55, 0x57, 0xd1, 0x65,
+    0xd1, 0x58, 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e,
+    0xd1, 0x5f, 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70,
+    0xd1, 0x5f, 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72,
+    0xd1, 0x55, 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65,
+    0xd1, 0xba, 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e,
+    0xd1, 0xbc, 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f,
+    0xd1, 0xbc, 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55,
+    0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00,
+    0x69, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00,
+    0x43, 0x44, 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a,
+    0x4b, 0x00, 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00,
+    0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
+    0x61, 0x62, 0x63, 0x64, 0x00, 0x66, 0x68, 0x00,
+    0x70, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x42,
+    0x00, 0x44, 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53,
+    0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, 0x45,
+    0x46, 0x47, 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
+    0x00, 0x4f, 0x53, 0x00, 0x61, 0x00, 0x41, 0x00,
+    0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00,
+    0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00,
+    0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x31, 0x01,
+    0x37, 0x02, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03,
+    0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05,
+    0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03,
+    0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03,
+    0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00,
+    0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03,
+    0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04,
+    0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03,
+    0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05,
+    0x0b, 0x0c, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
+    0x30, 0x00, 0x30, 0x00, 0x27, 0x06, 0x00, 0x01,
+    0x05, 0x08, 0x2a, 0x06, 0x1e, 0x08, 0x03, 0x0d,
+    0x20, 0x19, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17,
+    0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06,
+    0x0c, 0x0e, 0x10, 0x44, 0x90, 0x77, 0x45, 0x28,
+    0x06, 0x2c, 0x06, 0x00, 0x00, 0x47, 0x06, 0x33,
+    0x06, 0x17, 0x10, 0x11, 0x12, 0x13, 0x00, 0x06,
+    0x0e, 0x02, 0x0f, 0x34, 0x06, 0x2a, 0x06, 0x2b,
+    0x06, 0x2e, 0x06, 0x00, 0x00, 0x36, 0x06, 0x00,
+    0x00, 0x3a, 0x06, 0x2d, 0x06, 0x00, 0x00, 0x4a,
+    0x06, 0x00, 0x00, 0x44, 0x06, 0x00, 0x00, 0x46,
+    0x06, 0x33, 0x06, 0x39, 0x06, 0x00, 0x00, 0x35,
+    0x06, 0x42, 0x06, 0x00, 0x00, 0x34, 0x06, 0x00,
+    0x00, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x36,
+    0x06, 0x00, 0x00, 0x3a, 0x06, 0x00, 0x00, 0xba,
+    0x06, 0x00, 0x00, 0x6f, 0x06, 0x00, 0x00, 0x28,
+    0x06, 0x2c, 0x06, 0x00, 0x00, 0x47, 0x06, 0x00,
+    0x00, 0x00, 0x00, 0x2d, 0x06, 0x37, 0x06, 0x4a,
+    0x06, 0x43, 0x06, 0x00, 0x00, 0x45, 0x06, 0x46,
+    0x06, 0x33, 0x06, 0x39, 0x06, 0x41, 0x06, 0x35,
+    0x06, 0x42, 0x06, 0x00, 0x00, 0x34, 0x06, 0x2a,
+    0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, 0x36,
+    0x06, 0x38, 0x06, 0x3a, 0x06, 0x6e, 0x06, 0x00,
+    0x00, 0xa1, 0x06, 0x27, 0x06, 0x00, 0x01, 0x05,
+    0x08, 0x20, 0x21, 0x0b, 0x06, 0x10, 0x23, 0x2a,
+    0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b,
+    0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c,
+    0x0e, 0x10, 0x28, 0x06, 0x2c, 0x06, 0x2f, 0x06,
+    0x00, 0x00, 0x48, 0x06, 0x32, 0x06, 0x2d, 0x06,
+    0x37, 0x06, 0x4a, 0x06, 0x2a, 0x06, 0x1a, 0x1b,
+    0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a,
+    0x00, 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x30,
+    0x2e, 0x30, 0x00, 0x2c, 0x00, 0x28, 0x00, 0x41,
+    0x00, 0x29, 0x00, 0x14, 0x30, 0x53, 0x00, 0x15,
+    0x30, 0x43, 0x52, 0x43, 0x44, 0x57, 0x5a, 0x41,
+    0x00, 0x48, 0x56, 0x4d, 0x56, 0x53, 0x44, 0x53,
+    0x53, 0x50, 0x50, 0x56, 0x57, 0x43, 0x4d, 0x43,
+    0x4d, 0x44, 0x4d, 0x52, 0x44, 0x4a, 0x4b, 0x30,
+    0x30, 0x00, 0x68, 0x68, 0x4b, 0x62, 0x57, 0x5b,
+    0xcc, 0x53, 0xc7, 0x30, 0x8c, 0x4e, 0x1a, 0x59,
+    0xe3, 0x89, 0x29, 0x59, 0xa4, 0x4e, 0x20, 0x66,
+    0x21, 0x71, 0x99, 0x65, 0x4d, 0x52, 0x8c, 0x5f,
+    0x8d, 0x51, 0xb0, 0x65, 0x1d, 0x52, 0x42, 0x7d,
+    0x1f, 0x75, 0xa9, 0x8c, 0xf0, 0x58, 0x39, 0x54,
+    0x14, 0x6f, 0x95, 0x62, 0x55, 0x63, 0x00, 0x4e,
+    0x09, 0x4e, 0x4a, 0x90, 0xe6, 0x5d, 0x2d, 0x4e,
+    0xf3, 0x53, 0x07, 0x63, 0x70, 0x8d, 0x53, 0x62,
+    0x81, 0x79, 0x7a, 0x7a, 0x08, 0x54, 0x80, 0x6e,
+    0x09, 0x67, 0x08, 0x67, 0x33, 0x75, 0x72, 0x52,
+    0xb6, 0x55, 0x4d, 0x91, 0x14, 0x30, 0x15, 0x30,
+    0x2c, 0x67, 0x09, 0x4e, 0x8c, 0x4e, 0x89, 0x5b,
+    0xb9, 0x70, 0x53, 0x62, 0xd7, 0x76, 0xdd, 0x52,
+    0x57, 0x65, 0x97, 0x5f, 0xef, 0x53, 0x38, 0x4e,
+    0x05, 0x00, 0x09, 0x22, 0x01, 0x60, 0x4f, 0xae,
+    0x4f, 0xbb, 0x4f, 0x02, 0x50, 0x7a, 0x50, 0x99,
+    0x50, 0xe7, 0x50, 0xcf, 0x50, 0x9e, 0x34, 0x3a,
+    0x06, 0x4d, 0x51, 0x54, 0x51, 0x64, 0x51, 0x77,
+    0x51, 0x1c, 0x05, 0xb9, 0x34, 0x67, 0x51, 0x8d,
+    0x51, 0x4b, 0x05, 0x97, 0x51, 0xa4, 0x51, 0xcc,
+    0x4e, 0xac, 0x51, 0xb5, 0x51, 0xdf, 0x91, 0xf5,
+    0x51, 0x03, 0x52, 0xdf, 0x34, 0x3b, 0x52, 0x46,
+    0x52, 0x72, 0x52, 0x77, 0x52, 0x15, 0x35, 0x02,
+    0x00, 0x20, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00,
+    0xc7, 0x52, 0x00, 0x02, 0x1d, 0x33, 0x3e, 0x3f,
+    0x50, 0x82, 0x8a, 0x93, 0xac, 0xb6, 0xb8, 0xb8,
+    0xb8, 0x2c, 0x0a, 0x70, 0x70, 0xca, 0x53, 0xdf,
+    0x53, 0x63, 0x0b, 0xeb, 0x53, 0xf1, 0x53, 0x06,
+    0x54, 0x9e, 0x54, 0x38, 0x54, 0x48, 0x54, 0x68,
+    0x54, 0xa2, 0x54, 0xf6, 0x54, 0x10, 0x55, 0x53,
+    0x55, 0x63, 0x55, 0x84, 0x55, 0x84, 0x55, 0x99,
+    0x55, 0xab, 0x55, 0xb3, 0x55, 0xc2, 0x55, 0x16,
+    0x57, 0x06, 0x56, 0x17, 0x57, 0x51, 0x56, 0x74,
+    0x56, 0x07, 0x52, 0xee, 0x58, 0xce, 0x57, 0xf4,
+    0x57, 0x0d, 0x58, 0x8b, 0x57, 0x32, 0x58, 0x31,
+    0x58, 0xac, 0x58, 0xe4, 0x14, 0xf2, 0x58, 0xf7,
+    0x58, 0x06, 0x59, 0x1a, 0x59, 0x22, 0x59, 0x62,
+    0x59, 0xa8, 0x16, 0xea, 0x16, 0xec, 0x59, 0x1b,
+    0x5a, 0x27, 0x5a, 0xd8, 0x59, 0x66, 0x5a, 0xee,
+    0x36, 0xfc, 0x36, 0x08, 0x5b, 0x3e, 0x5b, 0x3e,
+    0x5b, 0xc8, 0x19, 0xc3, 0x5b, 0xd8, 0x5b, 0xe7,
+    0x5b, 0xf3, 0x5b, 0x18, 0x1b, 0xff, 0x5b, 0x06,
+    0x5c, 0x53, 0x5f, 0x22, 0x5c, 0x81, 0x37, 0x60,
+    0x5c, 0x6e, 0x5c, 0xc0, 0x5c, 0x8d, 0x5c, 0xe4,
+    0x1d, 0x43, 0x5d, 0xe6, 0x1d, 0x6e, 0x5d, 0x6b,
+    0x5d, 0x7c, 0x5d, 0xe1, 0x5d, 0xe2, 0x5d, 0x2f,
+    0x38, 0xfd, 0x5d, 0x28, 0x5e, 0x3d, 0x5e, 0x69,
+    0x5e, 0x62, 0x38, 0x83, 0x21, 0x7c, 0x38, 0xb0,
+    0x5e, 0xb3, 0x5e, 0xb6, 0x5e, 0xca, 0x5e, 0x92,
+    0xa3, 0xfe, 0x5e, 0x31, 0x23, 0x31, 0x23, 0x01,
+    0x82, 0x22, 0x5f, 0x22, 0x5f, 0xc7, 0x38, 0xb8,
+    0x32, 0xda, 0x61, 0x62, 0x5f, 0x6b, 0x5f, 0xe3,
+    0x38, 0x9a, 0x5f, 0xcd, 0x5f, 0xd7, 0x5f, 0xf9,
+    0x5f, 0x81, 0x60, 0x3a, 0x39, 0x1c, 0x39, 0x94,
+    0x60, 0xd4, 0x26, 0xc7, 0x60, 0x02, 0x02, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
+    0x0a, 0x00, 0x00, 0x02, 0x08, 0x00, 0x80, 0x08,
+    0x00, 0x00, 0x08, 0x80, 0x28, 0x80, 0x02, 0x00,
+    0x00, 0x02, 0x48, 0x61, 0x00, 0x04, 0x06, 0x04,
+    0x32, 0x46, 0x6a, 0x5c, 0x67, 0x96, 0xaa, 0xae,
+    0xc8, 0xd3, 0x5d, 0x62, 0x00, 0x54, 0x77, 0xf3,
+    0x0c, 0x2b, 0x3d, 0x63, 0xfc, 0x62, 0x68, 0x63,
+    0x83, 0x63, 0xe4, 0x63, 0xf1, 0x2b, 0x22, 0x64,
+    0xc5, 0x63, 0xa9, 0x63, 0x2e, 0x3a, 0x69, 0x64,
+    0x7e, 0x64, 0x9d, 0x64, 0x77, 0x64, 0x6c, 0x3a,
+    0x4f, 0x65, 0x6c, 0x65, 0x0a, 0x30, 0xe3, 0x65,
+    0xf8, 0x66, 0x49, 0x66, 0x19, 0x3b, 0x91, 0x66,
+    0x08, 0x3b, 0xe4, 0x3a, 0x92, 0x51, 0x95, 0x51,
+    0x00, 0x67, 0x9c, 0x66, 0xad, 0x80, 0xd9, 0x43,
+    0x17, 0x67, 0x1b, 0x67, 0x21, 0x67, 0x5e, 0x67,
+    0x53, 0x67, 0xc3, 0x33, 0x49, 0x3b, 0xfa, 0x67,
+    0x85, 0x67, 0x52, 0x68, 0x85, 0x68, 0x6d, 0x34,
+    0x8e, 0x68, 0x1f, 0x68, 0x14, 0x69, 0x9d, 0x3b,
+    0x42, 0x69, 0xa3, 0x69, 0xea, 0x69, 0xa8, 0x6a,
+    0xa3, 0x36, 0xdb, 0x6a, 0x18, 0x3c, 0x21, 0x6b,
+    0xa7, 0x38, 0x54, 0x6b, 0x4e, 0x3c, 0x72, 0x6b,
+    0x9f, 0x6b, 0xba, 0x6b, 0xbb, 0x6b, 0x8d, 0x3a,
+    0x0b, 0x1d, 0xfa, 0x3a, 0x4e, 0x6c, 0xbc, 0x3c,
+    0xbf, 0x6c, 0xcd, 0x6c, 0x67, 0x6c, 0x16, 0x6d,
+    0x3e, 0x6d, 0x77, 0x6d, 0x41, 0x6d, 0x69, 0x6d,
+    0x78, 0x6d, 0x85, 0x6d, 0x1e, 0x3d, 0x34, 0x6d,
+    0x2f, 0x6e, 0x6e, 0x6e, 0x33, 0x3d, 0xcb, 0x6e,
+    0xc7, 0x6e, 0xd1, 0x3e, 0xf9, 0x6d, 0x6e, 0x6f,
+    0x5e, 0x3f, 0x8e, 0x3f, 0xc6, 0x6f, 0x39, 0x70,
+    0x1e, 0x70, 0x1b, 0x70, 0x96, 0x3d, 0x4a, 0x70,
+    0x7d, 0x70, 0x77, 0x70, 0xad, 0x70, 0x25, 0x05,
+    0x45, 0x71, 0x63, 0x42, 0x9c, 0x71, 0xab, 0x43,
+    0x28, 0x72, 0x35, 0x72, 0x50, 0x72, 0x08, 0x46,
+    0x80, 0x72, 0x95, 0x72, 0x35, 0x47, 0x02, 0x20,
+    0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x08,
+    0x80, 0x00, 0x00, 0x02, 0x02, 0x80, 0x8a, 0x00,
+    0x00, 0x20, 0x00, 0x08, 0x0a, 0x00, 0x80, 0x88,
+    0x80, 0x20, 0x14, 0x48, 0x7a, 0x73, 0x8b, 0x73,
+    0xac, 0x3e, 0xa5, 0x73, 0xb8, 0x3e, 0xb8, 0x3e,
+    0x47, 0x74, 0x5c, 0x74, 0x71, 0x74, 0x85, 0x74,
+    0xca, 0x74, 0x1b, 0x3f, 0x24, 0x75, 0x36, 0x4c,
+    0x3e, 0x75, 0x92, 0x4c, 0x70, 0x75, 0x9f, 0x21,
+    0x10, 0x76, 0xa1, 0x4f, 0xb8, 0x4f, 0x44, 0x50,
+    0xfc, 0x3f, 0x08, 0x40, 0xf4, 0x76, 0xf3, 0x50,
+    0xf2, 0x50, 0x19, 0x51, 0x33, 0x51, 0x1e, 0x77,
+    0x1f, 0x77, 0x1f, 0x77, 0x4a, 0x77, 0x39, 0x40,
+    0x8b, 0x77, 0x46, 0x40, 0x96, 0x40, 0x1d, 0x54,
+    0x4e, 0x78, 0x8c, 0x78, 0xcc, 0x78, 0xe3, 0x40,
+    0x26, 0x56, 0x56, 0x79, 0x9a, 0x56, 0xc5, 0x56,
+    0x8f, 0x79, 0xeb, 0x79, 0x2f, 0x41, 0x40, 0x7a,
+    0x4a, 0x7a, 0x4f, 0x7a, 0x7c, 0x59, 0xa7, 0x5a,
+    0xa7, 0x5a, 0xee, 0x7a, 0x02, 0x42, 0xab, 0x5b,
+    0xc6, 0x7b, 0xc9, 0x7b, 0x27, 0x42, 0x80, 0x5c,
+    0xd2, 0x7c, 0xa0, 0x42, 0xe8, 0x7c, 0xe3, 0x7c,
+    0x00, 0x7d, 0x86, 0x5f, 0x63, 0x7d, 0x01, 0x43,
+    0xc7, 0x7d, 0x02, 0x7e, 0x45, 0x7e, 0x34, 0x43,
+    0x28, 0x62, 0x47, 0x62, 0x59, 0x43, 0xd9, 0x62,
+    0x7a, 0x7f, 0x3e, 0x63, 0x95, 0x7f, 0xfa, 0x7f,
+    0x05, 0x80, 0xda, 0x64, 0x23, 0x65, 0x60, 0x80,
+    0xa8, 0x65, 0x70, 0x80, 0x5f, 0x33, 0xd5, 0x43,
+    0xb2, 0x80, 0x03, 0x81, 0x0b, 0x44, 0x3e, 0x81,
+    0xb5, 0x5a, 0xa7, 0x67, 0xb5, 0x67, 0x93, 0x33,
+    0x9c, 0x33, 0x01, 0x82, 0x04, 0x82, 0x9e, 0x8f,
+    0x6b, 0x44, 0x91, 0x82, 0x8b, 0x82, 0x9d, 0x82,
+    0xb3, 0x52, 0xb1, 0x82, 0xb3, 0x82, 0xbd, 0x82,
+    0xe6, 0x82, 0x3c, 0x6b, 0xe5, 0x82, 0x1d, 0x83,
+    0x63, 0x83, 0xad, 0x83, 0x23, 0x83, 0xbd, 0x83,
+    0xe7, 0x83, 0x57, 0x84, 0x53, 0x83, 0xca, 0x83,
+    0xcc, 0x83, 0xdc, 0x83, 0x36, 0x6c, 0x6b, 0x6d,
+    0x02, 0x00, 0x00, 0x20, 0x22, 0x2a, 0xa0, 0x0a,
+    0x00, 0x20, 0x80, 0x28, 0x00, 0xa8, 0x20, 0x20,
+    0x00, 0x02, 0x80, 0x22, 0x02, 0x8a, 0x08, 0x00,
+    0xaa, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x28,
+    0xd5, 0x6c, 0x2b, 0x45, 0xf1, 0x84, 0xf3, 0x84,
+    0x16, 0x85, 0xca, 0x73, 0x64, 0x85, 0x2c, 0x6f,
+    0x5d, 0x45, 0x61, 0x45, 0xb1, 0x6f, 0xd2, 0x70,
+    0x6b, 0x45, 0x50, 0x86, 0x5c, 0x86, 0x67, 0x86,
+    0x69, 0x86, 0xa9, 0x86, 0x88, 0x86, 0x0e, 0x87,
+    0xe2, 0x86, 0x79, 0x87, 0x28, 0x87, 0x6b, 0x87,
+    0x86, 0x87, 0xd7, 0x45, 0xe1, 0x87, 0x01, 0x88,
+    0xf9, 0x45, 0x60, 0x88, 0x63, 0x88, 0x67, 0x76,
+    0xd7, 0x88, 0xde, 0x88, 0x35, 0x46, 0xfa, 0x88,
+    0xbb, 0x34, 0xae, 0x78, 0x66, 0x79, 0xbe, 0x46,
+    0xc7, 0x46, 0xa0, 0x8a, 0xed, 0x8a, 0x8a, 0x8b,
+    0x55, 0x8c, 0xa8, 0x7c, 0xab, 0x8c, 0xc1, 0x8c,
+    0x1b, 0x8d, 0x77, 0x8d, 0x2f, 0x7f, 0x04, 0x08,
+    0xcb, 0x8d, 0xbc, 0x8d, 0xf0, 0x8d, 0xde, 0x08,
+    0xd4, 0x8e, 0x38, 0x8f, 0xd2, 0x85, 0xed, 0x85,
+    0x94, 0x90, 0xf1, 0x90, 0x11, 0x91, 0x2e, 0x87,
+    0x1b, 0x91, 0x38, 0x92, 0xd7, 0x92, 0xd8, 0x92,
+    0x7c, 0x92, 0xf9, 0x93, 0x15, 0x94, 0xfa, 0x8b,
+    0x8b, 0x95, 0x95, 0x49, 0xb7, 0x95, 0x77, 0x8d,
+    0xe6, 0x49, 0xc3, 0x96, 0xb2, 0x5d, 0x23, 0x97,
+    0x45, 0x91, 0x1a, 0x92, 0x6e, 0x4a, 0x76, 0x4a,
+    0xe0, 0x97, 0x0a, 0x94, 0xb2, 0x4a, 0x96, 0x94,
+    0x0b, 0x98, 0x0b, 0x98, 0x29, 0x98, 0xb6, 0x95,
+    0xe2, 0x98, 0x33, 0x4b, 0x29, 0x99, 0xa7, 0x99,
+    0xc2, 0x99, 0xfe, 0x99, 0xce, 0x4b, 0x30, 0x9b,
+    0x12, 0x9b, 0x40, 0x9c, 0xfd, 0x9c, 0xce, 0x4c,
+    0xed, 0x4c, 0x67, 0x9d, 0xce, 0xa0, 0xf8, 0x4c,
+    0x05, 0xa1, 0x0e, 0xa2, 0x91, 0xa2, 0xbb, 0x9e,
+    0x56, 0x4d, 0xf9, 0x9e, 0xfe, 0x9e, 0x05, 0x9f,
+    0x0f, 0x9f, 0x16, 0x9f, 0x3b, 0x9f, 0x00, 0xa6,
+    0x02, 0x88, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x80,
+    0x00, 0x28, 0x00, 0x08, 0xa0, 0x80, 0xa0, 0x80,
+    0x00, 0x80, 0x80, 0x00, 0x0a, 0x88, 0x80, 0x00,
+    0x80, 0x00, 0x20, 0x2a, 0x00, 0x80,
+};
+
+static const uint16_t unicode_comp_table[944] = {
+    0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0,
+    0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982,
+    0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8,
+    0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292,
+    0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304,
+    0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306,
+    0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e,
+    0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8,
+    0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380,
+    0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac,
+    0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444,
+    0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940,
+    0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce,
+    0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296,
+    0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844,
+    0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e,
+    0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998,
+    0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322,
+    0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac,
+    0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326,
+    0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc,
+    0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce,
+    0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354,
+    0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3,
+    0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981,
+    0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7,
+    0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291,
+    0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303,
+    0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03,
+    0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d,
+    0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5,
+    0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343,
+    0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347,
+    0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5,
+    0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7,
+    0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3,
+    0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543,
+    0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1,
+    0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991,
+    0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d,
+    0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b,
+    0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3,
+    0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997,
+    0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343,
+    0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f,
+    0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf,
+    0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357,
+    0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448,
+    0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a,
+    0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06,
+    0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447,
+    0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289,
+    0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d,
+    0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb,
+    0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f,
+    0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306,
+    0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d,
+    0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8,
+    0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4,
+    0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882,
+    0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b,
+    0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541,
+    0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8,
+    0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8,
+    0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06,
+    0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850,
+    0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0,
+    0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940,
+    0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05,
+    0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0,
+    0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81,
+    0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184,
+    0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182,
+    0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0,
+    0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242,
+    0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0,
+    0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141,
+    0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245,
+    0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080,
+    0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341,
+    0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480,
+    0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800,
+    0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901,
+    0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80,
+    0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008,
+    0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9,
+    0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457,
+    0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0,
+    0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5,
+    0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583,
+    0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf,
+    0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683,
+    0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01,
+    0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc,
+    0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b,
+    0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3,
+    0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df,
+    0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783,
+    0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844,
+    0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e,
+    0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851,
+    0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a,
+    0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180,
+    0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001,
+    0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700,
+    0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982,
+    0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81,
+    0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41,
+    0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01,
+    0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448,
+    0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480,
+    0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541,
+    0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702,
+    0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712,
+    0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0,
+    0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900,
+    0x5901, 0x5902, 0x5903, 0x5940, 0x8e40, 0x8e42, 0x8e80, 0x8ec0,
+    0x8ec1, 0x8f00, 0x8f01, 0x8f41, 0x8f40, 0x8f43, 0x8f80, 0x8f81,
+};
+
+typedef enum {
+    UNICODE_GC_Cn,
+    UNICODE_GC_Lu,
+    UNICODE_GC_Ll,
+    UNICODE_GC_Lt,
+    UNICODE_GC_Lm,
+    UNICODE_GC_Lo,
+    UNICODE_GC_Mn,
+    UNICODE_GC_Mc,
+    UNICODE_GC_Me,
+    UNICODE_GC_Nd,
+    UNICODE_GC_Nl,
+    UNICODE_GC_No,
+    UNICODE_GC_Sm,
+    UNICODE_GC_Sc,
+    UNICODE_GC_Sk,
+    UNICODE_GC_So,
+    UNICODE_GC_Pc,
+    UNICODE_GC_Pd,
+    UNICODE_GC_Ps,
+    UNICODE_GC_Pe,
+    UNICODE_GC_Pi,
+    UNICODE_GC_Pf,
+    UNICODE_GC_Po,
+    UNICODE_GC_Zs,
+    UNICODE_GC_Zl,
+    UNICODE_GC_Zp,
+    UNICODE_GC_Cc,
+    UNICODE_GC_Cf,
+    UNICODE_GC_Cs,
+    UNICODE_GC_Co,
+    UNICODE_GC_LC,
+    UNICODE_GC_L,
+    UNICODE_GC_M,
+    UNICODE_GC_N,
+    UNICODE_GC_S,
+    UNICODE_GC_P,
+    UNICODE_GC_Z,
+    UNICODE_GC_C,
+    UNICODE_GC_COUNT,
+} UnicodeGCEnum;
+
+static const char unicode_gc_name_table[] =
+    "Cn,Unassigned"            "\0"
+    "Lu,Uppercase_Letter"      "\0"
+    "Ll,Lowercase_Letter"      "\0"
+    "Lt,Titlecase_Letter"      "\0"
+    "Lm,Modifier_Letter"       "\0"
+    "Lo,Other_Letter"          "\0"
+    "Mn,Nonspacing_Mark"       "\0"
+    "Mc,Spacing_Mark"          "\0"
+    "Me,Enclosing_Mark"        "\0"
+    "Nd,Decimal_Number,digit"  "\0"
+    "Nl,Letter_Number"         "\0"
+    "No,Other_Number"          "\0"
+    "Sm,Math_Symbol"           "\0"
+    "Sc,Currency_Symbol"       "\0"
+    "Sk,Modifier_Symbol"       "\0"
+    "So,Other_Symbol"          "\0"
+    "Pc,Connector_Punctuation" "\0"
+    "Pd,Dash_Punctuation"      "\0"
+    "Ps,Open_Punctuation"      "\0"
+    "Pe,Close_Punctuation"     "\0"
+    "Pi,Initial_Punctuation"   "\0"
+    "Pf,Final_Punctuation"     "\0"
+    "Po,Other_Punctuation"     "\0"
+    "Zs,Space_Separator"       "\0"
+    "Zl,Line_Separator"        "\0"
+    "Zp,Paragraph_Separator"   "\0"
+    "Cc,Control,cntrl"         "\0"
+    "Cf,Format"                "\0"
+    "Cs,Surrogate"             "\0"
+    "Co,Private_Use"           "\0"
+    "LC,Cased_Letter"          "\0"
+    "L,Letter"                 "\0"
+    "M,Mark,Combining_Mark"    "\0"
+    "N,Number"                 "\0"
+    "S,Symbol"                 "\0"
+    "P,Punctuation,punct"      "\0"
+    "Z,Separator"              "\0"
+    "C,Other"                  "\0"
+;
+
+static const uint8_t unicode_gc_table[3719] = {
+    0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13,
+    0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36,
+    0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e,
+    0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c,
+    0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e,
+    0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f,
+    0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05,
+    0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2,
+    0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff,
+    0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02,
+    0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02,
+    0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f,
+    0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02,
+    0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01,
+    0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02,
+    0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21,
+    0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22,
+    0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05,
+    0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee,
+    0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09,
+    0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04,
+    0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41,
+    0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00,
+    0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42,
+    0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02,
+    0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86,
+    0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58,
+    0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21,
+    0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25,
+    0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06,
+    0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0,
+    0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6,
+    0x03, 0x16, 0x1b, 0x00, 0x36, 0xe5, 0x18, 0x04,
+    0xe5, 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25,
+    0x06, 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f,
+    0xa6, 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02,
+    0x45, 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05,
+    0x06, 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51,
+    0xe6, 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5,
+    0x19, 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20,
+    0x06, 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01,
+    0x04, 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00,
+    0xe5, 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03,
+    0xe0, 0x2d, 0xe5, 0x0d, 0x00, 0xe5, 0x00, 0xe0,
+    0x0d, 0xe6, 0x07, 0x1b, 0xe6, 0x18, 0x07, 0xe5,
+    0x2e, 0x06, 0x07, 0x06, 0x05, 0x47, 0xe6, 0x00,
+    0x67, 0x06, 0x27, 0x05, 0xc6, 0xe5, 0x02, 0x26,
+    0x36, 0xe9, 0x02, 0x16, 0x04, 0xe5, 0x07, 0x06,
+    0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, 0x20, 0xe5,
+    0x0e, 0x00, 0xc5, 0x00, 0x05, 0x40, 0x65, 0x20,
+    0x06, 0x05, 0x47, 0x66, 0x20, 0x27, 0x20, 0x27,
+    0x06, 0x05, 0xe0, 0x00, 0x07, 0x60, 0x25, 0x00,
+    0x45, 0x26, 0x20, 0xe9, 0x02, 0x25, 0x2d, 0xab,
+    0x0f, 0x0d, 0x05, 0x16, 0x06, 0x20, 0x26, 0x07,
+    0x00, 0xa5, 0x60, 0x25, 0x20, 0xe5, 0x0e, 0x00,
+    0xc5, 0x00, 0x25, 0x00, 0x25, 0x00, 0x25, 0x20,
+    0x06, 0x00, 0x47, 0x26, 0x60, 0x26, 0x20, 0x46,
+    0x40, 0x06, 0xc0, 0x65, 0x00, 0x05, 0xc0, 0xe9,
+    0x02, 0x26, 0x45, 0x06, 0x16, 0xe0, 0x02, 0x26,
+    0x07, 0x00, 0xe5, 0x01, 0x00, 0x45, 0x00, 0xe5,
+    0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x85, 0x20,
+    0x06, 0x05, 0x47, 0x86, 0x00, 0x26, 0x07, 0x00,
+    0x27, 0x06, 0x20, 0x05, 0xe0, 0x07, 0x25, 0x26,
+    0x20, 0xe9, 0x02, 0x16, 0x0d, 0xc0, 0x05, 0xa6,
+    0x00, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25,
+    0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00,
+    0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x07, 0x66,
+    0x20, 0x27, 0x20, 0x27, 0x06, 0xe0, 0x00, 0x06,
+    0x07, 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9,
+    0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, 0x06, 0x05,
+    0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, 0x40, 0x25,
+    0x00, 0x05, 0x00, 0x25, 0x40, 0x25, 0x40, 0x45,
+    0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, 0x27, 0x40,
+    0x47, 0x00, 0x47, 0x06, 0x20, 0x05, 0xa0, 0x07,
+    0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, 0x0d, 0x0f,
+    0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, 0x00, 0x45,
+    0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, 0x40, 0x05,
+    0x46, 0x67, 0x00, 0x46, 0x00, 0x66, 0xc0, 0x26,
+    0x00, 0x45, 0x80, 0x25, 0x26, 0x20, 0xe9, 0x02,
+    0xc0, 0x16, 0xcb, 0x0f, 0x05, 0x06, 0x27, 0x16,
+    0xe5, 0x00, 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00,
+    0xe5, 0x02, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07,
+    0x06, 0x87, 0x00, 0x06, 0x27, 0x00, 0x27, 0x26,
+    0xc0, 0x27, 0xc0, 0x05, 0x00, 0x25, 0x26, 0x20,
+    0xe9, 0x02, 0x00, 0x25, 0xe0, 0x05, 0x26, 0x27,
+    0x00, 0xe5, 0x00, 0x00, 0x45, 0x00, 0xe5, 0x21,
+    0x26, 0x05, 0x47, 0x66, 0x00, 0x47, 0x00, 0x47,
+    0x06, 0x05, 0x0f, 0x60, 0x45, 0x07, 0xcb, 0x45,
+    0x26, 0x20, 0xe9, 0x02, 0xeb, 0x01, 0x0f, 0xa5,
+    0x20, 0x27, 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10,
+    0x00, 0xe5, 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40,
+    0x06, 0x60, 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7,
+    0x00, 0xa0, 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0,
+    0x04, 0xe5, 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d,
+    0xa5, 0x04, 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36,
+    0xe0, 0x1d, 0x25, 0x00, 0x05, 0x00, 0x85, 0x00,
+    0xe5, 0x10, 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06,
+    0x25, 0xe6, 0x01, 0x05, 0x20, 0x85, 0x00, 0x04,
+    0x00, 0xa6, 0x20, 0xe9, 0x02, 0x20, 0x65, 0xe0,
+    0x18, 0x05, 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f,
+    0x26, 0xaf, 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06,
+    0x0f, 0x06, 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13,
+    0x27, 0xe5, 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6,
+    0x06, 0x07, 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03,
+    0x00, 0xe6, 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf,
+    0x00, 0x2f, 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5,
+    0x23, 0x27, 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27,
+    0x26, 0x05, 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26,
+    0x65, 0x46, 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66,
+    0xe5, 0x05, 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05,
+    0x07, 0xe9, 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e,
+    0x00, 0x01, 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16,
+    0x04, 0x42, 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20,
+    0xc5, 0x00, 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21,
+    0x00, 0x65, 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20,
+    0xc5, 0x00, 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07,
+    0x00, 0xe5, 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b,
+    0x20, 0x46, 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5,
+    0x08, 0xef, 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2,
+    0x20, 0x11, 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5,
+    0x09, 0x17, 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5,
+    0x43, 0x56, 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x05,
+    0x00, 0x65, 0x46, 0xe0, 0x03, 0xe5, 0x0a, 0x46,
+    0x36, 0xe0, 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04,
+    0xe5, 0x05, 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04,
+    0xe5, 0x2c, 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06,
+    0x27, 0xe6, 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05,
+    0x06, 0x20, 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0,
+    0xb6, 0x11, 0x76, 0x46, 0x1b, 0x00, 0xe9, 0x02,
+    0xa0, 0xe5, 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85,
+    0x26, 0xe5, 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e,
+    0xe0, 0x02, 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26,
+    0x47, 0x60, 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f,
+    0x40, 0x36, 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85,
+    0xe0, 0x03, 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0,
+    0xe9, 0x02, 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f,
+    0x26, 0x27, 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07,
+    0x06, 0x07, 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27,
+    0xe6, 0x00, 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9,
+    0x02, 0xa0, 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6,
+    0x20, 0xe6, 0x06, 0x08, 0xe0, 0x39, 0x66, 0x07,
+    0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87,
+    0x06, 0x27, 0xc5, 0x60, 0xe9, 0x02, 0xd6, 0xef,
+    0x02, 0xe6, 0x01, 0xef, 0x01, 0x40, 0x26, 0x07,
+    0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, 0x07, 0x46,
+    0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, 0x07, 0x26,
+    0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, 0x00, 0x76,
+    0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, 0x27, 0x26,
+    0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, 0xe9, 0x02,
+    0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, 0xc0, 0xe1,
+    0x23, 0x20, 0x41, 0xf6, 0x00, 0xe0, 0x00, 0x46,
+    0x16, 0xe6, 0x05, 0x07, 0xc6, 0x65, 0x06, 0xa5,
+    0x06, 0x25, 0x07, 0x26, 0x05, 0x80, 0xe2, 0x24,
+    0xe4, 0x37, 0xe2, 0x05, 0x04, 0xe2, 0x1a, 0xe4,
+    0x1d, 0xe6, 0x32, 0x00, 0x86, 0xff, 0x80, 0x0e,
+    0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, 0x00,
+    0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, 0x00,
+    0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, 0x20,
+    0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+    0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, 0x20,
+    0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, 0x00,
+    0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, 0x61,
+    0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, 0x61,
+    0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, 0x4e,
+    0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, 0x22,
+    0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, 0xb1,
+    0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, 0x14,
+    0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, 0x01,
+    0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, 0x13,
+    0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, 0x17,
+    0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, 0xab,
+    0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, 0x12,
+    0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x18, 0xe0,
+    0x08, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, 0x04,
+    0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, 0x02,
+    0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, 0x0c,
+    0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f,
+    0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, 0x2f,
+    0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, 0x2f,
+    0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, 0x6a,
+    0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, 0x0c,
+    0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, 0x17,
+    0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, 0xec,
+    0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, 0x13,
+    0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, 0x49,
+    0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, 0xac,
+    0xef, 0x3d, 0xe0, 0x11, 0xef, 0x03, 0xe0, 0x0d,
+    0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, 0x80,
+    0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, 0xec,
+    0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, 0xef,
+    0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, 0x13,
+    0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13,
+    0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, 0x12,
+    0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, 0xec,
+    0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, 0xac,
+    0xef, 0x1f, 0x20, 0xef, 0x18, 0x20, 0xef, 0x60,
+    0xe1, 0x27, 0x00, 0xe2, 0x27, 0x00, 0x5f, 0x21,
+    0x22, 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82,
+    0x24, 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f,
+    0x46, 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e,
+    0x00, 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0,
+    0x04, 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0,
+    0x01, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5,
+    0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5,
+    0x00, 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15,
+    0x56, 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01,
+    0x11, 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14,
+    0x15, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11,
+    0x16, 0x12, 0xf6, 0x05, 0xe0, 0x28, 0xef, 0x12,
+    0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, 0x80, 0x4e,
+    0xe0, 0x12, 0xef, 0x04, 0x60, 0x17, 0x56, 0x0f,
+    0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, 0x13,
+    0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0x12,
+    0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, 0x84,
+    0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, 0xe5,
+    0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, 0xe5,
+    0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, 0x00,
+    0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, 0xe5,
+    0x13, 0x80, 0xef, 0x1c, 0xe0, 0x04, 0xe5, 0x08,
+    0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, 0xeb,
+    0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, 0x02,
+    0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, 0xe5,
+    0x99, 0x2e, 0xe0, 0x02, 0xef, 0x38, 0xe5, 0xc0,
+    0x11, 0x68, 0xe0, 0x08, 0xe5, 0x0d, 0x04, 0xe5,
+    0x83, 0xef, 0x40, 0xef, 0x2f, 0xe0, 0x01, 0xe5,
+    0x20, 0xa4, 0x36, 0xe5, 0x80, 0x84, 0x04, 0x56,
+    0xe5, 0x08, 0xe9, 0x02, 0x25, 0xe0, 0x0c, 0xff,
+    0x26, 0x05, 0x06, 0x48, 0x16, 0xe6, 0x02, 0x16,
+    0x04, 0xff, 0x14, 0x24, 0x26, 0xe5, 0x3e, 0xea,
+    0x02, 0x26, 0xb6, 0xe0, 0x00, 0xee, 0x0f, 0xe4,
+    0x01, 0x2e, 0xff, 0x06, 0x22, 0xff, 0x36, 0x04,
+    0xe2, 0x00, 0x9f, 0xff, 0x02, 0x04, 0x2e, 0x7f,
+    0x05, 0x7f, 0x22, 0xff, 0x0d, 0x61, 0x02, 0x81,
+    0x02, 0xff, 0x02, 0x20, 0x5f, 0x21, 0xe0, 0x28,
+    0x05, 0x24, 0x02, 0xc5, 0x06, 0x45, 0x06, 0x65,
+    0x06, 0xe5, 0x0f, 0x27, 0x26, 0x07, 0x6f, 0x60,
+    0xab, 0x2f, 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76,
+    0xe0, 0x00, 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26,
+    0xe0, 0x00, 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a,
+    0xa5, 0x56, 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02,
+    0xe5, 0x14, 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6,
+    0x03, 0x27, 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40,
+    0x46, 0x07, 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27,
+    0x26, 0x47, 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02,
+    0x60, 0x36, 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9,
+    0x02, 0x85, 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26,
+    0x27, 0x26, 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00,
+    0x06, 0x07, 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5,
+    0x08, 0x04, 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07,
+    0xe5, 0x2a, 0x06, 0x05, 0x46, 0x25, 0x26, 0x85,
+    0x26, 0x05, 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04,
+    0x36, 0xe5, 0x03, 0x07, 0x26, 0x27, 0x36, 0x05,
+    0x24, 0x07, 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5,
+    0x20, 0xa5, 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00,
+    0xe2, 0x23, 0x0e, 0x64, 0xe2, 0x00, 0xe0, 0x00,
+    0xe2, 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06,
+    0x27, 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0,
+    0xe5, 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60,
+    0xe5, 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98,
+    0x78, 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0,
+    0x1e, 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06,
+    0xe5, 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00,
+    0x05, 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64,
+    0xee, 0x08, 0xe0, 0x09, 0xe5, 0x80, 0xe3, 0x13,
+    0x12, 0xe0, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e,
+    0xe0, 0x20, 0xe5, 0x04, 0x0d, 0x0f, 0x20, 0xe6,
+    0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08,
+    0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12,
+    0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50,
+    0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13,
+    0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16,
+    0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20,
+    0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16,
+    0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c,
+    0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10,
+    0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12,
+    0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04,
+    0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20,
+    0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c,
+    0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0,
+    0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5,
+    0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5,
+    0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73,
+    0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01,
+    0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00,
+    0xef, 0x04, 0x60, 0x0f, 0xe0, 0x27, 0xef, 0x25,
+    0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29,
+    0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18,
+    0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00,
+    0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16,
+    0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16,
+    0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5,
+    0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60,
+    0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5,
+    0x2c, 0xe0, 0x03, 0x16, 0xe0, 0x80, 0x08, 0xe5,
+    0x80, 0xaf, 0xe0, 0x01, 0xe5, 0x0e, 0xe0, 0x02,
+    0xe5, 0x00, 0xe0, 0x80, 0x10, 0xa5, 0x20, 0x05,
+    0x00, 0xe5, 0x24, 0x00, 0x25, 0x40, 0x05, 0x20,
+    0xe5, 0x0f, 0x00, 0x16, 0xeb, 0x00, 0xe5, 0x0f,
+    0x2f, 0xcb, 0xe5, 0x17, 0xe0, 0x00, 0xeb, 0x01,
+    0xe0, 0x28, 0xe5, 0x0b, 0x00, 0x25, 0x80, 0x8b,
+    0xe5, 0x0e, 0xab, 0x40, 0x16, 0xe5, 0x12, 0x80,
+    0x16, 0xe0, 0x38, 0xe5, 0x30, 0x60, 0x2b, 0x25,
+    0xeb, 0x08, 0x20, 0xeb, 0x26, 0x05, 0x46, 0x00,
+    0x26, 0x80, 0x66, 0x65, 0x00, 0x45, 0x00, 0xe5,
+    0x15, 0x20, 0x46, 0x60, 0x06, 0xeb, 0x01, 0xc0,
+    0xf6, 0x01, 0xc0, 0xe5, 0x15, 0x2b, 0x16, 0xe5,
+    0x15, 0x4b, 0xe0, 0x18, 0xe5, 0x00, 0x0f, 0xe5,
+    0x14, 0x26, 0x60, 0x8b, 0xd6, 0xe0, 0x01, 0xe5,
+    0x2e, 0x40, 0xd6, 0xe5, 0x0e, 0x20, 0xeb, 0x00,
+    0xe5, 0x0b, 0x80, 0xeb, 0x00, 0xe5, 0x0a, 0xc0,
+    0x76, 0xe0, 0x04, 0xcb, 0xe0, 0x48, 0xe5, 0x41,
+    0xe0, 0x2f, 0xe1, 0x2b, 0xe0, 0x05, 0xe2, 0x2b,
+    0xc0, 0xab, 0xe5, 0x1c, 0x66, 0xe0, 0x00, 0xe9,
+    0x02, 0xe0, 0x80, 0x9e, 0xeb, 0x17, 0xe0, 0x79,
+    0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, 0x00, 0xe5,
+    0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, 0x7e, 0xe5,
+    0x0f, 0xe0, 0x01, 0x07, 0x06, 0x07, 0xe5, 0x2d,
+    0xe6, 0x07, 0xd6, 0x60, 0xeb, 0x0c, 0xe9, 0x02,
+    0xe0, 0x07, 0x46, 0x07, 0xe5, 0x25, 0x47, 0x66,
+    0x27, 0x26, 0x36, 0x1b, 0x76, 0xe0, 0x03, 0x1b,
+    0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, 0xa0, 0x46,
+    0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, 0x00, 0xe9,
+    0x02, 0x76, 0x05, 0x27, 0xe0, 0x01, 0xe5, 0x1b,
+    0x06, 0x36, 0x05, 0xe0, 0x01, 0x26, 0x07, 0xe5,
+    0x28, 0x47, 0xe6, 0x01, 0x27, 0x65, 0x76, 0x66,
+    0x16, 0x20, 0xe9, 0x02, 0x05, 0x16, 0x05, 0x56,
+    0x00, 0xeb, 0x0c, 0xe0, 0x03, 0xe5, 0x0a, 0x00,
+    0xe5, 0x11, 0x47, 0x46, 0x27, 0x06, 0x07, 0x26,
+    0xb6, 0x06, 0xe0, 0x39, 0xc5, 0x00, 0x05, 0x00,
+    0x65, 0x00, 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16,
+    0xa0, 0xe5, 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80,
+    0xe9, 0x02, 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00,
+    0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00,
+    0x25, 0x00, 0x85, 0x00, 0x26, 0x05, 0x27, 0x06,
+    0x67, 0x20, 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0,
+    0x07, 0x80, 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86,
+    0xe0, 0x80, 0x03, 0xe5, 0x2d, 0x47, 0xe6, 0x00,
+    0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02,
+    0x00, 0x16, 0x00, 0x16, 0x06, 0x05, 0xe0, 0x18,
+    0xe5, 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26,
+    0x07, 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9,
+    0x02, 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66,
+    0x20, 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65,
+    0x26, 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00,
+    0x27, 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03,
+    0xe9, 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5,
+    0x23, 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06,
+    0x05, 0xc0, 0xe9, 0x02, 0xe0, 0x2e, 0xe5, 0x13,
+    0x20, 0x46, 0x27, 0x66, 0x07, 0x86, 0x60, 0xe9,
+    0x02, 0x2b, 0x56, 0x0f, 0xe0, 0x80, 0x38, 0xe5,
+    0x24, 0x47, 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0,
+    0x5c, 0xe1, 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb,
+    0x01, 0xe0, 0x04, 0x05, 0xe0, 0x80, 0x18, 0xe5,
+    0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, 0x26,
+    0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, 0x13,
+    0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, 0x05,
+    0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, 0xa6,
+    0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, 0x26,
+    0x56, 0x05, 0x96, 0xe0, 0x15, 0xe5, 0x31, 0xe0,
+    0x80, 0x7f, 0xe5, 0x01, 0x00, 0xe5, 0x1d, 0x07,
+    0xc6, 0x00, 0xa6, 0x07, 0x06, 0x05, 0x96, 0xe0,
+    0x02, 0xe9, 0x02, 0xeb, 0x0b, 0x40, 0x36, 0xe5,
+    0x16, 0x20, 0xe6, 0x0e, 0x00, 0x07, 0xc6, 0x07,
+    0x26, 0x07, 0x26, 0xe0, 0x41, 0xc5, 0x00, 0x25,
+    0x00, 0xe5, 0x1e, 0xa6, 0x40, 0x06, 0x00, 0x26,
+    0x00, 0xc6, 0x05, 0x06, 0xe0, 0x00, 0xe9, 0x02,
+    0xa0, 0xa5, 0x00, 0x25, 0x00, 0xe5, 0x18, 0x87,
+    0x00, 0x26, 0x00, 0x27, 0x06, 0x07, 0x06, 0x05,
+    0xc0, 0xe9, 0x02, 0xe0, 0x80, 0xae, 0xe5, 0x0b,
+    0x26, 0x27, 0x36, 0xe0, 0x80, 0x3f, 0xeb, 0x0d,
+    0xef, 0x00, 0x6d, 0xef, 0x09, 0xe0, 0x05, 0x16,
+    0xe5, 0x83, 0x12, 0xe0, 0x5e, 0xea, 0x67, 0x00,
+    0x96, 0xe0, 0x03, 0xe5, 0x80, 0x3c, 0xe0, 0x8a,
+    0x34, 0xe5, 0x83, 0xa7, 0x00, 0xfb, 0x01, 0xe0,
+    0x8f, 0x3f, 0xe5, 0x81, 0xbf, 0xe0, 0xa1, 0x31,
+    0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, 0xe9,
+    0x02, 0x60, 0x36, 0xe0, 0x58, 0xe5, 0x16, 0x20,
+    0x86, 0x16, 0xe0, 0x02, 0xe5, 0x28, 0xc6, 0x96,
+    0x6f, 0x64, 0x16, 0x0f, 0xe0, 0x02, 0xe9, 0x02,
+    0x00, 0xcb, 0x00, 0xe5, 0x0d, 0x80, 0xe5, 0x0b,
+    0xe0, 0x82, 0x28, 0xe1, 0x18, 0xe2, 0x18, 0xeb,
+    0x0f, 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06,
+    0x05, 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0,
+    0x38, 0x24, 0x16, 0x04, 0xe0, 0x14, 0xe5, 0x97,
+    0x70, 0xe0, 0x00, 0xe5, 0x82, 0x6b, 0xe0, 0xa4,
+    0x85, 0xe5, 0x80, 0x97, 0xe0, 0x29, 0x45, 0xe0,
+    0x09, 0x65, 0xe0, 0x00, 0xe5, 0x81, 0x04, 0xe0,
+    0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, 0x05, 0x40,
+    0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, 0x0f, 0x26,
+    0x16, 0x7b, 0xe0, 0x92, 0xd4, 0xef, 0x80, 0x6e,
+    0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27,
+    0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f,
+    0xc6, 0xef, 0x16, 0x66, 0xef, 0x33, 0xe0, 0x0f,
+    0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x80, 0x12, 0xeb,
+    0x0c, 0xe0, 0x04, 0xef, 0x4f, 0xe0, 0x01, 0xeb,
+    0x11, 0xe0, 0x7f, 0xe1, 0x12, 0xe2, 0x12, 0xe1,
+    0x12, 0xc2, 0x00, 0xe2, 0x0a, 0xe1, 0x12, 0xe2,
+    0x12, 0x01, 0x00, 0x21, 0x20, 0x01, 0x20, 0x21,
+    0x20, 0x61, 0x00, 0xe1, 0x00, 0x62, 0x00, 0x02,
+    0x00, 0xc2, 0x00, 0xe2, 0x03, 0xe1, 0x12, 0xe2,
+    0x12, 0x21, 0x00, 0x61, 0x20, 0xe1, 0x00, 0x00,
+    0xc1, 0x00, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x00,
+    0x81, 0x00, 0x01, 0x40, 0xc1, 0x00, 0xe2, 0x12,
+    0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12,
+    0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12,
+    0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x14,
+    0x20, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2,
+    0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1,
+    0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11,
+    0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c,
+    0xe2, 0x11, 0x0c, 0xa2, 0x3f, 0x20, 0xe9, 0x2a,
+    0xef, 0x81, 0x78, 0xe6, 0x2f, 0x6f, 0xe6, 0x2a,
+    0xef, 0x00, 0x06, 0xef, 0x06, 0x06, 0x2f, 0x96,
+    0xe0, 0x07, 0x86, 0x00, 0xe6, 0x07, 0xe0, 0x84,
+    0xc8, 0xc6, 0x00, 0xe6, 0x09, 0x20, 0xc6, 0x00,
+    0x26, 0x00, 0x86, 0xe0, 0x80, 0x4d, 0xe5, 0x25,
+    0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, 0x60, 0x05,
+    0x0f, 0xe0, 0x80, 0xe8, 0xe5, 0x24, 0x66, 0xe9,
+    0x02, 0x80, 0x0d, 0xe0, 0x84, 0x78, 0xe5, 0x80,
+    0x3d, 0x20, 0xeb, 0x01, 0xc6, 0xe0, 0x21, 0xe1,
+    0x1a, 0xe2, 0x1a, 0xc6, 0x04, 0x60, 0xe9, 0x02,
+    0x60, 0x36, 0xe0, 0x82, 0x89, 0xeb, 0x33, 0x0f,
+    0x4b, 0x0d, 0x6b, 0xe0, 0x44, 0xeb, 0x25, 0x0f,
+    0xeb, 0x07, 0xe0, 0x80, 0x3a, 0x65, 0x00, 0xe5,
+    0x13, 0x00, 0x25, 0x00, 0x05, 0x20, 0x05, 0x00,
+    0xe5, 0x02, 0x00, 0x65, 0x00, 0x05, 0x00, 0x05,
+    0xa0, 0x05, 0x60, 0x05, 0x00, 0x05, 0x00, 0x05,
+    0x00, 0x45, 0x00, 0x25, 0x00, 0x05, 0x20, 0x05,
+    0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05,
+    0x00, 0x25, 0x00, 0x05, 0x20, 0x65, 0x00, 0xc5,
+    0x00, 0x65, 0x00, 0x65, 0x00, 0x05, 0x00, 0xe5,
+    0x02, 0x00, 0xe5, 0x09, 0x80, 0x45, 0x00, 0x85,
+    0x00, 0xe5, 0x09, 0xe0, 0x2c, 0x2c, 0xe0, 0x80,
+    0x86, 0xef, 0x24, 0x60, 0xef, 0x5c, 0xe0, 0x04,
+    0xef, 0x07, 0x20, 0xef, 0x07, 0x00, 0xef, 0x07,
+    0x00, 0xef, 0x1d, 0xe0, 0x02, 0xeb, 0x05, 0x40,
+    0xef, 0x55, 0x40, 0xef, 0x35, 0xe0, 0x31, 0xef,
+    0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, 0x01,
+    0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, 0x12,
+    0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x4e, 0xe0,
+    0x02, 0xef, 0x05, 0x40, 0xef, 0x03, 0x80, 0xef,
+    0x6c, 0xe0, 0x04, 0xef, 0x51, 0xc0, 0xef, 0x04,
+    0xe0, 0x0c, 0xef, 0x04, 0x60, 0xef, 0x30, 0xe0,
+    0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, 0x00,
+    0xef, 0x16, 0xe0, 0x4a, 0xef, 0x04, 0x00, 0xef,
+    0x5d, 0x00, 0x6f, 0x40, 0xef, 0x21, 0x20, 0xaf,
+    0x40, 0xef, 0x15, 0x20, 0xef, 0x7f, 0xe0, 0x04,
+    0xef, 0x06, 0x20, 0x6f, 0x60, 0x4f, 0x80, 0x4f,
+    0xe0, 0x05, 0xaf, 0xe0, 0x84, 0xe2, 0xe5, 0xc0,
+    0x66, 0x4f, 0xe0, 0x21, 0xe5, 0x8f, 0xad, 0xe0,
+    0x03, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa,
+    0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x8b, 0x97,
+    0xe5, 0x81, 0x96, 0xe0, 0xca, 0xc5, 0x5b, 0x1b,
+    0xe0, 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80,
+    0x68, 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf,
+    0x76, 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20,
+};
+
+typedef enum {
+    UNICODE_SCRIPT_Unknown,
+    UNICODE_SCRIPT_Adlam,
+    UNICODE_SCRIPT_Ahom,
+    UNICODE_SCRIPT_Anatolian_Hieroglyphs,
+    UNICODE_SCRIPT_Arabic,
+    UNICODE_SCRIPT_Armenian,
+    UNICODE_SCRIPT_Avestan,
+    UNICODE_SCRIPT_Balinese,
+    UNICODE_SCRIPT_Bamum,
+    UNICODE_SCRIPT_Bassa_Vah,
+    UNICODE_SCRIPT_Batak,
+    UNICODE_SCRIPT_Bengali,
+    UNICODE_SCRIPT_Bhaiksuki,
+    UNICODE_SCRIPT_Bopomofo,
+    UNICODE_SCRIPT_Brahmi,
+    UNICODE_SCRIPT_Braille,
+    UNICODE_SCRIPT_Buginese,
+    UNICODE_SCRIPT_Buhid,
+    UNICODE_SCRIPT_Canadian_Aboriginal,
+    UNICODE_SCRIPT_Carian,
+    UNICODE_SCRIPT_Caucasian_Albanian,
+    UNICODE_SCRIPT_Chakma,
+    UNICODE_SCRIPT_Cham,
+    UNICODE_SCRIPT_Cherokee,
+    UNICODE_SCRIPT_Common,
+    UNICODE_SCRIPT_Coptic,
+    UNICODE_SCRIPT_Cuneiform,
+    UNICODE_SCRIPT_Cypriot,
+    UNICODE_SCRIPT_Cyrillic,
+    UNICODE_SCRIPT_Deseret,
+    UNICODE_SCRIPT_Devanagari,
+    UNICODE_SCRIPT_Dogra,
+    UNICODE_SCRIPT_Duployan,
+    UNICODE_SCRIPT_Egyptian_Hieroglyphs,
+    UNICODE_SCRIPT_Elbasan,
+    UNICODE_SCRIPT_Elymaic,
+    UNICODE_SCRIPT_Ethiopic,
+    UNICODE_SCRIPT_Georgian,
+    UNICODE_SCRIPT_Glagolitic,
+    UNICODE_SCRIPT_Gothic,
+    UNICODE_SCRIPT_Grantha,
+    UNICODE_SCRIPT_Greek,
+    UNICODE_SCRIPT_Gujarati,
+    UNICODE_SCRIPT_Gunjala_Gondi,
+    UNICODE_SCRIPT_Gurmukhi,
+    UNICODE_SCRIPT_Han,
+    UNICODE_SCRIPT_Hangul,
+    UNICODE_SCRIPT_Hanifi_Rohingya,
+    UNICODE_SCRIPT_Hanunoo,
+    UNICODE_SCRIPT_Hatran,
+    UNICODE_SCRIPT_Hebrew,
+    UNICODE_SCRIPT_Hiragana,
+    UNICODE_SCRIPT_Imperial_Aramaic,
+    UNICODE_SCRIPT_Inherited,
+    UNICODE_SCRIPT_Inscriptional_Pahlavi,
+    UNICODE_SCRIPT_Inscriptional_Parthian,
+    UNICODE_SCRIPT_Javanese,
+    UNICODE_SCRIPT_Kaithi,
+    UNICODE_SCRIPT_Kannada,
+    UNICODE_SCRIPT_Katakana,
+    UNICODE_SCRIPT_Kayah_Li,
+    UNICODE_SCRIPT_Kharoshthi,
+    UNICODE_SCRIPT_Khmer,
+    UNICODE_SCRIPT_Khojki,
+    UNICODE_SCRIPT_Khudawadi,
+    UNICODE_SCRIPT_Lao,
+    UNICODE_SCRIPT_Latin,
+    UNICODE_SCRIPT_Lepcha,
+    UNICODE_SCRIPT_Limbu,
+    UNICODE_SCRIPT_Linear_A,
+    UNICODE_SCRIPT_Linear_B,
+    UNICODE_SCRIPT_Lisu,
+    UNICODE_SCRIPT_Lycian,
+    UNICODE_SCRIPT_Lydian,
+    UNICODE_SCRIPT_Makasar,
+    UNICODE_SCRIPT_Mahajani,
+    UNICODE_SCRIPT_Malayalam,
+    UNICODE_SCRIPT_Mandaic,
+    UNICODE_SCRIPT_Manichaean,
+    UNICODE_SCRIPT_Marchen,
+    UNICODE_SCRIPT_Masaram_Gondi,
+    UNICODE_SCRIPT_Medefaidrin,
+    UNICODE_SCRIPT_Meetei_Mayek,
+    UNICODE_SCRIPT_Mende_Kikakui,
+    UNICODE_SCRIPT_Meroitic_Cursive,
+    UNICODE_SCRIPT_Meroitic_Hieroglyphs,
+    UNICODE_SCRIPT_Miao,
+    UNICODE_SCRIPT_Modi,
+    UNICODE_SCRIPT_Mongolian,
+    UNICODE_SCRIPT_Mro,
+    UNICODE_SCRIPT_Multani,
+    UNICODE_SCRIPT_Myanmar,
+    UNICODE_SCRIPT_Nabataean,
+    UNICODE_SCRIPT_Nandinagari,
+    UNICODE_SCRIPT_New_Tai_Lue,
+    UNICODE_SCRIPT_Newa,
+    UNICODE_SCRIPT_Nko,
+    UNICODE_SCRIPT_Nushu,
+    UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong,
+    UNICODE_SCRIPT_Ogham,
+    UNICODE_SCRIPT_Ol_Chiki,
+    UNICODE_SCRIPT_Old_Hungarian,
+    UNICODE_SCRIPT_Old_Italic,
+    UNICODE_SCRIPT_Old_North_Arabian,
+    UNICODE_SCRIPT_Old_Permic,
+    UNICODE_SCRIPT_Old_Persian,
+    UNICODE_SCRIPT_Old_Sogdian,
+    UNICODE_SCRIPT_Old_South_Arabian,
+    UNICODE_SCRIPT_Old_Turkic,
+    UNICODE_SCRIPT_Oriya,
+    UNICODE_SCRIPT_Osage,
+    UNICODE_SCRIPT_Osmanya,
+    UNICODE_SCRIPT_Pahawh_Hmong,
+    UNICODE_SCRIPT_Palmyrene,
+    UNICODE_SCRIPT_Pau_Cin_Hau,
+    UNICODE_SCRIPT_Phags_Pa,
+    UNICODE_SCRIPT_Phoenician,
+    UNICODE_SCRIPT_Psalter_Pahlavi,
+    UNICODE_SCRIPT_Rejang,
+    UNICODE_SCRIPT_Runic,
+    UNICODE_SCRIPT_Samaritan,
+    UNICODE_SCRIPT_Saurashtra,
+    UNICODE_SCRIPT_Sharada,
+    UNICODE_SCRIPT_Shavian,
+    UNICODE_SCRIPT_Siddham,
+    UNICODE_SCRIPT_SignWriting,
+    UNICODE_SCRIPT_Sinhala,
+    UNICODE_SCRIPT_Sogdian,
+    UNICODE_SCRIPT_Sora_Sompeng,
+    UNICODE_SCRIPT_Soyombo,
+    UNICODE_SCRIPT_Sundanese,
+    UNICODE_SCRIPT_Syloti_Nagri,
+    UNICODE_SCRIPT_Syriac,
+    UNICODE_SCRIPT_Tagalog,
+    UNICODE_SCRIPT_Tagbanwa,
+    UNICODE_SCRIPT_Tai_Le,
+    UNICODE_SCRIPT_Tai_Tham,
+    UNICODE_SCRIPT_Tai_Viet,
+    UNICODE_SCRIPT_Takri,
+    UNICODE_SCRIPT_Tamil,
+    UNICODE_SCRIPT_Tangut,
+    UNICODE_SCRIPT_Telugu,
+    UNICODE_SCRIPT_Thaana,
+    UNICODE_SCRIPT_Thai,
+    UNICODE_SCRIPT_Tibetan,
+    UNICODE_SCRIPT_Tifinagh,
+    UNICODE_SCRIPT_Tirhuta,
+    UNICODE_SCRIPT_Ugaritic,
+    UNICODE_SCRIPT_Vai,
+    UNICODE_SCRIPT_Wancho,
+    UNICODE_SCRIPT_Warang_Citi,
+    UNICODE_SCRIPT_Yi,
+    UNICODE_SCRIPT_Zanabazar_Square,
+    UNICODE_SCRIPT_COUNT,
+} UnicodeScriptEnum;
+
+static const char unicode_script_name_table[] =
+    "Adlam,Adlm"                  "\0"
+    "Ahom,Ahom"                   "\0"
+    "Anatolian_Hieroglyphs,Hluw"  "\0"
+    "Arabic,Arab"                 "\0"
+    "Armenian,Armn"               "\0"
+    "Avestan,Avst"                "\0"
+    "Balinese,Bali"               "\0"
+    "Bamum,Bamu"                  "\0"
+    "Bassa_Vah,Bass"              "\0"
+    "Batak,Batk"                  "\0"
+    "Bengali,Beng"                "\0"
+    "Bhaiksuki,Bhks"              "\0"
+    "Bopomofo,Bopo"               "\0"
+    "Brahmi,Brah"                 "\0"
+    "Braille,Brai"                "\0"
+    "Buginese,Bugi"               "\0"
+    "Buhid,Buhd"                  "\0"
+    "Canadian_Aboriginal,Cans"    "\0"
+    "Carian,Cari"                 "\0"
+    "Caucasian_Albanian,Aghb"     "\0"
+    "Chakma,Cakm"                 "\0"
+    "Cham,Cham"                   "\0"
+    "Cherokee,Cher"               "\0"
+    "Common,Zyyy"                 "\0"
+    "Coptic,Copt,Qaac"            "\0"
+    "Cuneiform,Xsux"              "\0"
+    "Cypriot,Cprt"                "\0"
+    "Cyrillic,Cyrl"               "\0"
+    "Deseret,Dsrt"                "\0"
+    "Devanagari,Deva"             "\0"
+    "Dogra,Dogr"                  "\0"
+    "Duployan,Dupl"               "\0"
+    "Egyptian_Hieroglyphs,Egyp"   "\0"
+    "Elbasan,Elba"                "\0"
+    "Elymaic,Elym"                "\0"
+    "Ethiopic,Ethi"               "\0"
+    "Georgian,Geor"               "\0"
+    "Glagolitic,Glag"             "\0"
+    "Gothic,Goth"                 "\0"
+    "Grantha,Gran"                "\0"
+    "Greek,Grek"                  "\0"
+    "Gujarati,Gujr"               "\0"
+    "Gunjala_Gondi,Gong"          "\0"
+    "Gurmukhi,Guru"               "\0"
+    "Han,Hani"                    "\0"
+    "Hangul,Hang"                 "\0"
+    "Hanifi_Rohingya,Rohg"        "\0"
+    "Hanunoo,Hano"                "\0"
+    "Hatran,Hatr"                 "\0"
+    "Hebrew,Hebr"                 "\0"
+    "Hiragana,Hira"               "\0"
+    "Imperial_Aramaic,Armi"       "\0"
+    "Inherited,Zinh,Qaai"         "\0"
+    "Inscriptional_Pahlavi,Phli"  "\0"
+    "Inscriptional_Parthian,Prti" "\0"
+    "Javanese,Java"               "\0"
+    "Kaithi,Kthi"                 "\0"
+    "Kannada,Knda"                "\0"
+    "Katakana,Kana"               "\0"
+    "Kayah_Li,Kali"               "\0"
+    "Kharoshthi,Khar"             "\0"
+    "Khmer,Khmr"                  "\0"
+    "Khojki,Khoj"                 "\0"
+    "Khudawadi,Sind"              "\0"
+    "Lao,Laoo"                    "\0"
+    "Latin,Latn"                  "\0"
+    "Lepcha,Lepc"                 "\0"
+    "Limbu,Limb"                  "\0"
+    "Linear_A,Lina"               "\0"
+    "Linear_B,Linb"               "\0"
+    "Lisu,Lisu"                   "\0"
+    "Lycian,Lyci"                 "\0"
+    "Lydian,Lydi"                 "\0"
+    "Makasar,Maka"                "\0"
+    "Mahajani,Mahj"               "\0"
+    "Malayalam,Mlym"              "\0"
+    "Mandaic,Mand"                "\0"
+    "Manichaean,Mani"             "\0"
+    "Marchen,Marc"                "\0"
+    "Masaram_Gondi,Gonm"          "\0"
+    "Medefaidrin,Medf"            "\0"
+    "Meetei_Mayek,Mtei"           "\0"
+    "Mende_Kikakui,Mend"          "\0"
+    "Meroitic_Cursive,Merc"       "\0"
+    "Meroitic_Hieroglyphs,Mero"   "\0"
+    "Miao,Plrd"                   "\0"
+    "Modi,Modi"                   "\0"
+    "Mongolian,Mong"              "\0"
+    "Mro,Mroo"                    "\0"
+    "Multani,Mult"                "\0"
+    "Myanmar,Mymr"                "\0"
+    "Nabataean,Nbat"              "\0"
+    "Nandinagari,Nand"            "\0"
+    "New_Tai_Lue,Talu"            "\0"
+    "Newa,Newa"                   "\0"
+    "Nko,Nkoo"                    "\0"
+    "Nushu,Nshu"                  "\0"
+    "Nyiakeng_Puachue_Hmong,Hmnp" "\0"
+    "Ogham,Ogam"                  "\0"
+    "Ol_Chiki,Olck"               "\0"
+    "Old_Hungarian,Hung"          "\0"
+    "Old_Italic,Ital"             "\0"
+    "Old_North_Arabian,Narb"      "\0"
+    "Old_Permic,Perm"             "\0"
+    "Old_Persian,Xpeo"            "\0"
+    "Old_Sogdian,Sogo"            "\0"
+    "Old_South_Arabian,Sarb"      "\0"
+    "Old_Turkic,Orkh"             "\0"
+    "Oriya,Orya"                  "\0"
+    "Osage,Osge"                  "\0"
+    "Osmanya,Osma"                "\0"
+    "Pahawh_Hmong,Hmng"           "\0"
+    "Palmyrene,Palm"              "\0"
+    "Pau_Cin_Hau,Pauc"            "\0"
+    "Phags_Pa,Phag"               "\0"
+    "Phoenician,Phnx"             "\0"
+    "Psalter_Pahlavi,Phlp"        "\0"
+    "Rejang,Rjng"                 "\0"
+    "Runic,Runr"                  "\0"
+    "Samaritan,Samr"              "\0"
+    "Saurashtra,Saur"             "\0"
+    "Sharada,Shrd"                "\0"
+    "Shavian,Shaw"                "\0"
+    "Siddham,Sidd"                "\0"
+    "SignWriting,Sgnw"            "\0"
+    "Sinhala,Sinh"                "\0"
+    "Sogdian,Sogd"                "\0"
+    "Sora_Sompeng,Sora"           "\0"
+    "Soyombo,Soyo"                "\0"
+    "Sundanese,Sund"              "\0"
+    "Syloti_Nagri,Sylo"           "\0"
+    "Syriac,Syrc"                 "\0"
+    "Tagalog,Tglg"                "\0"
+    "Tagbanwa,Tagb"               "\0"
+    "Tai_Le,Tale"                 "\0"
+    "Tai_Tham,Lana"               "\0"
+    "Tai_Viet,Tavt"               "\0"
+    "Takri,Takr"                  "\0"
+    "Tamil,Taml"                  "\0"
+    "Tangut,Tang"                 "\0"
+    "Telugu,Telu"                 "\0"
+    "Thaana,Thaa"                 "\0"
+    "Thai,Thai"                   "\0"
+    "Tibetan,Tibt"                "\0"
+    "Tifinagh,Tfng"               "\0"
+    "Tirhuta,Tirh"                "\0"
+    "Ugaritic,Ugar"               "\0"
+    "Vai,Vaii"                    "\0"
+    "Wancho,Wcho"                 "\0"
+    "Warang_Citi,Wara"            "\0"
+    "Yi,Yiii"                     "\0"
+    "Zanabazar_Square,Zanb"       "\0"
+;
+
+static const uint8_t unicode_script_table[2565] = {
+    0xc0, 0x18, 0x99, 0x42, 0x85, 0x18, 0x99, 0x42,
+    0xae, 0x18, 0x80, 0x42, 0x8e, 0x18, 0x80, 0x42,
+    0x84, 0x18, 0x96, 0x42, 0x80, 0x18, 0x9e, 0x42,
+    0x80, 0x18, 0xe1, 0x60, 0x42, 0xa6, 0x18, 0x84,
+    0x42, 0x84, 0x18, 0x81, 0x0d, 0x93, 0x18, 0xe0,
+    0x0f, 0x35, 0x83, 0x29, 0x80, 0x18, 0x82, 0x29,
+    0x01, 0x83, 0x29, 0x80, 0x18, 0x80, 0x29, 0x03,
+    0x80, 0x29, 0x80, 0x18, 0x80, 0x29, 0x80, 0x18,
+    0x82, 0x29, 0x00, 0x80, 0x29, 0x00, 0x93, 0x29,
+    0x00, 0xbe, 0x29, 0x8d, 0x19, 0x8f, 0x29, 0xe0,
+    0x24, 0x1c, 0x81, 0x35, 0xe0, 0x48, 0x1c, 0x00,
+    0xa5, 0x05, 0x01, 0xaf, 0x05, 0x80, 0x18, 0x80,
+    0x05, 0x01, 0x82, 0x05, 0x00, 0xb6, 0x32, 0x07,
+    0x9a, 0x32, 0x03, 0x85, 0x32, 0x0a, 0x84, 0x04,
+    0x80, 0x18, 0x85, 0x04, 0x80, 0x18, 0x8d, 0x04,
+    0x80, 0x18, 0x80, 0x04, 0x00, 0x80, 0x04, 0x80,
+    0x18, 0x9f, 0x04, 0x80, 0x18, 0x89, 0x04, 0x8a,
+    0x35, 0x99, 0x04, 0x80, 0x35, 0xe0, 0x0b, 0x04,
+    0x80, 0x18, 0xa1, 0x04, 0x8d, 0x84, 0x00, 0xbb,
+    0x84, 0x01, 0x82, 0x84, 0xaf, 0x04, 0xb1, 0x8e,
+    0x0d, 0xba, 0x60, 0x01, 0x82, 0x60, 0xad, 0x78,
+    0x01, 0x8e, 0x78, 0x00, 0x9b, 0x4d, 0x01, 0x80,
+    0x4d, 0x00, 0x8a, 0x84, 0x34, 0x94, 0x04, 0x00,
+    0x87, 0x04, 0x14, 0x8e, 0x04, 0x80, 0x18, 0x9c,
+    0x04, 0xd0, 0x1e, 0x83, 0x35, 0x8e, 0x1e, 0x81,
+    0x18, 0x99, 0x1e, 0x83, 0x0b, 0x00, 0x87, 0x0b,
+    0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, 0x86,
+    0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, 0x01,
+    0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, 0x0b,
+    0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, 0x84,
+    0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x2c, 0x00,
+    0x85, 0x2c, 0x03, 0x81, 0x2c, 0x01, 0x95, 0x2c,
+    0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, 0x00, 0x81,
+    0x2c, 0x00, 0x81, 0x2c, 0x01, 0x80, 0x2c, 0x00,
+    0x84, 0x2c, 0x03, 0x81, 0x2c, 0x01, 0x82, 0x2c,
+    0x02, 0x80, 0x2c, 0x06, 0x83, 0x2c, 0x00, 0x80,
+    0x2c, 0x06, 0x90, 0x2c, 0x09, 0x82, 0x2a, 0x00,
+    0x88, 0x2a, 0x00, 0x82, 0x2a, 0x00, 0x95, 0x2a,
+    0x00, 0x86, 0x2a, 0x00, 0x81, 0x2a, 0x00, 0x84,
+    0x2a, 0x01, 0x89, 0x2a, 0x00, 0x82, 0x2a, 0x00,
+    0x82, 0x2a, 0x01, 0x80, 0x2a, 0x0e, 0x83, 0x2a,
+    0x01, 0x8b, 0x2a, 0x06, 0x86, 0x2a, 0x00, 0x82,
+    0x6d, 0x00, 0x87, 0x6d, 0x01, 0x81, 0x6d, 0x01,
+    0x95, 0x6d, 0x00, 0x86, 0x6d, 0x00, 0x81, 0x6d,
+    0x00, 0x84, 0x6d, 0x01, 0x88, 0x6d, 0x01, 0x81,
+    0x6d, 0x01, 0x82, 0x6d, 0x07, 0x81, 0x6d, 0x03,
+    0x81, 0x6d, 0x00, 0x84, 0x6d, 0x01, 0x91, 0x6d,
+    0x09, 0x81, 0x8b, 0x00, 0x85, 0x8b, 0x02, 0x82,
+    0x8b, 0x00, 0x83, 0x8b, 0x02, 0x81, 0x8b, 0x00,
+    0x80, 0x8b, 0x00, 0x81, 0x8b, 0x02, 0x81, 0x8b,
+    0x02, 0x82, 0x8b, 0x02, 0x8b, 0x8b, 0x03, 0x84,
+    0x8b, 0x02, 0x82, 0x8b, 0x00, 0x83, 0x8b, 0x01,
+    0x80, 0x8b, 0x05, 0x80, 0x8b, 0x0d, 0x94, 0x8b,
+    0x04, 0x8c, 0x8d, 0x00, 0x82, 0x8d, 0x00, 0x96,
+    0x8d, 0x00, 0x8f, 0x8d, 0x02, 0x87, 0x8d, 0x00,
+    0x82, 0x8d, 0x00, 0x83, 0x8d, 0x06, 0x81, 0x8d,
+    0x00, 0x82, 0x8d, 0x04, 0x83, 0x8d, 0x01, 0x89,
+    0x8d, 0x06, 0x88, 0x8d, 0x8c, 0x3a, 0x00, 0x82,
+    0x3a, 0x00, 0x96, 0x3a, 0x00, 0x89, 0x3a, 0x00,
+    0x84, 0x3a, 0x01, 0x88, 0x3a, 0x00, 0x82, 0x3a,
+    0x00, 0x83, 0x3a, 0x06, 0x81, 0x3a, 0x06, 0x80,
+    0x3a, 0x00, 0x83, 0x3a, 0x01, 0x89, 0x3a, 0x00,
+    0x81, 0x3a, 0x0c, 0x83, 0x4c, 0x00, 0x87, 0x4c,
+    0x00, 0x82, 0x4c, 0x00, 0xb2, 0x4c, 0x00, 0x82,
+    0x4c, 0x00, 0x85, 0x4c, 0x03, 0x8f, 0x4c, 0x01,
+    0x99, 0x4c, 0x01, 0x81, 0x7e, 0x00, 0x91, 0x7e,
+    0x02, 0x97, 0x7e, 0x00, 0x88, 0x7e, 0x00, 0x80,
+    0x7e, 0x01, 0x86, 0x7e, 0x02, 0x80, 0x7e, 0x03,
+    0x85, 0x7e, 0x00, 0x80, 0x7e, 0x00, 0x87, 0x7e,
+    0x05, 0x89, 0x7e, 0x01, 0x82, 0x7e, 0x0b, 0xb9,
+    0x8f, 0x03, 0x80, 0x18, 0x9b, 0x8f, 0x24, 0x81,
+    0x41, 0x00, 0x80, 0x41, 0x00, 0x84, 0x41, 0x00,
+    0x97, 0x41, 0x00, 0x80, 0x41, 0x00, 0x96, 0x41,
+    0x01, 0x84, 0x41, 0x00, 0x80, 0x41, 0x00, 0x85,
+    0x41, 0x01, 0x89, 0x41, 0x01, 0x83, 0x41, 0x1f,
+    0xc7, 0x90, 0x00, 0xa3, 0x90, 0x03, 0xa6, 0x90,
+    0x00, 0xa3, 0x90, 0x00, 0x8e, 0x90, 0x00, 0x86,
+    0x90, 0x83, 0x18, 0x81, 0x90, 0x24, 0xe0, 0x3f,
+    0x5b, 0xa5, 0x25, 0x00, 0x80, 0x25, 0x04, 0x80,
+    0x25, 0x01, 0xaa, 0x25, 0x80, 0x18, 0x83, 0x25,
+    0xe0, 0x9f, 0x2e, 0xc8, 0x24, 0x00, 0x83, 0x24,
+    0x01, 0x86, 0x24, 0x00, 0x80, 0x24, 0x00, 0x83,
+    0x24, 0x01, 0xa8, 0x24, 0x00, 0x83, 0x24, 0x01,
+    0xa0, 0x24, 0x00, 0x83, 0x24, 0x01, 0x86, 0x24,
+    0x00, 0x80, 0x24, 0x00, 0x83, 0x24, 0x01, 0x8e,
+    0x24, 0x00, 0xb8, 0x24, 0x00, 0x83, 0x24, 0x01,
+    0xc2, 0x24, 0x01, 0x9f, 0x24, 0x02, 0x99, 0x24,
+    0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, 0xe2,
+    0x1f, 0x12, 0x9c, 0x63, 0x02, 0xca, 0x77, 0x82,
+    0x18, 0x8a, 0x77, 0x06, 0x8c, 0x85, 0x00, 0x86,
+    0x85, 0x0a, 0x94, 0x30, 0x81, 0x18, 0x08, 0x93,
+    0x11, 0x0b, 0x8c, 0x86, 0x00, 0x82, 0x86, 0x00,
+    0x81, 0x86, 0x0b, 0xdd, 0x3e, 0x01, 0x89, 0x3e,
+    0x05, 0x89, 0x3e, 0x05, 0x81, 0x58, 0x81, 0x18,
+    0x80, 0x58, 0x80, 0x18, 0x88, 0x58, 0x00, 0x89,
+    0x58, 0x05, 0xd8, 0x58, 0x06, 0xaa, 0x58, 0x04,
+    0xc5, 0x12, 0x09, 0x9e, 0x44, 0x00, 0x8b, 0x44,
+    0x03, 0x8b, 0x44, 0x03, 0x80, 0x44, 0x02, 0x8b,
+    0x44, 0x9d, 0x87, 0x01, 0x84, 0x87, 0x0a, 0xab,
+    0x5e, 0x03, 0x99, 0x5e, 0x05, 0x8a, 0x5e, 0x02,
+    0x81, 0x5e, 0x9f, 0x3e, 0x9b, 0x10, 0x01, 0x81,
+    0x10, 0xbe, 0x88, 0x00, 0x9c, 0x88, 0x01, 0x8a,
+    0x88, 0x05, 0x89, 0x88, 0x05, 0x8d, 0x88, 0x01,
+    0x8e, 0x35, 0x40, 0xcb, 0x07, 0x03, 0xac, 0x07,
+    0x02, 0xbf, 0x82, 0xb3, 0x0a, 0x07, 0x83, 0x0a,
+    0xb7, 0x43, 0x02, 0x8e, 0x43, 0x02, 0x82, 0x43,
+    0xaf, 0x64, 0x88, 0x1c, 0x06, 0xaa, 0x25, 0x01,
+    0x82, 0x25, 0x87, 0x82, 0x07, 0x82, 0x35, 0x80,
+    0x18, 0x8c, 0x35, 0x80, 0x18, 0x86, 0x35, 0x83,
+    0x18, 0x80, 0x35, 0x85, 0x18, 0x80, 0x35, 0x82,
+    0x18, 0x81, 0x35, 0x80, 0x18, 0x04, 0xa5, 0x42,
+    0x84, 0x29, 0x80, 0x1c, 0xb0, 0x42, 0x84, 0x29,
+    0x83, 0x42, 0x84, 0x29, 0x8c, 0x42, 0x80, 0x1c,
+    0xc5, 0x42, 0x80, 0x29, 0xb9, 0x35, 0x00, 0x84,
+    0x35, 0xe0, 0x9f, 0x42, 0x95, 0x29, 0x01, 0x85,
+    0x29, 0x01, 0xa5, 0x29, 0x01, 0x85, 0x29, 0x01,
+    0x87, 0x29, 0x00, 0x80, 0x29, 0x00, 0x80, 0x29,
+    0x00, 0x80, 0x29, 0x00, 0x9e, 0x29, 0x01, 0xb4,
+    0x29, 0x00, 0x8e, 0x29, 0x00, 0x8d, 0x29, 0x01,
+    0x85, 0x29, 0x00, 0x92, 0x29, 0x01, 0x82, 0x29,
+    0x00, 0x88, 0x29, 0x00, 0x8b, 0x18, 0x81, 0x35,
+    0xd6, 0x18, 0x00, 0x8a, 0x18, 0x80, 0x42, 0x01,
+    0x8a, 0x18, 0x80, 0x42, 0x8e, 0x18, 0x00, 0x8c,
+    0x42, 0x02, 0x9f, 0x18, 0x0f, 0xa0, 0x35, 0x0e,
+    0xa5, 0x18, 0x80, 0x29, 0x82, 0x18, 0x81, 0x42,
+    0x85, 0x18, 0x80, 0x42, 0x9a, 0x18, 0x80, 0x42,
+    0x90, 0x18, 0xa8, 0x42, 0x82, 0x18, 0x03, 0xe2,
+    0x36, 0x18, 0x18, 0x8a, 0x18, 0x14, 0xe3, 0x3f,
+    0x18, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x18, 0x01,
+    0x9f, 0x18, 0x01, 0xe0, 0x07, 0x18, 0xae, 0x26,
+    0x00, 0xae, 0x26, 0x00, 0x9f, 0x42, 0xe0, 0x13,
+    0x19, 0x04, 0x86, 0x19, 0xa5, 0x25, 0x00, 0x80,
+    0x25, 0x04, 0x80, 0x25, 0x01, 0xb7, 0x91, 0x06,
+    0x81, 0x91, 0x0d, 0x80, 0x91, 0x96, 0x24, 0x08,
+    0x86, 0x24, 0x00, 0x86, 0x24, 0x00, 0x86, 0x24,
+    0x00, 0x86, 0x24, 0x00, 0x86, 0x24, 0x00, 0x86,
+    0x24, 0x00, 0x86, 0x24, 0x00, 0x86, 0x24, 0x00,
+    0x9f, 0x1c, 0xcf, 0x18, 0x2f, 0x99, 0x2d, 0x00,
+    0xd8, 0x2d, 0x0b, 0xe0, 0x75, 0x2d, 0x19, 0x8b,
+    0x18, 0x03, 0x84, 0x18, 0x80, 0x2d, 0x80, 0x18,
+    0x80, 0x2d, 0x98, 0x18, 0x88, 0x2d, 0x83, 0x35,
+    0x81, 0x2e, 0x87, 0x18, 0x83, 0x2d, 0x83, 0x18,
+    0x00, 0xd5, 0x33, 0x01, 0x81, 0x35, 0x81, 0x18,
+    0x82, 0x33, 0x80, 0x18, 0xd9, 0x3b, 0x81, 0x18,
+    0x82, 0x3b, 0x04, 0xaa, 0x0d, 0x00, 0xdd, 0x2e,
+    0x00, 0x8f, 0x18, 0x9a, 0x0d, 0x04, 0xa3, 0x18,
+    0x0b, 0x8f, 0x3b, 0x9e, 0x2e, 0x00, 0xbf, 0x18,
+    0x9e, 0x2e, 0xd0, 0x18, 0xae, 0x3b, 0x80, 0x18,
+    0xd7, 0x3b, 0xe0, 0x47, 0x18, 0xf0, 0x09, 0x55,
+    0x2d, 0x09, 0xbf, 0x18, 0xf0, 0x41, 0x8f, 0x2d,
+    0x0f, 0xe4, 0x2c, 0x97, 0x02, 0xb6, 0x97, 0x08,
+    0xaf, 0x47, 0xe0, 0xcb, 0x94, 0x13, 0xdf, 0x1c,
+    0xd7, 0x08, 0x07, 0xa1, 0x18, 0xe0, 0x05, 0x42,
+    0x82, 0x18, 0xb4, 0x42, 0x01, 0x84, 0x42, 0x2f,
+    0x88, 0x42, 0xab, 0x83, 0x03, 0x89, 0x18, 0x05,
+    0xb7, 0x73, 0x07, 0xc5, 0x79, 0x07, 0x8b, 0x79,
+    0x05, 0x9f, 0x1e, 0xad, 0x3c, 0x80, 0x18, 0x80,
+    0x3c, 0xa3, 0x76, 0x0a, 0x80, 0x76, 0x9c, 0x2e,
+    0x02, 0xcd, 0x38, 0x00, 0x80, 0x18, 0x89, 0x38,
+    0x03, 0x81, 0x38, 0x9e, 0x5b, 0x00, 0xb6, 0x16,
+    0x08, 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83,
+    0x16, 0x9f, 0x5b, 0xc2, 0x89, 0x17, 0x84, 0x89,
+    0x96, 0x52, 0x09, 0x85, 0x24, 0x01, 0x85, 0x24,
+    0x01, 0x85, 0x24, 0x08, 0x86, 0x24, 0x00, 0x86,
+    0x24, 0x00, 0xaa, 0x42, 0x80, 0x18, 0x88, 0x42,
+    0x80, 0x29, 0x81, 0x42, 0x07, 0xcf, 0x17, 0xad,
+    0x52, 0x01, 0x89, 0x52, 0x05, 0xf0, 0x1b, 0x43,
+    0x2e, 0x0b, 0x96, 0x2e, 0x03, 0xb0, 0x2e, 0x70,
+    0x10, 0xa3, 0xe1, 0x0d, 0x2d, 0x01, 0xe0, 0x09,
+    0x2d, 0x25, 0x86, 0x42, 0x0b, 0x84, 0x05, 0x04,
+    0x99, 0x32, 0x00, 0x84, 0x32, 0x00, 0x80, 0x32,
+    0x00, 0x81, 0x32, 0x00, 0x81, 0x32, 0x00, 0x89,
+    0x32, 0xe0, 0x11, 0x04, 0x10, 0xe1, 0x0a, 0x04,
+    0x81, 0x18, 0x0f, 0xbf, 0x04, 0x01, 0xb5, 0x04,
+    0x27, 0x8d, 0x04, 0x01, 0x8f, 0x35, 0x89, 0x18,
+    0x05, 0x8d, 0x35, 0x81, 0x1c, 0xa2, 0x18, 0x00,
+    0x92, 0x18, 0x00, 0x83, 0x18, 0x03, 0x84, 0x04,
+    0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, 0x18, 0x00,
+    0x9f, 0x18, 0x99, 0x42, 0x85, 0x18, 0x99, 0x42,
+    0x8a, 0x18, 0x89, 0x3b, 0x80, 0x18, 0xac, 0x3b,
+    0x81, 0x18, 0x9e, 0x2e, 0x02, 0x85, 0x2e, 0x01,
+    0x85, 0x2e, 0x01, 0x85, 0x2e, 0x01, 0x82, 0x2e,
+    0x02, 0x86, 0x18, 0x00, 0x86, 0x18, 0x09, 0x84,
+    0x18, 0x01, 0x8b, 0x46, 0x00, 0x99, 0x46, 0x00,
+    0x92, 0x46, 0x00, 0x81, 0x46, 0x00, 0x8e, 0x46,
+    0x01, 0x8d, 0x46, 0x21, 0xe0, 0x1a, 0x46, 0x04,
+    0x82, 0x18, 0x03, 0xac, 0x18, 0x02, 0x88, 0x18,
+    0xce, 0x29, 0x00, 0x8b, 0x18, 0x03, 0x80, 0x29,
+    0x2e, 0xac, 0x18, 0x80, 0x35, 0x60, 0x21, 0x9c,
+    0x48, 0x02, 0xb0, 0x13, 0x0e, 0x80, 0x35, 0x9a,
+    0x18, 0x03, 0xa3, 0x66, 0x08, 0x82, 0x66, 0x9a,
+    0x27, 0x04, 0xaa, 0x68, 0x04, 0x9d, 0x93, 0x00,
+    0x80, 0x93, 0xa3, 0x69, 0x03, 0x8d, 0x69, 0x29,
+    0xcf, 0x1d, 0xaf, 0x7b, 0x9d, 0x6f, 0x01, 0x89,
+    0x6f, 0x05, 0xa3, 0x6e, 0x03, 0xa3, 0x6e, 0x03,
+    0xa7, 0x22, 0x07, 0xb3, 0x14, 0x0a, 0x80, 0x14,
+    0x60, 0x2f, 0xe0, 0xd6, 0x45, 0x08, 0x95, 0x45,
+    0x09, 0x87, 0x45, 0x60, 0x37, 0x85, 0x1b, 0x01,
+    0x80, 0x1b, 0x00, 0xab, 0x1b, 0x00, 0x81, 0x1b,
+    0x02, 0x80, 0x1b, 0x01, 0x80, 0x1b, 0x95, 0x34,
+    0x00, 0x88, 0x34, 0x9f, 0x71, 0x9e, 0x5c, 0x07,
+    0x88, 0x5c, 0x2f, 0x92, 0x31, 0x00, 0x81, 0x31,
+    0x04, 0x84, 0x31, 0x9b, 0x74, 0x02, 0x80, 0x74,
+    0x99, 0x49, 0x04, 0x80, 0x49, 0x3f, 0x9f, 0x55,
+    0x97, 0x54, 0x03, 0x93, 0x54, 0x01, 0xad, 0x54,
+    0x83, 0x3d, 0x00, 0x81, 0x3d, 0x04, 0x87, 0x3d,
+    0x00, 0x82, 0x3d, 0x00, 0x9c, 0x3d, 0x01, 0x82,
+    0x3d, 0x03, 0x89, 0x3d, 0x06, 0x88, 0x3d, 0x06,
+    0x9f, 0x6b, 0x9f, 0x67, 0x1f, 0xa6, 0x4e, 0x03,
+    0x8b, 0x4e, 0x08, 0xb5, 0x06, 0x02, 0x86, 0x06,
+    0x95, 0x37, 0x01, 0x87, 0x37, 0x92, 0x36, 0x04,
+    0x87, 0x36, 0x91, 0x75, 0x06, 0x83, 0x75, 0x0b,
+    0x86, 0x75, 0x4f, 0xc8, 0x6c, 0x36, 0xb2, 0x65,
+    0x0c, 0xb2, 0x65, 0x06, 0x85, 0x65, 0xa7, 0x2f,
+    0x07, 0x89, 0x2f, 0x60, 0xc5, 0x9e, 0x04, 0x60,
+    0x20, 0xa7, 0x6a, 0x07, 0xa9, 0x7f, 0x60, 0x25,
+    0x96, 0x23, 0x08, 0xcd, 0x0e, 0x03, 0x9d, 0x0e,
+    0x0e, 0x80, 0x0e, 0xc1, 0x39, 0x0a, 0x80, 0x39,
+    0x01, 0x98, 0x80, 0x06, 0x89, 0x80, 0x05, 0xb4,
+    0x15, 0x00, 0x90, 0x15, 0x08, 0xa6, 0x4b, 0x08,
+    0xcd, 0x7a, 0x01, 0x8f, 0x7a, 0x00, 0x93, 0x7e,
+    0x0a, 0x91, 0x3f, 0x00, 0xab, 0x3f, 0x40, 0x86,
+    0x5a, 0x00, 0x80, 0x5a, 0x00, 0x83, 0x5a, 0x00,
+    0x8e, 0x5a, 0x00, 0x8a, 0x5a, 0x05, 0xba, 0x40,
+    0x04, 0x89, 0x40, 0x05, 0x83, 0x28, 0x00, 0x87,
+    0x28, 0x01, 0x81, 0x28, 0x01, 0x95, 0x28, 0x00,
+    0x86, 0x28, 0x00, 0x81, 0x28, 0x00, 0x84, 0x28,
+    0x00, 0x80, 0x35, 0x88, 0x28, 0x01, 0x81, 0x28,
+    0x01, 0x82, 0x28, 0x01, 0x80, 0x28, 0x05, 0x80,
+    0x28, 0x04, 0x86, 0x28, 0x01, 0x86, 0x28, 0x02,
+    0x84, 0x28, 0x60, 0x2a, 0xd9, 0x5f, 0x00, 0x80,
+    0x5f, 0x00, 0x82, 0x5f, 0x1f, 0xc7, 0x92, 0x07,
+    0x89, 0x92, 0x60, 0x45, 0xb5, 0x7c, 0x01, 0xa5,
+    0x7c, 0x21, 0xc4, 0x57, 0x0a, 0x89, 0x57, 0x05,
+    0x8c, 0x58, 0x12, 0xb8, 0x8a, 0x06, 0x89, 0x8a,
+    0x35, 0x9a, 0x02, 0x01, 0x8e, 0x02, 0x03, 0x8f,
+    0x02, 0x60, 0x5f, 0xbb, 0x1f, 0x60, 0x03, 0xd2,
+    0x96, 0x0b, 0x80, 0x96, 0x60, 0x3f, 0x87, 0x5d,
+    0x01, 0xad, 0x5d, 0x01, 0x8a, 0x5d, 0x1a, 0xc7,
+    0x98, 0x07, 0xd2, 0x81, 0x1c, 0xb8, 0x72, 0x60,
+    0xa6, 0x88, 0x0c, 0x00, 0xac, 0x0c, 0x00, 0x8d,
+    0x0c, 0x09, 0x9c, 0x0c, 0x02, 0x9f, 0x4f, 0x01,
+    0x95, 0x4f, 0x00, 0x8d, 0x4f, 0x48, 0x86, 0x50,
+    0x00, 0x81, 0x50, 0x00, 0xab, 0x50, 0x02, 0x80,
+    0x50, 0x00, 0x81, 0x50, 0x00, 0x88, 0x50, 0x07,
+    0x89, 0x50, 0x05, 0x85, 0x2b, 0x00, 0x81, 0x2b,
+    0x00, 0xa4, 0x2b, 0x00, 0x81, 0x2b, 0x00, 0x85,
+    0x2b, 0x06, 0x89, 0x2b, 0x60, 0xd5, 0x98, 0x4a,
+    0x60, 0x66, 0xb1, 0x8b, 0x0c, 0x80, 0x8b, 0xe3,
+    0x39, 0x1a, 0x60, 0x05, 0xe0, 0x0e, 0x1a, 0x00,
+    0x84, 0x1a, 0x0a, 0xe0, 0x63, 0x1a, 0x6a, 0x5b,
+    0xe3, 0xce, 0x21, 0x00, 0x88, 0x21, 0x6f, 0x66,
+    0xe1, 0xe6, 0x03, 0x70, 0x11, 0x58, 0xe1, 0xd8,
+    0x08, 0x06, 0x9e, 0x59, 0x00, 0x89, 0x59, 0x03,
+    0x81, 0x59, 0x5f, 0x9d, 0x09, 0x01, 0x85, 0x09,
+    0x09, 0xc5, 0x70, 0x09, 0x89, 0x70, 0x00, 0x86,
+    0x70, 0x00, 0x94, 0x70, 0x04, 0x92, 0x70, 0x62,
+    0x4f, 0xda, 0x51, 0x60, 0x04, 0xca, 0x56, 0x03,
+    0xb8, 0x56, 0x06, 0x90, 0x56, 0x3f, 0x80, 0x8c,
+    0x80, 0x61, 0x81, 0x18, 0x1b, 0xf0, 0x07, 0x97,
+    0x8c, 0x07, 0xe2, 0x92, 0x8c, 0x70, 0x14, 0xac,
+    0x80, 0x3b, 0xe0, 0xbd, 0x33, 0x30, 0x82, 0x33,
+    0x10, 0x83, 0x3b, 0x07, 0xe1, 0x2b, 0x61, 0x68,
+    0xa3, 0xe0, 0x0a, 0x20, 0x04, 0x8c, 0x20, 0x02,
+    0x88, 0x20, 0x06, 0x89, 0x20, 0x01, 0x83, 0x20,
+    0x83, 0x18, 0x70, 0x02, 0xfb, 0xe0, 0x95, 0x18,
+    0x09, 0xa6, 0x18, 0x01, 0xbd, 0x18, 0x82, 0x35,
+    0x90, 0x18, 0x87, 0x35, 0x81, 0x18, 0x86, 0x35,
+    0x9d, 0x18, 0x83, 0x35, 0xba, 0x18, 0x16, 0xc5,
+    0x29, 0x60, 0x39, 0x93, 0x18, 0x0b, 0xd6, 0x18,
+    0x08, 0x98, 0x18, 0x60, 0x26, 0xd4, 0x18, 0x00,
+    0xc6, 0x18, 0x00, 0x81, 0x18, 0x01, 0x80, 0x18,
+    0x01, 0x81, 0x18, 0x01, 0x83, 0x18, 0x00, 0x8b,
+    0x18, 0x00, 0x80, 0x18, 0x00, 0x86, 0x18, 0x00,
+    0xc0, 0x18, 0x00, 0x83, 0x18, 0x01, 0x87, 0x18,
+    0x00, 0x86, 0x18, 0x00, 0x9b, 0x18, 0x00, 0x83,
+    0x18, 0x00, 0x84, 0x18, 0x00, 0x80, 0x18, 0x02,
+    0x86, 0x18, 0x00, 0xe0, 0xf3, 0x18, 0x01, 0xe0,
+    0xc3, 0x18, 0x01, 0xb1, 0x18, 0xe2, 0x2b, 0x7d,
+    0x0e, 0x84, 0x7d, 0x00, 0x8e, 0x7d, 0x64, 0xef,
+    0x86, 0x26, 0x00, 0x90, 0x26, 0x01, 0x86, 0x26,
+    0x00, 0x81, 0x26, 0x00, 0x84, 0x26, 0x60, 0x74,
+    0xac, 0x62, 0x02, 0x8d, 0x62, 0x01, 0x89, 0x62,
+    0x03, 0x81, 0x62, 0x61, 0x0f, 0xb9, 0x95, 0x04,
+    0x80, 0x95, 0x64, 0x9f, 0xe0, 0x64, 0x53, 0x01,
+    0x8f, 0x53, 0x28, 0xcb, 0x01, 0x03, 0x89, 0x01,
+    0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x18, 0x4b,
+    0xbc, 0x18, 0x60, 0x61, 0x83, 0x04, 0x00, 0x9a,
+    0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, 0x01,
+    0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, 0x04,
+    0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, 0x80,
+    0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00,
+    0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, 0x04,
+    0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, 0x80,
+    0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00,
+    0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04,
+    0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, 0x83,
+    0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, 0x00,
+    0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, 0x04,
+    0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, 0x81,
+    0x04, 0x60, 0xad, 0xab, 0x18, 0x03, 0xe0, 0x03,
+    0x18, 0x0b, 0x8e, 0x18, 0x01, 0x8e, 0x18, 0x00,
+    0x8e, 0x18, 0x00, 0xa4, 0x18, 0x09, 0x8c, 0x18,
+    0x02, 0xdc, 0x18, 0x02, 0xbc, 0x18, 0x38, 0x99,
+    0x18, 0x80, 0x33, 0x81, 0x18, 0x0c, 0xab, 0x18,
+    0x03, 0x88, 0x18, 0x06, 0x81, 0x18, 0x0d, 0x85,
+    0x18, 0x60, 0x39, 0xe3, 0x75, 0x18, 0x09, 0x8c,
+    0x18, 0x02, 0x8a, 0x18, 0x04, 0xe0, 0x13, 0x18,
+    0x0b, 0xd8, 0x18, 0x06, 0x8b, 0x18, 0x13, 0x8b,
+    0x18, 0x03, 0xb7, 0x18, 0x07, 0x89, 0x18, 0x05,
+    0xa7, 0x18, 0x07, 0x9d, 0x18, 0x51, 0x8b, 0x18,
+    0x00, 0xe0, 0x04, 0x18, 0x00, 0x83, 0x18, 0x02,
+    0xa8, 0x18, 0x01, 0x85, 0x18, 0x02, 0x9c, 0x18,
+    0x01, 0xe0, 0x26, 0x18, 0x0b, 0x8d, 0x18, 0x01,
+    0x83, 0x18, 0x03, 0x82, 0x18, 0x04, 0x82, 0x18,
+    0x0c, 0x85, 0x18, 0x65, 0x09, 0xf0, 0x96, 0x76,
+    0x2d, 0x28, 0xef, 0xd4, 0x2d, 0x0a, 0xe0, 0x7d,
+    0x2d, 0x01, 0xf0, 0x06, 0x21, 0x2d, 0x0d, 0xf0,
+    0x0c, 0xd0, 0x2d, 0x6b, 0xbe, 0xe1, 0xbd, 0x2d,
+    0x7a, 0xf5, 0x82, 0x80, 0x18, 0x1d, 0xdf, 0x18,
+    0x60, 0x1f, 0xe0, 0x8f, 0x35,
+};
+
+static const uint8_t unicode_script_ext_table[789] = {
+    0x82, 0xc1, 0x00, 0x00, 0x01, 0x29, 0x01, 0x00,
+    0x00, 0x01, 0x29, 0x1c, 0x00, 0x0c, 0x01, 0x42,
+    0x80, 0x92, 0x00, 0x00, 0x02, 0x1c, 0x68, 0x00,
+    0x02, 0x1c, 0x26, 0x01, 0x02, 0x1c, 0x42, 0x00,
+    0x02, 0x1c, 0x26, 0x80, 0x80, 0x00, 0x00, 0x02,
+    0x05, 0x25, 0x80, 0x01, 0x00, 0x00, 0x04, 0x04,
+    0x2f, 0x84, 0x8e, 0x0d, 0x00, 0x00, 0x04, 0x04,
+    0x2f, 0x84, 0x8e, 0x00, 0x03, 0x04, 0x84, 0x8e,
+    0x01, 0x00, 0x00, 0x04, 0x04, 0x2f, 0x84, 0x8e,
+    0x1f, 0x00, 0x00, 0x08, 0x01, 0x04, 0x4d, 0x4e,
+    0x75, 0x2f, 0x7f, 0x84, 0x09, 0x00, 0x0a, 0x02,
+    0x04, 0x84, 0x09, 0x00, 0x09, 0x02, 0x04, 0x8e,
+    0x05, 0x00, 0x00, 0x02, 0x04, 0x84, 0x62, 0x00,
+    0x00, 0x02, 0x04, 0x2f, 0x81, 0xfb, 0x00, 0x00,
+    0x0d, 0x0b, 0x1e, 0x28, 0x2a, 0x2c, 0x3a, 0x42,
+    0x4c, 0x6d, 0x7a, 0x8b, 0x8d, 0x92, 0x00, 0x0c,
+    0x0b, 0x1e, 0x28, 0x2a, 0x2c, 0x3a, 0x42, 0x4c,
+    0x6d, 0x8b, 0x8d, 0x92, 0x10, 0x00, 0x00, 0x14,
+    0x0b, 0x1e, 0x1f, 0x2b, 0x50, 0x28, 0x2a, 0x2c,
+    0x3a, 0x4b, 0x4c, 0x5d, 0x6d, 0x40, 0x7e, 0x83,
+    0x8a, 0x8b, 0x8d, 0x92, 0x00, 0x15, 0x0b, 0x1e,
+    0x1f, 0x2b, 0x50, 0x28, 0x2a, 0x2c, 0x3a, 0x44,
+    0x4b, 0x4c, 0x5d, 0x6d, 0x40, 0x7e, 0x83, 0x8a,
+    0x8b, 0x8d, 0x92, 0x09, 0x04, 0x1e, 0x1f, 0x39,
+    0x4b, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x83,
+    0x75, 0x00, 0x09, 0x02, 0x2c, 0x5a, 0x75, 0x00,
+    0x09, 0x02, 0x2a, 0x3f, 0x80, 0x75, 0x00, 0x0d,
+    0x02, 0x28, 0x8b, 0x80, 0x71, 0x00, 0x09, 0x02,
+    0x3a, 0x5d, 0x82, 0xcf, 0x00, 0x09, 0x03, 0x15,
+    0x5b, 0x87, 0x80, 0x30, 0x00, 0x00, 0x02, 0x25,
+    0x42, 0x85, 0xb8, 0x00, 0x01, 0x04, 0x11, 0x30,
+    0x86, 0x85, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x58,
+    0x73, 0x00, 0x00, 0x00, 0x02, 0x58, 0x73, 0x84,
+    0x49, 0x00, 0x00, 0x04, 0x0b, 0x1e, 0x28, 0x3a,
+    0x00, 0x01, 0x1e, 0x00, 0x04, 0x0b, 0x1e, 0x28,
+    0x3a, 0x00, 0x02, 0x1e, 0x28, 0x00, 0x01, 0x1e,
+    0x01, 0x02, 0x0b, 0x1e, 0x00, 0x02, 0x1e, 0x7a,
+    0x00, 0x02, 0x0b, 0x1e, 0x00, 0x02, 0x1e, 0x7a,
+    0x00, 0x06, 0x1e, 0x3a, 0x4c, 0x6d, 0x8b, 0x8d,
+    0x00, 0x01, 0x1e, 0x01, 0x02, 0x1e, 0x7a, 0x01,
+    0x01, 0x1e, 0x00, 0x02, 0x1e, 0x7a, 0x00, 0x02,
+    0x0b, 0x1e, 0x06, 0x01, 0x1e, 0x00, 0x02, 0x1e,
+    0x5d, 0x00, 0x02, 0x0b, 0x1e, 0x01, 0x01, 0x1e,
+    0x00, 0x02, 0x0b, 0x1e, 0x03, 0x01, 0x1e, 0x00,
+    0x08, 0x0b, 0x1e, 0x28, 0x3a, 0x5d, 0x6d, 0x8d,
+    0x92, 0x00, 0x02, 0x1e, 0x28, 0x00, 0x03, 0x1e,
+    0x28, 0x3a, 0x01, 0x02, 0x0b, 0x1e, 0x00, 0x01,
+    0x0b, 0x01, 0x02, 0x1e, 0x28, 0x00, 0x01, 0x5d,
+    0x80, 0x44, 0x00, 0x01, 0x01, 0x29, 0x81, 0xec,
+    0x00, 0x00, 0x02, 0x42, 0x58, 0x80, 0x3f, 0x00,
+    0x00, 0x03, 0x1e, 0x28, 0x42, 0x8c, 0xd1, 0x00,
+    0x00, 0x02, 0x1c, 0x26, 0x81, 0x3c, 0x00, 0x01,
+    0x06, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x97, 0x00,
+    0x05, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x01, 0x00,
+    0x00, 0x01, 0x2d, 0x00, 0x00, 0x09, 0x06, 0x0d,
+    0x2e, 0x2d, 0x33, 0x3b, 0x97, 0x00, 0x00, 0x00,
+    0x05, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x07, 0x06,
+    0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x97, 0x03, 0x05,
+    0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x09, 0x00, 0x03,
+    0x02, 0x0d, 0x2d, 0x01, 0x00, 0x00, 0x05, 0x0d,
+    0x2e, 0x2d, 0x33, 0x3b, 0x04, 0x02, 0x33, 0x3b,
+    0x00, 0x00, 0x00, 0x05, 0x0d, 0x2e, 0x2d, 0x33,
+    0x3b, 0x03, 0x00, 0x01, 0x03, 0x2d, 0x33, 0x3b,
+    0x01, 0x01, 0x2d, 0x58, 0x00, 0x03, 0x02, 0x33,
+    0x3b, 0x02, 0x00, 0x00, 0x02, 0x33, 0x3b, 0x59,
+    0x00, 0x00, 0x06, 0x0d, 0x2e, 0x2d, 0x33, 0x3b,
+    0x97, 0x00, 0x02, 0x33, 0x3b, 0x80, 0x12, 0x00,
+    0x0f, 0x01, 0x2d, 0x1f, 0x00, 0x23, 0x01, 0x2d,
+    0x3b, 0x00, 0x27, 0x01, 0x2d, 0x37, 0x00, 0x30,
+    0x01, 0x2d, 0x0e, 0x00, 0x0b, 0x01, 0x2d, 0x32,
+    0x00, 0x00, 0x01, 0x2d, 0x57, 0x00, 0x18, 0x01,
+    0x2d, 0x09, 0x00, 0x04, 0x01, 0x2d, 0x5f, 0x00,
+    0x1e, 0x01, 0x2d, 0xc0, 0x31, 0xef, 0x00, 0x00,
+    0x02, 0x1c, 0x26, 0x81, 0x3f, 0x00, 0x02, 0x0e,
+    0x1e, 0x1f, 0x2a, 0x2c, 0x3f, 0x3a, 0x39, 0x4b,
+    0x4c, 0x57, 0x5d, 0x40, 0x8a, 0x92, 0x02, 0x0d,
+    0x1e, 0x1f, 0x2a, 0x2c, 0x3f, 0x3a, 0x39, 0x4b,
+    0x57, 0x5d, 0x40, 0x8a, 0x92, 0x03, 0x0b, 0x1e,
+    0x1f, 0x2a, 0x2c, 0x3f, 0x39, 0x4b, 0x57, 0x40,
+    0x8a, 0x92, 0x80, 0x36, 0x00, 0x00, 0x02, 0x0b,
+    0x1e, 0x00, 0x00, 0x00, 0x02, 0x1e, 0x8b, 0x39,
+    0x00, 0x00, 0x03, 0x3c, 0x42, 0x5b, 0x80, 0x1f,
+    0x00, 0x00, 0x02, 0x10, 0x38, 0xc0, 0x13, 0xa1,
+    0x00, 0x00, 0x02, 0x04, 0x8e, 0x09, 0x00, 0x00,
+    0x02, 0x04, 0x8e, 0x46, 0x00, 0x01, 0x05, 0x0d,
+    0x2e, 0x2d, 0x33, 0x3b, 0x80, 0x99, 0x00, 0x04,
+    0x06, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x97, 0x09,
+    0x00, 0x00, 0x02, 0x33, 0x3b, 0x2c, 0x00, 0x01,
+    0x02, 0x33, 0x3b, 0x80, 0xdf, 0x00, 0x02, 0x02,
+    0x1b, 0x46, 0x03, 0x00, 0x2c, 0x03, 0x1b, 0x45,
+    0x46, 0x02, 0x00, 0x08, 0x02, 0x1b, 0x46, 0x81,
+    0x1f, 0x00, 0x1b, 0x02, 0x04, 0x19, 0x8f, 0x84,
+    0x00, 0x00, 0x02, 0x28, 0x8b, 0x00, 0x00, 0x00,
+    0x02, 0x28, 0x8b, 0x36, 0x00, 0x01, 0x02, 0x28,
+    0x8b, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x28, 0x8b,
+    0x00, 0x00, 0x00, 0x02, 0x28, 0x8b, 0xc0, 0x5c,
+    0x4b, 0x00, 0x03, 0x01, 0x20, 0x96, 0x3b, 0x00,
+    0x11, 0x01, 0x2d, 0x9e, 0x5d, 0x00, 0x01, 0x01,
+    0x2d, 0xce, 0xcd, 0x2d, 0x00,
+};
+
+static const uint8_t unicode_prop_Hyphen_table[28] = {
+    0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52,
+    0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80,
+    0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40,
+    0xa8, 0x80, 0xd6, 0x80,
+};
+
+static const uint8_t unicode_prop_Other_Math_table[200] = {
+    0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09,
+    0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80,
+    0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c,
+    0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00,
+    0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24,
+    0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b,
+    0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41,
+    0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80,
+    0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24,
+    0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97,
+    0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81,
+    0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95,
+    0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00,
+    0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4,
+    0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b,
+    0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f,
+    0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80,
+    0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e,
+    0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e,
+    0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81,
+    0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08,
+    0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00,
+    0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00,
+    0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90,
+};
+
+static const uint8_t unicode_prop_Other_Alphabetic_table[396] = {
+    0x43, 0x44, 0x80, 0x42, 0x69, 0x8d, 0x00, 0x01,
+    0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, 0x06, 0x8f,
+    0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, 0xa2, 0x80,
+    0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, 0x88, 0x02,
+    0x03, 0x40, 0xa6, 0x8b, 0x16, 0x85, 0x93, 0xb5,
+    0x09, 0x8e, 0x01, 0x22, 0x89, 0x81, 0x9c, 0x82,
+    0xb9, 0x31, 0x09, 0x81, 0x89, 0x80, 0x89, 0x81,
+    0x9c, 0x82, 0xb9, 0x23, 0x09, 0x0b, 0x80, 0x9d,
+    0x0a, 0x80, 0x8a, 0x82, 0xb9, 0x38, 0x10, 0x81,
+    0x94, 0x81, 0x95, 0x13, 0x82, 0xb9, 0x31, 0x09,
+    0x81, 0x88, 0x81, 0x89, 0x81, 0x9d, 0x80, 0xba,
+    0x22, 0x10, 0x82, 0x89, 0x80, 0xa7, 0x83, 0xb9,
+    0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x9c, 0x82,
+    0xb9, 0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x9b,
+    0x83, 0xb9, 0x30, 0x10, 0x82, 0x89, 0x80, 0x89,
+    0x81, 0x9d, 0x81, 0xca, 0x28, 0x00, 0x87, 0x91,
+    0x81, 0xbc, 0x01, 0x86, 0x91, 0x80, 0xe2, 0x01,
+    0x28, 0x81, 0x8f, 0x80, 0x40, 0xa2, 0x90, 0x8a,
+    0x8a, 0x80, 0xa3, 0xed, 0x8b, 0x00, 0x0b, 0x96,
+    0x1b, 0x10, 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x00,
+    0x89, 0x83, 0x46, 0x73, 0x81, 0x9d, 0x81, 0x9d,
+    0x81, 0x9d, 0x81, 0xc1, 0x92, 0x40, 0xbb, 0x81,
+    0xa1, 0x80, 0xf5, 0x8b, 0x83, 0x88, 0x40, 0xdd,
+    0x84, 0xb8, 0x89, 0x81, 0x93, 0x40, 0x8a, 0x84,
+    0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8,
+    0x8a, 0xb1, 0x92, 0x41, 0xaf, 0x8d, 0x46, 0xc0,
+    0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87,
+    0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84,
+    0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5,
+    0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4,
+    0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82,
+    0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80,
+    0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48,
+    0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43,
+    0x13, 0x83, 0x42, 0xd7, 0x82, 0xb4, 0x8d, 0xbb,
+    0x80, 0xac, 0x88, 0xc6, 0x82, 0xa3, 0x8b, 0x91,
+    0x81, 0xb8, 0x82, 0xaf, 0x8c, 0xeb, 0x88, 0x08,
+    0x28, 0x40, 0x9f, 0x89, 0x96, 0x83, 0xb9, 0x31,
+    0x09, 0x81, 0x89, 0x80, 0x89, 0x81, 0x40, 0xd0,
+    0x8c, 0x02, 0xe9, 0x91, 0x40, 0xec, 0x31, 0x86,
+    0x9c, 0x81, 0xd1, 0x8e, 0x00, 0xe9, 0x8a, 0xe6,
+    0x8d, 0x41, 0x00, 0x8c, 0x41, 0x97, 0x31, 0x2b,
+    0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, 0x91, 0x8a,
+    0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, 0xd2, 0x95,
+    0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, 0x10, 0x02,
+    0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, 0x5b, 0x83,
+    0x60, 0x50, 0x57, 0x00, 0xb6, 0x33, 0x60, 0x4d,
+    0x0a, 0x80, 0x60, 0x23, 0x60, 0x30, 0x90, 0x0e,
+    0x01, 0x04, 0x49, 0x1b, 0x80, 0x47, 0xe7, 0x99,
+    0x85, 0x99, 0x85, 0x99,
+};
+
+static const uint8_t unicode_prop_Other_Lowercase_table[51] = {
+    0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88,
+    0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x59,
+    0xb0, 0xbe, 0x8c, 0x80, 0xa1, 0xa4, 0x42, 0xb0,
+    0x80, 0x8c, 0x80, 0x8f, 0x8c, 0x40, 0xd2, 0x8f,
+    0x43, 0x4f, 0x99, 0x47, 0x91, 0x81, 0x60, 0x7a,
+    0x1d, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x86, 0x81,
+    0x43, 0x61, 0x83,
+};
+
+static const uint8_t unicode_prop_Other_Uppercase_table[15] = {
+    0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61,
+    0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99,
+};
+
+static const uint8_t unicode_prop_Other_Grapheme_Extend_table[62] = {
+    0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80,
+    0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe9,
+    0x80, 0x91, 0x81, 0xe6, 0x80, 0x97, 0x80, 0xf6,
+    0x80, 0x8e, 0x80, 0x4d, 0x54, 0x80, 0x44, 0xd5,
+    0x80, 0x50, 0x20, 0x81, 0x60, 0xcf, 0x6d, 0x81,
+    0x53, 0x9d, 0x80, 0x97, 0x80, 0x41, 0x57, 0x80,
+    0x8b, 0x80, 0x40, 0xf0, 0x80, 0x60, 0xbb, 0xb4,
+    0x07, 0x84, 0x6c, 0x2e, 0xac, 0xdf,
+};
+
+static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = {
+    0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52,
+    0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60,
+    0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06,
+    0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f,
+};
+
+static const uint8_t unicode_prop_Other_ID_Start_table[11] = {
+    0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80,
+    0x4f, 0x6b, 0x81,
+};
+
+static const uint8_t unicode_prop_Other_ID_Continue_table[12] = {
+    0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0,
+    0x88, 0x46, 0x67, 0x80,
+};
+
+static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[17] = {
+    0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80,
+    0x41, 0xd1, 0x80, 0x61, 0x07, 0xd9, 0x80, 0x8e,
+    0x80,
+};
+
+static const uint8_t unicode_prop_XID_Start1_table[31] = {
+    0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80,
+    0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85,
+    0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81,
+};
+
+static const uint8_t unicode_prop_XID_Continue1_table[23] = {
+    0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60,
+    0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+};
+
+static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = {
+    0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e,
+    0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87,
+    0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80,
+};
+
+static const uint8_t unicode_prop_Changes_When_Casefolded1_table[33] = {
+    0x40, 0xde, 0x80, 0xcf, 0x80, 0x97, 0x80, 0x44,
+    0x3c, 0x80, 0x59, 0x11, 0x80, 0x40, 0xe4, 0x3f,
+    0x3f, 0x87, 0x89, 0x11, 0x05, 0x02, 0x11, 0x80,
+    0xa9, 0x11, 0x80, 0x60, 0xdb, 0x07, 0x86, 0x8b,
+    0x84,
+};
+
+static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[436] = {
+    0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12,
+    0x10, 0x82, 0x9f, 0x80, 0xcf, 0x01, 0x80, 0x8b,
+    0x07, 0x80, 0xfb, 0x01, 0x01, 0x80, 0xa5, 0x80,
+    0x40, 0xbb, 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08,
+    0x81, 0x89, 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08,
+    0x80, 0xc9, 0x82, 0x9c, 0x80, 0x41, 0x93, 0x80,
+    0x40, 0x93, 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87,
+    0xfb, 0x08, 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11,
+    0x80, 0x40, 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe,
+    0x80, 0xa7, 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88,
+    0x03, 0x03, 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00,
+    0x26, 0x80, 0x90, 0x80, 0x88, 0x03, 0x03, 0x03,
+    0x80, 0x8b, 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81,
+    0x46, 0x52, 0x81, 0xd4, 0x83, 0x45, 0x1c, 0x10,
+    0x8a, 0x80, 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1,
+    0xa4, 0x40, 0xd9, 0x80, 0x40, 0xd5, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x3f, 0x87,
+    0x89, 0x11, 0x04, 0x00, 0x29, 0x04, 0x12, 0x80,
+    0x88, 0x12, 0x80, 0x88, 0x11, 0x11, 0x04, 0x08,
+    0x8f, 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b,
+    0x00, 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a,
+    0x80, 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a,
+    0x01, 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06,
+    0x05, 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80,
+    0x40, 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41,
+    0x34, 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6,
+    0x82, 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0,
+    0x80, 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40,
+    0xd5, 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09,
+    0x80, 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf,
+    0x9e, 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f,
+    0x60, 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40,
+    0x86, 0x81, 0x43, 0x61, 0x83, 0x60, 0x4d, 0x9f,
+    0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, 0x00, 0x00,
+    0x09, 0x82, 0xc3, 0x81, 0xe9, 0xa5, 0x86, 0x8b,
+    0x24, 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80,
+    0xeb, 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5,
+    0xa7, 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b,
+    0x80, 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86,
+    0x08, 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d,
+    0x0d, 0x0a, 0x16, 0x06, 0x80, 0x88, 0x60, 0xbc,
+    0xa6, 0x83, 0x54, 0xb9, 0x86, 0x8d, 0x87, 0xbf,
+    0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08,
+    0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0,
+    0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00,
+    0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81,
+    0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08,
+    0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00,
+    0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00,
+    0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90,
+    0x42, 0x43, 0x8a, 0x84, 0x9e, 0x80, 0x9f, 0x99,
+    0x82, 0xa2, 0x80, 0xee, 0x82, 0x8c, 0xab, 0x83,
+    0x88, 0x31, 0x61, 0x05, 0xad, 0x42, 0x1d, 0x6b,
+    0x05, 0xe1, 0x4f, 0xff,
+};
+
+static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = {
+    0xaf, 0x89, 0x35, 0x99, 0x85,
+};
+
+static const uint8_t unicode_prop_Bidi_Control_table[10] = {
+    0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84,
+    0xb6, 0x83,
+};
+
+static const uint8_t unicode_prop_Dash_table[50] = {
+    0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e,
+    0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85,
+    0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85,
+    0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80,
+    0x41, 0xda, 0x80, 0x92, 0x80, 0xee, 0x80, 0x60,
+    0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, 0x80, 0x40,
+    0xa8, 0x80,
+};
+
+static const uint8_t unicode_prop_Deprecated_table[23] = {
+    0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02,
+    0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85,
+    0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80,
+};
+
+static const uint8_t unicode_prop_Diacritic_table[350] = {
+    0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81,
+    0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b,
+    0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0,
+    0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00,
+    0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a,
+    0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1,
+    0x81, 0x40, 0xc8, 0x9b, 0xbc, 0x80, 0x8f, 0x02,
+    0x83, 0x9b, 0x80, 0xc9, 0x80, 0x8f, 0x80, 0xed,
+    0x80, 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xae,
+    0x82, 0xbb, 0x80, 0x8f, 0x80, 0xfe, 0x80, 0xfe,
+    0x80, 0xed, 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f,
+    0x80, 0xfb, 0x80, 0xfb, 0x28, 0x80, 0xea, 0x80,
+    0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03,
+    0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00,
+    0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89,
+    0x81, 0x42, 0xc0, 0x82, 0x44, 0x68, 0x8a, 0x88,
+    0x80, 0x41, 0x5a, 0x82, 0x41, 0x38, 0x39, 0x80,
+    0xaf, 0x8d, 0xf5, 0x80, 0x8e, 0x80, 0xa5, 0x88,
+    0xb5, 0x81, 0x40, 0x89, 0x81, 0xbf, 0x85, 0xd1,
+    0x98, 0x18, 0x28, 0x0a, 0xb1, 0xbe, 0xd8, 0x8b,
+    0xa4, 0x22, 0x82, 0x41, 0xbc, 0x00, 0x82, 0x8a,
+    0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, 0x81, 0x4c,
+    0xef, 0x82, 0x41, 0x3c, 0x80, 0x41, 0xf9, 0x85,
+    0xe8, 0x83, 0xde, 0x80, 0x60, 0x75, 0x71, 0x80,
+    0x8b, 0x08, 0x80, 0x9b, 0x81, 0xd1, 0x81, 0x8d,
+    0xa1, 0xe5, 0x82, 0xec, 0x81, 0x40, 0xc9, 0x80,
+    0x9a, 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80,
+    0x8b, 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0,
+    0x83, 0xb2, 0x80, 0xe3, 0x84, 0x40, 0x8b, 0x81,
+    0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41,
+    0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2,
+    0x80, 0x42, 0xfb, 0x80, 0x48, 0x03, 0x81, 0x42,
+    0x3a, 0x85, 0x42, 0x1d, 0x8a, 0x41, 0x67, 0x81,
+    0xf7, 0x81, 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82,
+    0xe7, 0x81, 0x40, 0xb1, 0x81, 0xd0, 0x80, 0x8f,
+    0x80, 0x97, 0x32, 0x84, 0x40, 0xcc, 0x02, 0x80,
+    0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, 0x80, 0xf5,
+    0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, 0x41, 0xa4,
+    0x80, 0xd2, 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41,
+    0xa4, 0x80, 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80,
+    0x60, 0x4d, 0x57, 0x84, 0xba, 0x86, 0x44, 0x57,
+    0x90, 0x60, 0x61, 0xc6, 0x12, 0x2f, 0x39, 0x86,
+    0x9d, 0x83, 0x4f, 0x81, 0x86, 0x41, 0xb4, 0x83,
+    0x45, 0xdf, 0x86, 0xec, 0x10, 0x82,
+};
+
+static const uint8_t unicode_prop_Extender_table[86] = {
+    0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d,
+    0x80, 0x41, 0xb8, 0x80, 0x46, 0x4a, 0x80, 0xfe,
+    0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62,
+    0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88,
+    0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60,
+    0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1,
+    0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80,
+    0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x53, 0xeb,
+    0x80, 0x42, 0x67, 0x82, 0x44, 0xce, 0x80, 0x60,
+    0x50, 0xa8, 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60,
+    0x71, 0x57, 0x81, 0x48, 0x05, 0x82,
+};
+
+static const uint8_t unicode_prop_Hex_Digit_table[12] = {
+    0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8,
+    0x89, 0x35, 0x99, 0x85,
+};
+
+static const uint8_t unicode_prop_IDS_Binary_Operator_table[5] = {
+    0x60, 0x2f, 0xef, 0x09, 0x87,
+};
+
+static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = {
+    0x60, 0x2f, 0xf1, 0x81,
+};
+
+static const uint8_t unicode_prop_Ideographic_table[58] = {
+    0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82,
+    0x43, 0xc4, 0x59, 0xb5, 0xc9, 0x60, 0x51, 0xef,
+    0x60, 0x59, 0x0f, 0x41, 0x6d, 0x81, 0xe9, 0x60,
+    0x75, 0x25, 0x57, 0xf7, 0x87, 0x42, 0xf2, 0x60,
+    0x26, 0x7c, 0x41, 0x8b, 0x60, 0x4d, 0x03, 0x60,
+    0xa6, 0xd6, 0xa8, 0x50, 0x34, 0x8a, 0x40, 0xdd,
+    0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x4c, 0x1e,
+    0x42, 0x1d,
+};
+
+static const uint8_t unicode_prop_Join_Control_table[4] = {
+    0x60, 0x20, 0x0b, 0x81,
+};
+
+static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = {
+    0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11,
+    0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81,
+};
+
+static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = {
+    0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60,
+    0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81,
+};
+
+static const uint8_t unicode_prop_Pattern_Syntax_table[58] = {
+    0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99,
+    0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03,
+    0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17,
+    0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41,
+    0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d,
+    0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13,
+    0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41,
+    0x04, 0x81,
+};
+
+static const uint8_t unicode_prop_Pattern_White_Space_table[11] = {
+    0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87,
+    0x81, 0x97, 0x81,
+};
+
+static const uint8_t unicode_prop_Quotation_Mark_table[31] = {
+    0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80,
+    0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80,
+    0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20,
+    0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81,
+};
+
+static const uint8_t unicode_prop_Radical_table[9] = {
+    0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40,
+    0xd5,
+};
+
+static const uint8_t unicode_prop_Regional_Indicator_table[4] = {
+    0x61, 0xf1, 0xe5, 0x99,
+};
+
+static const uint8_t unicode_prop_Sentence_Terminal_table[184] = {
+    0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48,
+    0x80, 0x40, 0x93, 0x81, 0x40, 0xb3, 0x80, 0xaa,
+    0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81,
+    0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15,
+    0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81,
+    0x40, 0xcb, 0x04, 0x80, 0x41, 0x39, 0x81, 0x41,
+    0x61, 0x83, 0x40, 0xad, 0x09, 0x81, 0x40, 0xda,
+    0x81, 0xc0, 0x81, 0x43, 0xbb, 0x81, 0x88, 0x82,
+    0x4d, 0xe3, 0x80, 0x8c, 0x80, 0x41, 0xc4, 0x80,
+    0x60, 0x74, 0xfb, 0x80, 0x41, 0x0d, 0x81, 0x40,
+    0xe2, 0x02, 0x80, 0x41, 0x7d, 0x81, 0xd5, 0x81,
+    0xde, 0x80, 0x40, 0x97, 0x81, 0x40, 0x92, 0x82,
+    0x40, 0x8f, 0x81, 0x40, 0xf8, 0x80, 0x60, 0x52,
+    0x65, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80,
+    0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44,
+    0xfc, 0x84, 0x40, 0xec, 0x81, 0xf4, 0x83, 0xfe,
+    0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7,
+    0x08, 0x81, 0xeb, 0x80, 0x41, 0xa0, 0x81, 0x41,
+    0x74, 0x0c, 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82,
+    0x43, 0x02, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81,
+    0x42, 0xb3, 0x81, 0x60, 0x4b, 0x74, 0x81, 0x40,
+    0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, 0x43, 0x52,
+    0x80, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe7, 0x80,
+};
+
+static const uint8_t unicode_prop_Soft_Dotted_table[71] = {
+    0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80,
+    0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f,
+    0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2,
+    0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40,
+    0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81,
+    0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1,
+    0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1,
+    0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1,
+    0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81,
+};
+
+static const uint8_t unicode_prop_Terminal_Punctuation_table[237] = {
+    0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80,
+    0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8,
+    0x80, 0xc7, 0x80, 0x8d, 0x01, 0x81, 0x40, 0xb3,
+    0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5,
+    0x8e, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, 0xf3,
+    0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, 0x81,
+    0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, 0x82,
+    0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, 0x19,
+    0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, 0x40,
+    0xad, 0x08, 0x82, 0x40, 0xda, 0x84, 0xbd, 0x81,
+    0x43, 0xbb, 0x81, 0x88, 0x82, 0x4d, 0xe3, 0x80,
+    0x8c, 0x03, 0x80, 0x89, 0x00, 0x81, 0x41, 0xb0,
+    0x81, 0x60, 0x74, 0xfa, 0x81, 0x41, 0x0c, 0x82,
+    0x40, 0xe2, 0x84, 0x41, 0x7d, 0x81, 0xd5, 0x81,
+    0xde, 0x80, 0x40, 0x96, 0x82, 0x40, 0x92, 0x82,
+    0xfe, 0x80, 0x8f, 0x81, 0x40, 0xf8, 0x80, 0x60,
+    0x52, 0x63, 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89,
+    0x00, 0x80, 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80,
+    0x44, 0x39, 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80,
+    0x40, 0xc6, 0x80, 0x41, 0x35, 0x81, 0x40, 0x97,
+    0x85, 0xc3, 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84,
+    0x40, 0xec, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40,
+    0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb,
+    0x80, 0x41, 0xa0, 0x82, 0x8c, 0x80, 0x41, 0x65,
+    0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82, 0x43,
+    0x02, 0x81, 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82,
+    0xac, 0x80, 0x42, 0x84, 0x81, 0x45, 0x76, 0x84,
+    0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0,
+    0x82, 0x89, 0x80, 0x43, 0x51, 0x81, 0x60, 0x4e,
+    0x05, 0x80, 0x5d, 0xe6, 0x83,
+};
+
+static const uint8_t unicode_prop_Unified_Ideograph_table[38] = {
+    0x60, 0x33, 0xff, 0x59, 0xb5, 0xc9, 0x60, 0x51,
+    0xef, 0x60, 0x5a, 0x1d, 0x08, 0x00, 0x81, 0x89,
+    0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60,
+    0xa6, 0xd6, 0xa8, 0x50, 0x34, 0x8a, 0x40, 0xdd,
+    0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30,
+};
+
+static const uint8_t unicode_prop_Variation_Selector_table[12] = {
+    0x58, 0x0a, 0x82, 0x60, 0xe5, 0xf1, 0x8f, 0x6d,
+    0x02, 0xef, 0x40, 0xef,
+};
+
+static const uint8_t unicode_prop_White_Space_table[22] = {
+    0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80,
+    0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c,
+    0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80,
+};
+
+static const uint8_t unicode_prop_Bidi_Mirrored_table[171] = {
+    0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80,
+    0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e,
+    0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81,
+    0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0,
+    0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18,
+    0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23,
+    0x88, 0x08, 0x00, 0x39, 0x9e, 0x0b, 0x20, 0x88,
+    0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81,
+    0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d,
+    0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d,
+    0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35,
+    0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89,
+    0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08,
+    0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18,
+    0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00,
+    0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80,
+    0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89,
+    0x41, 0xdd, 0x89, 0x0f, 0x60, 0xce, 0x3c, 0x2c,
+    0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, 0x80, 0x9b,
+    0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, 0x81, 0x60,
+    0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, 0x80, 0xb8,
+    0x80, 0xb8, 0x80,
+};
+
+static const uint8_t unicode_prop_Emoji_table[236] = {
+    0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f,
+    0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95,
+    0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81,
+    0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a,
+    0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80,
+    0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01,
+    0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f,
+    0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08,
+    0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a,
+    0x0f, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, 0x08,
+    0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, 0x01,
+    0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, 0x05,
+    0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03,
+    0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, 0x80,
+    0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41,
+    0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44,
+    0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80,
+    0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, 0x40,
+    0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0xca,
+    0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, 0x94,
+    0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, 0x02,
+    0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, 0xbe,
+    0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, 0x19,
+    0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, 0x04,
+    0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, 0x02,
+    0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x08, 0x89, 0x2a,
+    0x00, 0x0a, 0x01, 0x87, 0x40, 0xe4, 0x8b, 0x41,
+    0x20, 0xad, 0x80, 0x89, 0x80, 0xaa, 0x03, 0x82,
+    0xa8, 0x0d, 0x82, 0x9c, 0x81, 0xb2, 0xef, 0x1b,
+    0x14, 0x82, 0x8c, 0x85,
+};
+
+static const uint8_t unicode_prop_Emoji_Component_table[28] = {
+    0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40,
+    0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3,
+    0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83,
+    0x6c, 0x06, 0x6b, 0xdf,
+};
+
+static const uint8_t unicode_prop_Emoji_Modifier_table[4] = {
+    0x61, 0xf3, 0xfa, 0x84,
+};
+
+static const uint8_t unicode_prop_Emoji_Modifier_Base_table[63] = {
+    0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f,
+    0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01,
+    0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a,
+    0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b,
+    0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84,
+    0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80,
+    0x42, 0x41, 0x07, 0x3d, 0x80, 0x88, 0x89, 0x0a,
+    0xf5, 0x08, 0x08, 0x80, 0x90, 0x10, 0x8c,
+};
+
+static const uint8_t unicode_prop_Emoji_Presentation_table[143] = {
+    0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01,
+    0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b,
+    0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90,
+    0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03,
+    0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00,
+    0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d,
+    0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61,
+    0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd,
+    0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93,
+    0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0,
+    0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa,
+    0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80,
+    0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91,
+    0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf,
+    0xc5, 0x28, 0x12, 0x08, 0x94, 0x0e, 0x86, 0x40,
+    0xe4, 0x8b, 0x41, 0x20, 0xad, 0x80, 0x89, 0x80,
+    0xaa, 0x03, 0x82, 0xa8, 0x0d, 0x82, 0x9c, 0x81,
+    0xb2, 0xef, 0x1b, 0x14, 0x82, 0x8c, 0x85,
+};
+
+static const uint8_t unicode_prop_Extended_Pictographic_table[152] = {
+    0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b,
+    0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85,
+    0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde,
+    0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6,
+    0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80,
+    0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5,
+    0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89,
+    0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80,
+    0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80,
+    0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82,
+    0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80,
+    0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd,
+    0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb,
+    0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8,
+    0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03,
+    0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41,
+    0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b,
+    0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d,
+    0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x46, 0xb6,
+};
+
+static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = {
+    0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb,
+    0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4,
+    0x83, 0x47, 0xfb, 0x84, 0x99, 0x84, 0xb0, 0x8f,
+    0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40,
+    0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60,
+    0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e,
+    0x84, 0x4f, 0xff,
+};
+
+typedef enum {
+    UNICODE_PROP_Hyphen,
+    UNICODE_PROP_Other_Math,
+    UNICODE_PROP_Other_Alphabetic,
+    UNICODE_PROP_Other_Lowercase,
+    UNICODE_PROP_Other_Uppercase,
+    UNICODE_PROP_Other_Grapheme_Extend,
+    UNICODE_PROP_Other_Default_Ignorable_Code_Point,
+    UNICODE_PROP_Other_ID_Start,
+    UNICODE_PROP_Other_ID_Continue,
+    UNICODE_PROP_Prepended_Concatenation_Mark,
+    UNICODE_PROP_ID_Continue1,
+    UNICODE_PROP_XID_Start1,
+    UNICODE_PROP_XID_Continue1,
+    UNICODE_PROP_Changes_When_Titlecased1,
+    UNICODE_PROP_Changes_When_Casefolded1,
+    UNICODE_PROP_Changes_When_NFKC_Casefolded1,
+    UNICODE_PROP_ASCII_Hex_Digit,
+    UNICODE_PROP_Bidi_Control,
+    UNICODE_PROP_Dash,
+    UNICODE_PROP_Deprecated,
+    UNICODE_PROP_Diacritic,
+    UNICODE_PROP_Extender,
+    UNICODE_PROP_Hex_Digit,
+    UNICODE_PROP_IDS_Binary_Operator,
+    UNICODE_PROP_IDS_Trinary_Operator,
+    UNICODE_PROP_Ideographic,
+    UNICODE_PROP_Join_Control,
+    UNICODE_PROP_Logical_Order_Exception,
+    UNICODE_PROP_Noncharacter_Code_Point,
+    UNICODE_PROP_Pattern_Syntax,
+    UNICODE_PROP_Pattern_White_Space,
+    UNICODE_PROP_Quotation_Mark,
+    UNICODE_PROP_Radical,
+    UNICODE_PROP_Regional_Indicator,
+    UNICODE_PROP_Sentence_Terminal,
+    UNICODE_PROP_Soft_Dotted,
+    UNICODE_PROP_Terminal_Punctuation,
+    UNICODE_PROP_Unified_Ideograph,
+    UNICODE_PROP_Variation_Selector,
+    UNICODE_PROP_White_Space,
+    UNICODE_PROP_Bidi_Mirrored,
+    UNICODE_PROP_Emoji,
+    UNICODE_PROP_Emoji_Component,
+    UNICODE_PROP_Emoji_Modifier,
+    UNICODE_PROP_Emoji_Modifier_Base,
+    UNICODE_PROP_Emoji_Presentation,
+    UNICODE_PROP_Extended_Pictographic,
+    UNICODE_PROP_Default_Ignorable_Code_Point,
+    UNICODE_PROP_ID_Start,
+    UNICODE_PROP_Case_Ignorable,
+    UNICODE_PROP_ASCII,
+    UNICODE_PROP_Alphabetic,
+    UNICODE_PROP_Any,
+    UNICODE_PROP_Assigned,
+    UNICODE_PROP_Cased,
+    UNICODE_PROP_Changes_When_Casefolded,
+    UNICODE_PROP_Changes_When_Casemapped,
+    UNICODE_PROP_Changes_When_Lowercased,
+    UNICODE_PROP_Changes_When_NFKC_Casefolded,
+    UNICODE_PROP_Changes_When_Titlecased,
+    UNICODE_PROP_Changes_When_Uppercased,
+    UNICODE_PROP_Grapheme_Base,
+    UNICODE_PROP_Grapheme_Extend,
+    UNICODE_PROP_ID_Continue,
+    UNICODE_PROP_Lowercase,
+    UNICODE_PROP_Math,
+    UNICODE_PROP_Uppercase,
+    UNICODE_PROP_XID_Continue,
+    UNICODE_PROP_XID_Start,
+    UNICODE_PROP_Cased1,
+    UNICODE_PROP_COUNT,
+} UnicodePropertyEnum;
+
+static const char unicode_prop_name_table[] =
+    "ASCII_Hex_Digit,AHex"               "\0"
+    "Bidi_Control,Bidi_C"                "\0"
+    "Dash"                               "\0"
+    "Deprecated,Dep"                     "\0"
+    "Diacritic,Dia"                      "\0"
+    "Extender,Ext"                       "\0"
+    "Hex_Digit,Hex"                      "\0"
+    "IDS_Binary_Operator,IDSB"           "\0"
+    "IDS_Trinary_Operator,IDST"          "\0"
+    "Ideographic,Ideo"                   "\0"
+    "Join_Control,Join_C"                "\0"
+    "Logical_Order_Exception,LOE"        "\0"
+    "Noncharacter_Code_Point,NChar"      "\0"
+    "Pattern_Syntax,Pat_Syn"             "\0"
+    "Pattern_White_Space,Pat_WS"         "\0"
+    "Quotation_Mark,QMark"               "\0"
+    "Radical"                            "\0"
+    "Regional_Indicator,RI"              "\0"
+    "Sentence_Terminal,STerm"            "\0"
+    "Soft_Dotted,SD"                     "\0"
+    "Terminal_Punctuation,Term"          "\0"
+    "Unified_Ideograph,UIdeo"            "\0"
+    "Variation_Selector,VS"              "\0"
+    "White_Space,space"                  "\0"
+    "Bidi_Mirrored,Bidi_M"               "\0"
+    "Emoji"                              "\0"
+    "Emoji_Component"                    "\0"
+    "Emoji_Modifier"                     "\0"
+    "Emoji_Modifier_Base"                "\0"
+    "Emoji_Presentation"                 "\0"
+    "Extended_Pictographic"              "\0"
+    "Default_Ignorable_Code_Point,DI"    "\0"
+    "ID_Start,IDS"                       "\0"
+    "Case_Ignorable,CI"                  "\0"
+    "ASCII"                              "\0"
+    "Alphabetic,Alpha"                   "\0"
+    "Any"                                "\0"
+    "Assigned"                           "\0"
+    "Cased"                              "\0"
+    "Changes_When_Casefolded,CWCF"       "\0"
+    "Changes_When_Casemapped,CWCM"       "\0"
+    "Changes_When_Lowercased,CWL"        "\0"
+    "Changes_When_NFKC_Casefolded,CWKCF" "\0"
+    "Changes_When_Titlecased,CWT"        "\0"
+    "Changes_When_Uppercased,CWU"        "\0"
+    "Grapheme_Base,Gr_Base"              "\0"
+    "Grapheme_Extend,Gr_Ext"             "\0"
+    "ID_Continue,IDC"                    "\0"
+    "Lowercase,Lower"                    "\0"
+    "Math"                               "\0"
+    "Uppercase,Upper"                    "\0"
+    "XID_Continue,XIDC"                  "\0"
+    "XID_Start,XIDS"                     "\0"
+;
+
+static const uint8_t * const unicode_prop_table[] = {
+    unicode_prop_Hyphen_table,
+    unicode_prop_Other_Math_table,
+    unicode_prop_Other_Alphabetic_table,
+    unicode_prop_Other_Lowercase_table,
+    unicode_prop_Other_Uppercase_table,
+    unicode_prop_Other_Grapheme_Extend_table,
+    unicode_prop_Other_Default_Ignorable_Code_Point_table,
+    unicode_prop_Other_ID_Start_table,
+    unicode_prop_Other_ID_Continue_table,
+    unicode_prop_Prepended_Concatenation_Mark_table,
+    unicode_prop_ID_Continue1_table,
+    unicode_prop_XID_Start1_table,
+    unicode_prop_XID_Continue1_table,
+    unicode_prop_Changes_When_Titlecased1_table,
+    unicode_prop_Changes_When_Casefolded1_table,
+    unicode_prop_Changes_When_NFKC_Casefolded1_table,
+    unicode_prop_ASCII_Hex_Digit_table,
+    unicode_prop_Bidi_Control_table,
+    unicode_prop_Dash_table,
+    unicode_prop_Deprecated_table,
+    unicode_prop_Diacritic_table,
+    unicode_prop_Extender_table,
+    unicode_prop_Hex_Digit_table,
+    unicode_prop_IDS_Binary_Operator_table,
+    unicode_prop_IDS_Trinary_Operator_table,
+    unicode_prop_Ideographic_table,
+    unicode_prop_Join_Control_table,
+    unicode_prop_Logical_Order_Exception_table,
+    unicode_prop_Noncharacter_Code_Point_table,
+    unicode_prop_Pattern_Syntax_table,
+    unicode_prop_Pattern_White_Space_table,
+    unicode_prop_Quotation_Mark_table,
+    unicode_prop_Radical_table,
+    unicode_prop_Regional_Indicator_table,
+    unicode_prop_Sentence_Terminal_table,
+    unicode_prop_Soft_Dotted_table,
+    unicode_prop_Terminal_Punctuation_table,
+    unicode_prop_Unified_Ideograph_table,
+    unicode_prop_Variation_Selector_table,
+    unicode_prop_White_Space_table,
+    unicode_prop_Bidi_Mirrored_table,
+    unicode_prop_Emoji_table,
+    unicode_prop_Emoji_Component_table,
+    unicode_prop_Emoji_Modifier_table,
+    unicode_prop_Emoji_Modifier_Base_table,
+    unicode_prop_Emoji_Presentation_table,
+    unicode_prop_Extended_Pictographic_table,
+    unicode_prop_Default_Ignorable_Code_Point_table,
+    unicode_prop_ID_Start_table,
+    unicode_prop_Case_Ignorable_table,
+};
+
+static const uint16_t unicode_prop_len_table[] = {
+    countof(unicode_prop_Hyphen_table),
+    countof(unicode_prop_Other_Math_table),
+    countof(unicode_prop_Other_Alphabetic_table),
+    countof(unicode_prop_Other_Lowercase_table),
+    countof(unicode_prop_Other_Uppercase_table),
+    countof(unicode_prop_Other_Grapheme_Extend_table),
+    countof(unicode_prop_Other_Default_Ignorable_Code_Point_table),
+    countof(unicode_prop_Other_ID_Start_table),
+    countof(unicode_prop_Other_ID_Continue_table),
+    countof(unicode_prop_Prepended_Concatenation_Mark_table),
+    countof(unicode_prop_ID_Continue1_table),
+    countof(unicode_prop_XID_Start1_table),
+    countof(unicode_prop_XID_Continue1_table),
+    countof(unicode_prop_Changes_When_Titlecased1_table),
+    countof(unicode_prop_Changes_When_Casefolded1_table),
+    countof(unicode_prop_Changes_When_NFKC_Casefolded1_table),
+    countof(unicode_prop_ASCII_Hex_Digit_table),
+    countof(unicode_prop_Bidi_Control_table),
+    countof(unicode_prop_Dash_table),
+    countof(unicode_prop_Deprecated_table),
+    countof(unicode_prop_Diacritic_table),
+    countof(unicode_prop_Extender_table),
+    countof(unicode_prop_Hex_Digit_table),
+    countof(unicode_prop_IDS_Binary_Operator_table),
+    countof(unicode_prop_IDS_Trinary_Operator_table),
+    countof(unicode_prop_Ideographic_table),
+    countof(unicode_prop_Join_Control_table),
+    countof(unicode_prop_Logical_Order_Exception_table),
+    countof(unicode_prop_Noncharacter_Code_Point_table),
+    countof(unicode_prop_Pattern_Syntax_table),
+    countof(unicode_prop_Pattern_White_Space_table),
+    countof(unicode_prop_Quotation_Mark_table),
+    countof(unicode_prop_Radical_table),
+    countof(unicode_prop_Regional_Indicator_table),
+    countof(unicode_prop_Sentence_Terminal_table),
+    countof(unicode_prop_Soft_Dotted_table),
+    countof(unicode_prop_Terminal_Punctuation_table),
+    countof(unicode_prop_Unified_Ideograph_table),
+    countof(unicode_prop_Variation_Selector_table),
+    countof(unicode_prop_White_Space_table),
+    countof(unicode_prop_Bidi_Mirrored_table),
+    countof(unicode_prop_Emoji_table),
+    countof(unicode_prop_Emoji_Component_table),
+    countof(unicode_prop_Emoji_Modifier_table),
+    countof(unicode_prop_Emoji_Modifier_Base_table),
+    countof(unicode_prop_Emoji_Presentation_table),
+    countof(unicode_prop_Extended_Pictographic_table),
+    countof(unicode_prop_Default_Ignorable_Code_Point_table),
+    countof(unicode_prop_ID_Start_table),
+    countof(unicode_prop_Case_Ignorable_table),
+};
+
+#endif /* CONFIG_ALL_UNICODE */
diff --git a/libunicode.c b/libunicode.c
new file mode 100644
index 0000000..96ff002
--- /dev/null
+++ b/libunicode.c
@@ -0,0 +1,1538 @@
+/*
+ * Unicode utilities
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+
+#include "cutils.h"
+#include "libunicode.h"
+#include "libunicode-table.h"
+
+enum {
+    RUN_TYPE_U,
+    RUN_TYPE_L,
+    RUN_TYPE_UF,
+    RUN_TYPE_LF,
+    RUN_TYPE_UL,
+    RUN_TYPE_LSU,
+    RUN_TYPE_U2L_399_EXT2,
+    RUN_TYPE_UF_D20,
+    RUN_TYPE_UF_D1_EXT,
+    RUN_TYPE_U_EXT,
+    RUN_TYPE_LF_EXT,
+    RUN_TYPE_U_EXT2,
+    RUN_TYPE_L_EXT2,
+    RUN_TYPE_U_EXT3,
+};
+
+/* conv_type:
+   0 = to upper 
+   1 = to lower
+   2 = case folding (= to lower with modifications) 
+*/
+int lre_case_conv(uint32_t *res, uint32_t c, int conv_type)
+{
+    if (c < 128) {
+        if (conv_type) {
+            if (c >= 'A' && c <= 'Z') {
+                c = c - 'A' + 'a';
+            }
+        } else {
+            if (c >= 'a' && c <= 'z') {
+                c = c - 'a' + 'A';
+            }
+        }
+    } else {
+        uint32_t v, code, data, type, len, a, is_lower;
+        int idx, idx_min, idx_max;
+        
+        is_lower = (conv_type != 0);
+        idx_min = 0;
+        idx_max = countof(case_conv_table1) - 1;
+        while (idx_min <= idx_max) {
+            idx = (unsigned)(idx_max + idx_min) / 2;
+            v = case_conv_table1[idx];
+            code = v >> (32 - 17);
+            len = (v >> (32 - 17 - 7)) & 0x7f;
+            if (c < code) {
+                idx_max = idx - 1;
+            } else if (c >= code + len) {
+                idx_min = idx + 1;
+            } else {
+                type = (v >> (32 - 17 - 7 - 4)) & 0xf;
+                data = ((v & 0xf) << 8) | case_conv_table2[idx];
+                switch(type) {
+                case RUN_TYPE_U:
+                case RUN_TYPE_L:
+                case RUN_TYPE_UF:
+                case RUN_TYPE_LF:
+                    if (conv_type == (type & 1) ||
+                        (type >= RUN_TYPE_UF && conv_type == 2)) {
+                        c = c - code + (case_conv_table1[data] >> (32 - 17));
+                    }
+                    break;
+                case RUN_TYPE_UL:
+                    a = c - code;
+                    if ((a & 1) != (1 - is_lower))
+                        break;
+                    c = (a ^ 1) + code;
+                    break;
+                case RUN_TYPE_LSU:
+                    a = c - code;
+                    if (a == 1) {
+                        c += 2 * is_lower - 1;
+                    } else if (a == (1 - is_lower) * 2) {
+                        c += (2 * is_lower - 1) * 2;
+                    }
+                    break;
+                case RUN_TYPE_U2L_399_EXT2:
+                    if (!is_lower) {
+                        res[0] = c - code + case_conv_ext[data >> 6];
+                        res[1] = 0x399;
+                        return 2;
+                    } else {
+                        c = c - code + case_conv_ext[data & 0x3f];
+                    }
+                    break;
+                case RUN_TYPE_UF_D20:
+                    if (conv_type == 1)
+                        break;
+                    c = data + (conv_type == 2) * 0x20;
+                    break;
+                case RUN_TYPE_UF_D1_EXT:
+                    if (conv_type == 1)
+                        break;
+                    c = case_conv_ext[data] + (conv_type == 2);
+                    break;
+                case RUN_TYPE_U_EXT:
+                case RUN_TYPE_LF_EXT:
+                    if (is_lower != (type - RUN_TYPE_U_EXT))
+                        break;
+                    c = case_conv_ext[data];
+                    break;
+                case RUN_TYPE_U_EXT2:
+                case RUN_TYPE_L_EXT2:
+                    if (conv_type != (type - RUN_TYPE_U_EXT2))
+                        break;
+                    res[0] = c - code + case_conv_ext[data >> 6];
+                    res[1] = case_conv_ext[data & 0x3f];
+                    return 2;
+                default:
+                case RUN_TYPE_U_EXT3:
+                    if (conv_type != 0)
+                        break;
+                    res[0] = case_conv_ext[data >> 8];
+                    res[1] = case_conv_ext[(data >> 4) & 0xf];
+                    res[2] = case_conv_ext[data & 0xf];
+                    return 3;
+                }
+                break;
+            }
+        }
+    }
+    res[0] = c;
+    return 1;
+}
+
+static uint32_t get_le24(const uint8_t *ptr)
+{
+#if defined(__x86__) || defined(__x86_64__)
+    return *(uint16_t *)ptr | (ptr[2] << 16);
+#else
+    return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16);
+#endif
+}
+
+#define UNICODE_INDEX_BLOCK_LEN 32
+
+/* return -1 if not in table, otherwise the offset in the block */
+static int get_index_pos(uint32_t *pcode, uint32_t c,
+                         const uint8_t *index_table, int index_table_len)
+{
+    uint32_t code, v;
+    int idx_min, idx_max, idx;
+
+    idx_min = 0;
+    v = get_le24(index_table);
+    code = v & ((1 << 21) - 1);
+    if (c < code) {
+        *pcode = 0;
+        return 0;
+    }
+    idx_max = index_table_len - 1;
+    code = get_le24(index_table + idx_max * 3);
+    if (c >= code)
+        return -1;
+    /* invariant: tab[idx_min] <= c < tab2[idx_max] */
+    while ((idx_max - idx_min) > 1) {
+        idx = (idx_max + idx_min) / 2;
+        v = get_le24(index_table + idx * 3);
+        code = v & ((1 << 21) - 1);
+        if (c < code) {
+            idx_max = idx;
+        } else {
+            idx_min = idx;
+        }
+    }
+    v = get_le24(index_table + idx_min * 3);
+    *pcode = v & ((1 << 21) - 1);
+    return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21);
+}
+
+static BOOL lre_is_in_table(uint32_t c, const uint8_t *table,
+                            const uint8_t *index_table, int index_table_len)
+{
+    uint32_t code, b, bit;
+    int pos;
+    const uint8_t *p;
+    
+    pos = get_index_pos(&code, c, index_table, index_table_len);
+    if (pos < 0)
+        return FALSE; /* outside the table */
+    p = table + pos;
+    bit = 0;
+    for(;;) {
+        b = *p++;
+        if (b < 64) {
+            code += (b >> 3) + 1;
+            if (c < code)
+                return bit;
+            bit ^= 1;
+            code += (b & 7) + 1;
+        } else if (b >= 0x80) {
+            code += b - 0x80 + 1;
+        } else if (b < 0x60) {
+            code += (((b - 0x40) << 8) | p[0]) + 1;
+            p++;
+        } else {
+            code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1;
+            p += 2;
+        }
+        if (c < code)
+            return bit;
+        bit ^= 1;
+    }
+}
+
+BOOL lre_is_cased(uint32_t c)
+{
+    uint32_t v, code, len;
+    int idx, idx_min, idx_max;
+        
+    idx_min = 0;
+    idx_max = countof(case_conv_table1) - 1;
+    while (idx_min <= idx_max) {
+        idx = (unsigned)(idx_max + idx_min) / 2;
+        v = case_conv_table1[idx];
+        code = v >> (32 - 17);
+        len = (v >> (32 - 17 - 7)) & 0x7f;
+        if (c < code) {
+            idx_max = idx - 1;
+        } else if (c >= code + len) {
+            idx_min = idx + 1;
+        } else {
+            return TRUE;
+        }
+    }
+    return lre_is_in_table(c, unicode_prop_Cased1_table,
+                           unicode_prop_Cased1_index,
+                           sizeof(unicode_prop_Cased1_index) / 3);
+}
+
+BOOL lre_is_case_ignorable(uint32_t c)
+{
+    return lre_is_in_table(c, unicode_prop_Case_Ignorable_table,
+                           unicode_prop_Case_Ignorable_index,
+                           sizeof(unicode_prop_Case_Ignorable_index) / 3);
+}
+
+/* character range */
+
+static __maybe_unused void cr_dump(CharRange *cr)
+{
+    int i;
+    for(i = 0; i < cr->len; i++)
+        printf("%d: 0x%04x\n", i, cr->points[i]);
+}
+
+static void *cr_default_realloc(void *opaque, void *ptr, size_t size)
+{
+    return realloc(ptr, size);
+}
+
+void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func)
+{
+    cr->len = cr->size = 0;
+    cr->points = NULL;
+    cr->mem_opaque = mem_opaque;
+    cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc;
+}
+
+void cr_free(CharRange *cr)
+{
+    cr->realloc_func(cr->mem_opaque, cr->points, 0);
+}
+
+int cr_realloc(CharRange *cr, int size)
+{
+    int new_size;
+    uint32_t *new_buf;
+    
+    if (size > cr->size) {
+        new_size = max_int(size, cr->size * 3 / 2);
+        new_buf = cr->realloc_func(cr->mem_opaque, cr->points,
+                                   new_size * sizeof(cr->points[0]));
+        if (!new_buf)
+            return -1;
+        cr->points = new_buf;
+        cr->size = new_size;
+    }
+    return 0;
+}
+
+int cr_copy(CharRange *cr, const CharRange *cr1)
+{
+    if (cr_realloc(cr, cr1->len))
+        return -1;
+    memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len);
+    cr->len = cr1->len;
+    return 0;
+}
+
+/* merge consecutive intervals and remove empty intervals */
+static void cr_compress(CharRange *cr)
+{
+    int i, j, k, len;
+    uint32_t *pt;
+    
+    pt = cr->points;
+    len = cr->len;
+    i = 0;
+    j = 0;
+    k = 0;
+    while ((i + 1) < len) {
+        if (pt[i] == pt[i + 1]) {
+            /* empty interval */
+            i += 2;
+        } else {
+            j = i;
+            while ((j + 3) < len && pt[j + 1] == pt[j + 2])
+                j += 2;
+            /* just copy */
+            pt[k] = pt[i];
+            pt[k + 1] = pt[j + 1];
+            k += 2;
+            i = j + 2;
+        }
+    }
+    cr->len = k;
+}
+
+/* union or intersection */
+int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len,
+          const uint32_t *b_pt, int b_len, int op)
+{
+    int a_idx, b_idx, is_in;
+    uint32_t v;
+    
+    a_idx = 0;
+    b_idx = 0;
+    for(;;) {
+        /* get one more point from a or b in increasing order */
+        if (a_idx < a_len && b_idx < b_len) {
+            if (a_pt[a_idx] < b_pt[b_idx]) {
+                goto a_add;
+            } else if (a_pt[a_idx] == b_pt[b_idx]) {
+                v = a_pt[a_idx];
+                a_idx++;
+                b_idx++;
+            } else {
+                goto b_add;
+            }
+        } else if (a_idx < a_len) {
+        a_add:
+            v = a_pt[a_idx++];
+        } else if (b_idx < b_len) {
+        b_add:
+            v = b_pt[b_idx++];
+        } else {
+            break;
+        }
+        /* add the point if the in/out status changes */
+        switch(op) {
+        case CR_OP_UNION:
+            is_in = (a_idx & 1) | (b_idx & 1);
+            break;
+        case CR_OP_INTER:
+            is_in = (a_idx & 1) & (b_idx & 1);
+            break;
+        case CR_OP_XOR:
+            is_in = (a_idx & 1) ^ (b_idx & 1);
+            break;
+        default:
+            abort();
+        }
+        if (is_in != (cr->len & 1)) {
+            if (cr_add_point(cr, v))
+                return -1;
+        }
+    }
+    cr_compress(cr);
+    return 0;
+}
+
+int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len)
+{
+    CharRange a = *cr;
+    int ret;
+    cr->len = 0;
+    cr->size = 0;
+    cr->points = NULL;
+    ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION);
+    cr_free(&a);
+    return ret;
+}
+
+int cr_invert(CharRange *cr)
+{
+    int len;
+    len = cr->len;
+    if (cr_realloc(cr, len + 2))
+        return -1;
+    memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0]));
+    cr->points[0] = 0;
+    cr->points[len + 1] = UINT32_MAX;
+    cr->len = len + 2;
+    cr_compress(cr);
+    return 0;
+}
+
+#ifdef CONFIG_ALL_UNICODE
+
+BOOL lre_is_id_start(uint32_t c)
+{
+    return lre_is_in_table(c, unicode_prop_ID_Start_table,
+                           unicode_prop_ID_Start_index,
+                           sizeof(unicode_prop_ID_Start_index) / 3);
+}
+
+BOOL lre_is_id_continue(uint32_t c)
+{
+    return lre_is_id_start(c) ||
+        lre_is_in_table(c, unicode_prop_ID_Continue1_table,
+                        unicode_prop_ID_Continue1_index,
+                        sizeof(unicode_prop_ID_Continue1_index) / 3);
+}
+
+#define UNICODE_DECOMP_LEN_MAX 18
+
+typedef enum {
+    DECOMP_TYPE_C1, /* 16 bit char */
+    DECOMP_TYPE_L1, /* 16 bit char table */
+    DECOMP_TYPE_L2,
+    DECOMP_TYPE_L3,
+    DECOMP_TYPE_L4,
+    DECOMP_TYPE_L5, /* XXX: not used */
+    DECOMP_TYPE_L6, /* XXX: could remove */
+    DECOMP_TYPE_L7, /* XXX: could remove */
+    DECOMP_TYPE_LL1, /* 18 bit char table */
+    DECOMP_TYPE_LL2,
+    DECOMP_TYPE_S1, /* 8 bit char table */
+    DECOMP_TYPE_S2,
+    DECOMP_TYPE_S3,
+    DECOMP_TYPE_S4,
+    DECOMP_TYPE_S5,
+    DECOMP_TYPE_I1, /* increment 16 bit char value */
+    DECOMP_TYPE_I2_0,
+    DECOMP_TYPE_I2_1,
+    DECOMP_TYPE_I3_1,
+    DECOMP_TYPE_I3_2,
+    DECOMP_TYPE_I4_1,
+    DECOMP_TYPE_I4_2,
+    DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */
+    DECOMP_TYPE_B2,
+    DECOMP_TYPE_B3,
+    DECOMP_TYPE_B4,
+    DECOMP_TYPE_B5,
+    DECOMP_TYPE_B6,
+    DECOMP_TYPE_B7,
+    DECOMP_TYPE_B8,
+    DECOMP_TYPE_B18,
+    DECOMP_TYPE_LS2,
+    DECOMP_TYPE_PAT3,
+    DECOMP_TYPE_S2_UL,
+    DECOMP_TYPE_LS2_UL,
+} DecompTypeEnum;
+
+static uint32_t unicode_get_short_code(uint32_t c)
+{
+    static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 };
+
+    if (c < 0x80)
+        return c;
+    else if (c < 0x80 + 0x50)
+        return c - 0x80 + 0x300;
+    else
+        return unicode_short_table[c - 0x80 - 0x50];
+}
+
+static uint32_t unicode_get_lower_simple(uint32_t c)
+{
+    if (c < 0x100 || (c >= 0x410 && c <= 0x42f))
+        c += 0x20;
+    else
+        c++;
+    return c;
+}
+
+static uint16_t unicode_get16(const uint8_t *p)
+{
+    return p[0] | (p[1] << 8);
+}
+
+static int unicode_decomp_entry(uint32_t *res, uint32_t c,
+                                int idx, uint32_t code, uint32_t len,
+                                uint32_t type)
+{
+    uint32_t c1;
+    int l, i, p;
+    const uint8_t *d;
+
+    if (type == DECOMP_TYPE_C1) {
+        res[0] = unicode_decomp_table2[idx];
+        return 1;
+    } else {
+        d = unicode_decomp_data + unicode_decomp_table2[idx];
+        switch(type) {
+        case DECOMP_TYPE_L1 ... DECOMP_TYPE_L7:
+            l = type - DECOMP_TYPE_L1 + 1;
+            d += (c - code) * l * 2;
+            for(i = 0; i < l; i++) {
+                if ((res[i] = unicode_get16(d + 2 * i)) == 0)
+                    return 0;
+            }
+            return l;
+        case DECOMP_TYPE_LL1 ... DECOMP_TYPE_LL2:
+            {
+                uint32_t k, p;
+                l = type - DECOMP_TYPE_LL1 + 1;
+                k = (c - code) * l;
+                p = len * l * 2;
+                for(i = 0; i < l; i++) {
+                    c1 = unicode_get16(d + 2 * k) |
+                        (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16);
+                    if (!c1)
+                        return 0;
+                    res[i] = c1;
+                    k++;
+                }
+            }
+            return l;
+        case DECOMP_TYPE_S1 ... DECOMP_TYPE_S5:
+            l = type - DECOMP_TYPE_S1 + 1;
+            d += (c - code) * l;
+            for(i = 0; i < l; i++) {
+                if ((res[i] = unicode_get_short_code(d[i])) == 0)
+                    return 0;
+            }
+            return l;
+        case DECOMP_TYPE_I1:
+            l = 1;
+            p = 0;
+            goto decomp_type_i;
+        case DECOMP_TYPE_I2_0:
+        case DECOMP_TYPE_I2_1:
+        case DECOMP_TYPE_I3_1:
+        case DECOMP_TYPE_I3_2:
+        case DECOMP_TYPE_I4_1:
+        case DECOMP_TYPE_I4_2:
+            l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1);
+            p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2);
+        decomp_type_i:
+            for(i = 0; i < l; i++) {
+                c1 = unicode_get16(d + 2 * i);
+                if (i == p)
+                    c1 += c - code;
+                res[i] = c1;
+            }
+            return l;
+        case DECOMP_TYPE_B18:
+            l = 18;
+            goto decomp_type_b;
+        case DECOMP_TYPE_B1 ... DECOMP_TYPE_B8:
+            l = type - DECOMP_TYPE_B1 + 1;
+        decomp_type_b:
+            {
+                uint32_t c_min;
+                c_min = unicode_get16(d);
+                d += 2 + (c - code) * l;
+                for(i = 0; i < l; i++) {
+                    c1 = d[i];
+                    if (c1 == 0xff)
+                        c1 = 0x20;
+                    else
+                        c1 += c_min;
+                    res[i] = c1;
+                }
+            }
+            return l;
+        case DECOMP_TYPE_LS2:
+            d += (c - code) * 3;
+            if (!(res[0] = unicode_get16(d)))
+                return 0;
+            res[1] = unicode_get_short_code(d[2]);
+            return 2;
+        case DECOMP_TYPE_PAT3:
+            res[0] = unicode_get16(d);
+            res[2] = unicode_get16(d + 2);
+            d += 4 + (c - code) * 2;
+            res[1] = unicode_get16(d);
+            return 3;
+        case DECOMP_TYPE_S2_UL:
+        case DECOMP_TYPE_LS2_UL:
+            c1 = c - code;
+            if (type == DECOMP_TYPE_S2_UL) {
+                d += c1 & ~1;
+                c = unicode_get_short_code(*d);
+                d++;
+            } else {
+                d += (c1 >> 1) * 3;
+                c = unicode_get16(d);
+                d += 2;
+            }
+            if (c1 & 1)
+                c = unicode_get_lower_simple(c);
+            res[0] = c;
+            res[1] = unicode_get_short_code(*d);
+            return 2;
+        }
+    }
+    return 0;
+}
+
+
+/* return the length of the decomposition (length <=
+   UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */
+static int unicode_decomp_char(uint32_t *res, uint32_t c, BOOL is_compat1)
+{
+    uint32_t v, type, is_compat, code, len;
+    int idx_min, idx_max, idx;
+    
+    idx_min = 0;
+    idx_max = countof(unicode_decomp_table1) - 1;
+    while (idx_min <= idx_max) {
+        idx = (idx_max + idx_min) / 2;
+        v = unicode_decomp_table1[idx];
+        code = v >> (32 - 18);
+        len = (v >> (32 - 18 - 7)) & 0x7f;
+        //        printf("idx=%d code=%05x len=%d\n", idx, code, len);
+        if (c < code) {
+            idx_max = idx - 1;
+        } else if (c >= code + len) {
+            idx_min = idx + 1;
+        } else {
+            is_compat = v & 1;
+            if (is_compat1 < is_compat)
+                break;
+            type = (v >> (32 - 18 - 7 - 6)) & 0x3f;
+            return unicode_decomp_entry(res, c, idx, code, len, type);
+        }
+    }
+    return 0;
+}
+
+/* return 0 if no pair found */
+static int unicode_compose_pair(uint32_t c0, uint32_t c1)
+{
+    uint32_t code, len, type, v, idx1, d_idx, d_offset, ch;
+    int idx_min, idx_max, idx, d;
+    uint32_t pair[2];
+    
+    idx_min = 0;
+    idx_max = countof(unicode_comp_table) - 1;
+    while (idx_min <= idx_max) {
+        idx = (idx_max + idx_min) / 2;
+        idx1 = unicode_comp_table[idx];
+
+        /* idx1 represent an entry of the decomposition table */
+        d_idx = idx1 >> 6;
+        d_offset = idx1 & 0x3f;
+        v = unicode_decomp_table1[d_idx];
+        code = v >> (32 - 18);
+        len = (v >> (32 - 18 - 7)) & 0x7f;
+        type = (v >> (32 - 18 - 7 - 6)) & 0x3f;
+        ch = code + d_offset;
+        unicode_decomp_entry(pair, ch, d_idx, code, len, type);
+        d = c0 - pair[0];
+        if (d == 0)
+            d = c1 - pair[1];
+        if (d < 0) {
+            idx_max = idx - 1;
+        } else if (d > 0) {
+            idx_min = idx + 1;
+        } else {
+            return ch;
+        }
+    }
+    return 0;
+}
+
+/* return the combining class of character c (between 0 and 255) */
+static int unicode_get_cc(uint32_t c)
+{
+    uint32_t code, n, type, cc, c1, b;
+    int pos;
+    const uint8_t *p;
+    
+    pos = get_index_pos(&code, c,
+                        unicode_cc_index, sizeof(unicode_cc_index) / 3);
+    if (pos < 0)
+        return 0;
+    p = unicode_cc_table + pos;
+    for(;;) {
+        b = *p++;
+        type = b >> 6;
+        n = b & 0x3f;
+        if (n < 48) {
+        } else if (n < 56) {
+            n = (n - 48) << 8;
+            n |= *p++;
+            n += 48;
+        } else {
+            n = (n - 56) << 8;
+            n |= *p++ << 8;
+            n |= *p++;
+            n += 48 + (1 << 11);
+        }
+        if (type <= 1)
+            p++;
+        c1 = code + n + 1;
+        if (c < c1) {
+            switch(type) {
+            case 0:
+                cc = p[-1];
+                break;
+            case 1:
+                cc = p[-1] + c - code;
+                break;
+            case 2:
+                cc = 0;
+                break;
+            default:
+            case 3:
+                cc = 230;
+                break;
+            }
+            return cc;
+        }
+        code = c1;
+    }
+}
+
+static void sort_cc(int *buf, int len)
+{
+    int i, j, k, cc, cc1, start, ch1;
+    
+    for(i = 0; i < len; i++) {
+        cc = unicode_get_cc(buf[i]);
+        if (cc != 0) {
+            start = i;
+            j = i + 1;
+            while (j < len) {
+                ch1 = buf[j];
+                cc1 = unicode_get_cc(ch1);
+                if (cc1 == 0)
+                    break;
+                k = j - 1;
+                while (k >= start) {
+                    if (unicode_get_cc(buf[k]) <= cc1)
+                        break;
+                    buf[k + 1] = buf[k];
+                    k--;
+                }
+                buf[k + 1] = ch1;
+                j++;
+            }
+#if 0
+            printf("cc:");
+            for(k = start; k < j; k++) {
+                printf(" %3d", unicode_get_cc(buf[k]));
+            }
+            printf("\n");
+#endif
+            i = j;
+        }
+    }
+}
+
+static void to_nfd_rec(DynBuf *dbuf,
+                       const int *src, int src_len, int is_compat)
+{
+    uint32_t c, v;
+    int i, l;
+    uint32_t res[UNICODE_DECOMP_LEN_MAX];
+    
+    for(i = 0; i < src_len; i++) {
+        c = src[i];
+        if (c >= 0xac00 && c < 0xd7a4) {
+            /* Hangul decomposition */
+            c -= 0xac00;
+            dbuf_put_u32(dbuf, 0x1100 + c / 588);
+            dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28);
+            v = c % 28;
+            if (v != 0)
+                dbuf_put_u32(dbuf, 0x11a7 + v);
+        } else {
+            l = unicode_decomp_char(res, c, is_compat);
+            if (l) {
+                to_nfd_rec(dbuf, (int *)res, l, is_compat);
+            } else {
+                dbuf_put_u32(dbuf, c);
+            }
+        }
+    }
+}
+
+/* return 0 if not found */
+static int compose_pair(uint32_t c0, uint32_t c1)
+{
+    /* Hangul composition */
+    if (c0 >= 0x1100 && c0 < 0x1100 + 19 &&
+        c1 >= 0x1161 && c1 < 0x1161 + 21) {
+        return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28;
+    } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 &&
+               (c0 - 0xac00) % 28 == 0 &&
+               c1 >= 0x11a7 && c1 < 0x11a7 + 28) {
+        return c0 + c1 - 0x11a7;
+    } else {
+        return unicode_compose_pair(c0, c1);
+    }
+}
+
+int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len,
+                      UnicodeNormalizationEnum n_type,
+                      void *opaque, DynBufReallocFunc *realloc_func)
+{
+    int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len;
+    BOOL is_compat;
+    DynBuf dbuf_s, *dbuf = &dbuf_s;
+    
+    is_compat = n_type >> 1;
+
+    dbuf_init2(dbuf, opaque, realloc_func);
+    if (dbuf_realloc(dbuf, sizeof(int) * src_len))
+        goto fail;
+
+    /* common case: latin1 is unaffected by NFC */
+    if (n_type == UNICODE_NFC) {
+        for(i = 0; i < src_len; i++) {
+            if (src[i] >= 0x100)
+                goto not_latin1;
+        }
+        buf = (int *)dbuf->buf;
+        memcpy(buf, src, src_len * sizeof(int));
+        *pdst = (uint32_t *)buf;
+        return src_len;
+    not_latin1: ;
+    }
+
+    to_nfd_rec(dbuf, (const int *)src, src_len, is_compat);
+    if (dbuf_error(dbuf)) {
+    fail:
+        *pdst = NULL;
+        return -1;
+    }
+    buf = (int *)dbuf->buf;
+    buf_len = dbuf->size / sizeof(int);
+        
+    sort_cc(buf, buf_len);
+    
+    if (buf_len <= 1 || (n_type & 1) != 0) {
+        /* NFD / NFKD */
+        *pdst = (uint32_t *)buf;
+        return buf_len;
+    }
+    
+    i = 1;
+    out_len = 1;
+    while (i < buf_len) {
+        /* find the starter character and test if it is blocked from
+           the character at 'i' */
+        last_cc = unicode_get_cc(buf[i]);
+        starter_pos = out_len - 1;
+        while (starter_pos >= 0) {
+            cc = unicode_get_cc(buf[starter_pos]);
+            if (cc == 0)
+                break;
+            if (cc >= last_cc)
+                goto next;
+            last_cc = 256;
+            starter_pos--;
+        }
+        if (starter_pos >= 0 &&
+            (p = compose_pair(buf[starter_pos], buf[i])) != 0) {
+            buf[starter_pos] = p;
+            i++;
+        } else {
+        next:
+            buf[out_len++] = buf[i++];
+        }
+    }
+    *pdst = (uint32_t *)buf;
+    return out_len;
+}
+
+/* char ranges for various unicode properties */
+
+static int unicode_find_name(const char *name_table, const char *name)
+{
+    const char *p, *r;
+    int pos;
+    size_t name_len, len;
+    
+    p = name_table;
+    pos = 0;
+    name_len = strlen(name);
+    while (*p) {
+        for(;;) {
+            r = strchr(p, ',');
+            if (!r)
+                len = strlen(p);
+            else
+                len = r - p;
+            if (len == name_len && !memcmp(p, name, name_len))
+                return pos;
+            p += len + 1;
+            if (!r)
+                break;
+        }
+        pos++;
+    }
+    return -1;
+}
+
+/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2
+   if not found */
+int unicode_script(CharRange *cr,
+                   const char *script_name, BOOL is_ext)
+{
+    int script_idx;
+    const uint8_t *p, *p_end;
+    uint32_t c, c1, b, n, v, v_len, i, type;
+    CharRange cr1_s, *cr1;
+    CharRange cr2_s, *cr2 = &cr2_s;
+    BOOL is_common;
+    
+    script_idx = unicode_find_name(unicode_script_name_table, script_name);
+    if (script_idx < 0)
+        return -2;
+    /* Note: we remove the "Unknown" Script */
+    script_idx += UNICODE_SCRIPT_Unknown + 1;
+        
+    is_common = (script_idx == UNICODE_SCRIPT_Common ||
+                 script_idx == UNICODE_SCRIPT_Inherited);
+    if (is_ext) {
+        cr1 = &cr1_s;
+        cr_init(cr1, cr->mem_opaque, cr->realloc_func);
+        cr_init(cr2, cr->mem_opaque, cr->realloc_func);
+    } else {
+        cr1 = cr;
+    }
+
+    p = unicode_script_table;
+    p_end = unicode_script_table + countof(unicode_script_table);
+    c = 0;
+    while (p < p_end) {
+        b = *p++;
+        type = b >> 7;
+        n = b & 0x7f;
+        if (n < 96) {
+        } else if (n < 112) {
+            n = (n - 96) << 8;
+            n |= *p++;
+            n += 96;
+        } else {
+            n = (n - 112) << 16;
+            n |= *p++ << 8;
+            n |= *p++;
+            n += 96 + (1 << 12);
+        }
+        if (type == 0)
+            v = 0;
+        else
+            v = *p++;
+        c1 = c + n + 1;
+        if (v == script_idx) {
+            if (cr_add_interval(cr1, c, c1))
+                goto fail;
+        }
+        c = c1;
+    }
+
+    if (is_ext) {
+        /* add the script extensions */
+        p = unicode_script_ext_table;
+        p_end = unicode_script_ext_table + countof(unicode_script_ext_table);
+        c = 0;
+        while (p < p_end) {
+            b = *p++;
+            if (b < 128) {
+                n = b;
+            } else if (b < 128 + 64) {
+                n = (b - 128) << 8;
+                n |= *p++;
+                n += 128;
+            } else {
+                n = (b - 128 - 64) << 16;
+                n |= *p++ << 8;
+                n |= *p++;
+                n += 128 + (1 << 14);
+            }
+            c1 = c + n + 1;
+            v_len = *p++;
+            if (is_common) {
+                if (v_len != 0) {
+                    if (cr_add_interval(cr2, c, c1))
+                        goto fail;
+                }
+            } else {
+                for(i = 0; i < v_len; i++) {
+                    if (p[i] == script_idx) {
+                        if (cr_add_interval(cr2, c, c1))
+                            goto fail;
+                        break;
+                    }
+                }
+            }
+            p += v_len;
+            c = c1;
+        }
+        if (is_common) {
+            /* remove all the characters with script extensions */
+            if (cr_invert(cr2))
+                goto fail;
+            if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len,
+                      CR_OP_INTER))
+                goto fail;
+        } else {
+            if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len,
+                      CR_OP_UNION))
+                goto fail;
+        }
+        cr_free(cr1);
+        cr_free(cr2);
+    }
+    return 0;
+ fail:
+    if (is_ext) {
+        cr_free(cr1);
+        cr_free(cr2);
+    }
+    goto fail;
+}
+
+#define M(id) (1U << UNICODE_GC_ ## id)
+
+static int unicode_general_category1(CharRange *cr, uint32_t gc_mask)
+{
+    const uint8_t *p, *p_end;
+    uint32_t c, c0, b, n, v;
+
+    p = unicode_gc_table;
+    p_end = unicode_gc_table + countof(unicode_gc_table);
+    c = 0;
+    while (p < p_end) {
+        b = *p++;
+        n = b >> 5;
+        v = b & 0x1f;
+        if (n == 7) {
+            n = *p++;
+            if (n < 128) {
+                n += 7;
+            } else if (n < 128 + 64) {
+                n = (n - 128) << 8;
+                n |= *p++;
+                n += 7 + 128;
+            } else {
+                n = (n - 128 - 64) << 16;
+                n |= *p++ << 8;
+                n |= *p++;
+                n += 7 + 128 + (1 << 14);
+            }
+        }
+        c0 = c;
+        c += n + 1;
+        if (v == 31) {
+            /* run of Lu / Ll */
+            b = gc_mask & (M(Lu) | M(Ll));
+            if (b != 0) {
+                if (b == (M(Lu) | M(Ll))) {
+                    goto add_range;
+                } else {
+                    c0 += ((gc_mask & M(Ll)) != 0);
+                    for(; c0 < c; c0 += 2) {
+                        if (cr_add_interval(cr, c0, c0 + 1))
+                            return -1;
+                    }
+                }
+            }
+        } else if ((gc_mask >> v) & 1) {
+        add_range:
+            if (cr_add_interval(cr, c0, c))
+                return -1;
+        }
+    }
+    return 0;
+}
+
+static int unicode_prop1(CharRange *cr, int prop_idx)
+{
+    const uint8_t *p, *p_end;
+    uint32_t c, c0, b, bit;
+
+    p = unicode_prop_table[prop_idx];
+    p_end = p + unicode_prop_len_table[prop_idx];
+    c = 0;
+    bit = 0;
+    while (p < p_end) {
+        c0 = c;
+        b = *p++;
+        if (b < 64) {
+            c += (b >> 3) + 1;
+            if (bit)  {
+                if (cr_add_interval(cr, c0, c))
+                    return -1;
+            }
+            bit ^= 1;
+            c0 = c;
+            c += (b & 7) + 1;
+        } else if (b >= 0x80) {
+            c += b - 0x80 + 1;
+        } else if (b < 0x60) {
+            c += (((b - 0x40) << 8) | p[0]) + 1;
+            p++;
+        } else {
+            c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1;
+            p += 2;
+        }
+        if (bit)  {
+            if (cr_add_interval(cr, c0, c))
+                return -1;
+        }
+        bit ^= 1;
+    }
+    return 0;
+}
+
+#define CASE_U (1 << 0)
+#define CASE_L (1 << 1)
+#define CASE_F (1 << 2)
+
+/* use the case conversion table to generate range of characters.
+   CASE_U: set char if modified by uppercasing,
+   CASE_L: set char if modified by lowercasing,
+   CASE_F: set char if modified by case folding,
+ */
+static int unicode_case1(CharRange *cr, int case_mask)
+{
+#define MR(x) (1 << RUN_TYPE_ ## x)
+    const uint32_t tab_run_mask[3] = {
+        MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) |
+        MR(UF_D1_EXT) | MR(U_EXT) | MR(U_EXT2) | MR(U_EXT3),
+
+        MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(L_EXT2),
+
+        MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT),
+    };
+#undef MR
+    uint32_t mask, v, code, type, len, i, idx;
+
+    if (case_mask == 0)
+        return 0;
+    mask = 0;
+    for(i = 0; i < 3; i++) {
+        if ((case_mask >> i) & 1)
+            mask |= tab_run_mask[i];
+    }
+    for(idx = 0; idx < countof(case_conv_table1); idx++) {
+        v = case_conv_table1[idx];
+        type = (v >> (32 - 17 - 7 - 4)) & 0xf;
+        code = v >> (32 - 17);
+        len = (v >> (32 - 17 - 7)) & 0x7f;
+        if ((mask >> type) & 1) {
+            //            printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1);
+            switch(type) {
+            case RUN_TYPE_UL:
+                if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F)))
+                    goto def_case;
+                code += ((case_mask & CASE_U) != 0);
+                for(i = 0; i < len; i += 2) {
+                    if (cr_add_interval(cr, code + i, code + i + 1))
+                        return -1;
+                }
+                break;
+            case RUN_TYPE_LSU:
+                if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F)))
+                    goto def_case;
+                if (!(case_mask & CASE_U)) {
+                    if (cr_add_interval(cr, code, code + 1))
+                        return -1;
+                }
+                if (cr_add_interval(cr, code + 1, code + 2))
+                    return -1;
+                if (case_mask & CASE_U) {
+                    if (cr_add_interval(cr, code + 2, code + 3))
+                        return -1;
+                }
+                break;
+            default:
+            def_case:
+                if (cr_add_interval(cr, code, code + len))
+                    return -1;
+                break;
+            }
+        }
+    }
+    return 0;
+}
+        
+typedef enum {
+    POP_GC,
+    POP_PROP,
+    POP_CASE,
+    POP_UNION,
+    POP_INTER,
+    POP_XOR,
+    POP_INVERT,
+    POP_END,
+} PropOPEnum;
+
+#define POP_STACK_LEN_MAX 4
+
+static int unicode_prop_ops(CharRange *cr, ...)
+{
+    va_list ap;
+    CharRange stack[POP_STACK_LEN_MAX];
+    int stack_len, op, ret, i;
+    uint32_t a;
+    
+    va_start(ap, cr);
+    stack_len = 0;
+    for(;;) {
+        op = va_arg(ap, int);
+        switch(op) {
+        case POP_GC:
+            assert(stack_len < POP_STACK_LEN_MAX);
+            a = va_arg(ap, int);
+            cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func);
+            if (unicode_general_category1(&stack[stack_len - 1], a))
+                goto fail;
+            break;
+        case POP_PROP:
+            assert(stack_len < POP_STACK_LEN_MAX);
+            a = va_arg(ap, int);
+            cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func);
+            if (unicode_prop1(&stack[stack_len - 1], a))
+                goto fail;
+            break;
+        case POP_CASE:
+            assert(stack_len < POP_STACK_LEN_MAX);
+            a = va_arg(ap, int);
+            cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func);
+            if (unicode_case1(&stack[stack_len - 1], a))
+                goto fail;
+            break;
+        case POP_UNION:
+        case POP_INTER:
+        case POP_XOR:
+            {
+                CharRange *cr1, *cr2, *cr3;
+                assert(stack_len >= 2);
+                assert(stack_len < POP_STACK_LEN_MAX);
+                cr1 = &stack[stack_len - 2];
+                cr2 = &stack[stack_len - 1];
+                cr3 = &stack[stack_len++];
+                cr_init(cr3, cr->mem_opaque, cr->realloc_func);
+                if (cr_op(cr3, cr1->points, cr1->len,
+                          cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION))
+                    goto fail;
+                cr_free(cr1);
+                cr_free(cr2);
+                *cr1 = *cr3;
+                stack_len -= 2;
+            }
+            break;
+        case POP_INVERT:
+            assert(stack_len >= 1);
+            if (cr_invert(&stack[stack_len - 1]))
+                goto fail;
+            break;
+        case POP_END:
+            goto done;
+        default:
+            abort();
+        }
+    }
+ done:
+    assert(stack_len == 1);
+    ret = cr_copy(cr, &stack[0]);
+    cr_free(&stack[0]);
+    return ret;
+ fail:
+    for(i = 0; i < stack_len; i++)
+        cr_free(&stack[i]);
+    return -1;
+}
+
+static const uint32_t unicode_gc_mask_table[] = {
+    M(Lu) | M(Ll) | M(Lt), /* LC */
+    M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */
+    M(Mn) | M(Mc) | M(Me), /* M */
+    M(Nd) | M(Nl) | M(No), /* N */
+    M(Sm) | M(Sc) | M(Sk) | M(So), /* S */
+    M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */
+    M(Zs) | M(Zl) | M(Zp), /* Z */
+    M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */
+};
+
+/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2
+   if not found */
+int unicode_general_category(CharRange *cr, const char *gc_name)
+{
+    int gc_idx;
+    uint32_t gc_mask;
+    
+    gc_idx = unicode_find_name(unicode_gc_name_table, gc_name);
+    if (gc_idx < 0)
+        return -2;
+    if (gc_idx <= UNICODE_GC_Co) {
+        gc_mask = (uint64_t)1 << gc_idx;
+    } else {
+        gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC];
+    }
+    return unicode_general_category1(cr, gc_mask);
+}
+
+
+/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2
+   if not found */
+int unicode_prop(CharRange *cr, const char *prop_name)
+{
+    int prop_idx, ret;
+    
+    prop_idx = unicode_find_name(unicode_prop_name_table, prop_name);
+    if (prop_idx < 0)
+        return -2;
+    prop_idx += UNICODE_PROP_ASCII_Hex_Digit;
+
+    ret = 0;
+    switch(prop_idx) {
+    case UNICODE_PROP_ASCII:
+        if (cr_add_interval(cr, 0x00, 0x7f + 1))
+            return -1;
+        break;
+    case UNICODE_PROP_Any:
+        if (cr_add_interval(cr, 0x00000, 0x10ffff + 1))
+            return -1;
+        break;
+    case UNICODE_PROP_Assigned:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Cn),
+                               POP_INVERT,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Math:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Sm),
+                               POP_PROP, UNICODE_PROP_Other_Math,
+                               POP_UNION,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Lowercase:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Ll),
+                               POP_PROP, UNICODE_PROP_Other_Lowercase,
+                               POP_UNION,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Uppercase:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu),
+                               POP_PROP, UNICODE_PROP_Other_Uppercase,
+                               POP_UNION,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Cased:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu) | M(Ll) | M(Lt),
+                               POP_PROP, UNICODE_PROP_Other_Uppercase,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Other_Lowercase,
+                               POP_UNION,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Alphabetic:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl),
+                               POP_PROP, UNICODE_PROP_Other_Uppercase,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Other_Lowercase,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Other_Alphabetic,
+                               POP_UNION,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Grapheme_Base:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn),
+                               POP_PROP, UNICODE_PROP_Other_Grapheme_Extend,
+                               POP_UNION,
+                               POP_INVERT,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Grapheme_Extend:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Me) | M(Mn),
+                               POP_PROP, UNICODE_PROP_Other_Grapheme_Extend,
+                               POP_UNION,
+                               POP_END);
+        break;
+    case UNICODE_PROP_XID_Start:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl),
+                               POP_PROP, UNICODE_PROP_Other_ID_Start,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Pattern_Syntax,
+                               POP_PROP, UNICODE_PROP_Pattern_White_Space,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_XID_Start1,
+                               POP_UNION,
+                               POP_INVERT,
+                               POP_INTER,
+                               POP_END);
+        break;
+    case UNICODE_PROP_XID_Continue:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) |
+                               M(Mn) | M(Mc) | M(Nd) | M(Pc),
+                               POP_PROP, UNICODE_PROP_Other_ID_Start,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Other_ID_Continue,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Pattern_Syntax,
+                               POP_PROP, UNICODE_PROP_Pattern_White_Space,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_XID_Continue1,
+                               POP_UNION,
+                               POP_INVERT,
+                               POP_INTER,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Changes_When_Uppercased:
+        ret = unicode_case1(cr, CASE_U);
+        break;
+    case UNICODE_PROP_Changes_When_Lowercased:
+        ret = unicode_case1(cr, CASE_L);
+        break;
+    case UNICODE_PROP_Changes_When_Casemapped:
+        ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F);
+        break;
+    case UNICODE_PROP_Changes_When_Titlecased:
+        ret = unicode_prop_ops(cr,
+                               POP_CASE, CASE_U,
+                               POP_PROP, UNICODE_PROP_Changes_When_Titlecased1,
+                               POP_XOR,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Changes_When_Casefolded:
+        ret = unicode_prop_ops(cr,
+                               POP_CASE, CASE_F,
+                               POP_PROP, UNICODE_PROP_Changes_When_Casefolded1,
+                               POP_XOR,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Changes_When_NFKC_Casefolded:
+        ret = unicode_prop_ops(cr,
+                               POP_CASE, CASE_F,
+                               POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1,
+                               POP_XOR,
+                               POP_END);
+        break;
+#if 0
+    case UNICODE_PROP_ID_Start:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl),
+                               POP_PROP, UNICODE_PROP_Other_ID_Start,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Pattern_Syntax,
+                               POP_PROP, UNICODE_PROP_Pattern_White_Space,
+                               POP_UNION,
+                               POP_INVERT,
+                               POP_INTER,
+                               POP_END);
+        break;
+    case UNICODE_PROP_ID_Continue:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) |
+                               M(Mn) | M(Mc) | M(Nd) | M(Pc),
+                               POP_PROP, UNICODE_PROP_Other_ID_Start,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Other_ID_Continue,
+                               POP_UNION,
+                               POP_PROP, UNICODE_PROP_Pattern_Syntax,
+                               POP_PROP, UNICODE_PROP_Pattern_White_Space,
+                               POP_UNION,
+                               POP_INVERT,
+                               POP_INTER,
+                               POP_END);
+        break;
+    case UNICODE_PROP_Case_Ignorable:
+        ret = unicode_prop_ops(cr,
+                               POP_GC, M(Mn) | M(Cf) | M(Lm) | M(Sk),
+                               POP_PROP, UNICODE_PROP_Case_Ignorable1,
+                               POP_XOR,
+                               POP_END);
+        break;
+#else
+        /* we use the existing tables */
+    case UNICODE_PROP_ID_Continue:
+        ret = unicode_prop_ops(cr,
+                               POP_PROP, UNICODE_PROP_ID_Start,
+                               POP_PROP, UNICODE_PROP_ID_Continue1,
+                               POP_XOR,
+                               POP_END);
+        break;
+#endif
+    default:
+        if (prop_idx >= countof(unicode_prop_table))
+            return -2;
+        ret = unicode_prop1(cr, prop_idx);
+        break;
+    }
+    return ret;
+}
+
+#endif /* CONFIG_ALL_UNICODE */
diff --git a/libunicode.h b/libunicode.h
new file mode 100644
index 0000000..cfa600a
--- /dev/null
+++ b/libunicode.h
@@ -0,0 +1,124 @@
+/*
+ * Unicode utilities
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef LIBUNICODE_H
+#define LIBUNICODE_H
+
+#include <inttypes.h>
+
+#define LRE_BOOL  int       /* for documentation purposes */
+
+/* define it to include all the unicode tables (40KB larger) */
+#define CONFIG_ALL_UNICODE
+
+#define LRE_CC_RES_LEN_MAX 3
+
+typedef enum {
+    UNICODE_NFC,
+    UNICODE_NFD,
+    UNICODE_NFKC,
+    UNICODE_NFKD,
+} UnicodeNormalizationEnum;
+
+int lre_case_conv(uint32_t *res, uint32_t c, int conv_type);
+LRE_BOOL lre_is_cased(uint32_t c);
+LRE_BOOL lre_is_case_ignorable(uint32_t c);
+
+/* char ranges */
+
+typedef struct {
+    int len; /* in points, always even */
+    int size;
+    uint32_t *points; /* points sorted by increasing value */
+    void *mem_opaque;
+    void *(*realloc_func)(void *opaque, void *ptr, size_t size);
+} CharRange;
+
+typedef enum {
+    CR_OP_UNION,
+    CR_OP_INTER,
+    CR_OP_XOR,
+} CharRangeOpEnum;
+
+void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size));
+void cr_free(CharRange *cr);
+int cr_realloc(CharRange *cr, int size);
+int cr_copy(CharRange *cr, const CharRange *cr1);
+
+static inline int cr_add_point(CharRange *cr, uint32_t v)
+{
+    if (cr->len >= cr->size) {
+        if (cr_realloc(cr, cr->len + 1))
+            return -1;
+    }
+    cr->points[cr->len++] = v;
+    return 0;
+}
+
+static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2)
+{
+    if ((cr->len + 2) > cr->size) {
+        if (cr_realloc(cr, cr->len + 2))
+            return -1;
+    }
+    cr->points[cr->len++] = c1;
+    cr->points[cr->len++] = c2;
+    return 0;
+}
+
+int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len);
+
+static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2)
+{
+    uint32_t b_pt[2];
+    b_pt[0] = c1;
+    b_pt[1] = c2 + 1;
+    return cr_union1(cr, b_pt, 2);
+}
+
+int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len,
+          const uint32_t *b_pt, int b_len, int op);
+
+int cr_invert(CharRange *cr);
+
+#ifdef CONFIG_ALL_UNICODE
+
+LRE_BOOL lre_is_id_start(uint32_t c);
+LRE_BOOL lre_is_id_continue(uint32_t c);
+
+int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len,
+                      UnicodeNormalizationEnum n_type,
+                      void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size));
+
+/* Unicode character range functions */
+
+int unicode_script(CharRange *cr,
+                   const char *script_name, LRE_BOOL is_ext);
+int unicode_general_category(CharRange *cr, const char *gc_name);
+int unicode_prop(CharRange *cr, const char *prop_name);
+
+#endif /* CONFIG_ALL_UNICODE */
+
+#undef LRE_BOOL
+
+#endif /* LIBUNICODE_H */
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..0a1bc5a
--- /dev/null
+++ b/list.h
@@ -0,0 +1,100 @@
+/*
+ * Linux klist like system
+ * 
+ * Copyright (c) 2016-2017 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef LIST_H
+#define LIST_H
+
+#ifndef NULL
+#include <stddef.h>
+#endif
+
+struct list_head {
+    struct list_head *prev;
+    struct list_head *next;
+};
+
+#define LIST_HEAD_INIT(el) { &(el), &(el) }
+
+/* return the pointer of type 'type *' containing 'el' as field 'member' */
+#define list_entry(el, type, member) \
+    ((type *)((uint8_t *)(el) - offsetof(type, member)))
+
+static inline void init_list_head(struct list_head *head)
+{
+    head->prev = head;
+    head->next = head;
+}
+
+/* insert 'el' between 'prev' and 'next' */
+static inline void __list_add(struct list_head *el, 
+                              struct list_head *prev, struct list_head *next)
+{
+    prev->next = el;
+    el->prev = prev;
+    el->next = next;
+    next->prev = el;
+}
+
+/* add 'el' at the head of the list 'head' (= after element head) */
+static inline void list_add(struct list_head *el, struct list_head *head)
+{
+    __list_add(el, head, head->next);
+}
+
+/* add 'el' at the end of the list 'head' (= before element head) */
+static inline void list_add_tail(struct list_head *el, struct list_head *head)
+{
+    __list_add(el, head->prev, head);
+}
+
+static inline void list_del(struct list_head *el)
+{
+    struct list_head *prev, *next;
+    prev = el->prev;
+    next = el->next;
+    prev->next = next;
+    next->prev = prev;
+    el->prev = NULL; /* fail safe */
+    el->next = NULL; /* fail safe */
+}
+
+static inline int list_empty(struct list_head *el)
+{
+    return el->next == el;
+}
+
+#define list_for_each(el, head) \
+  for(el = (head)->next; el != (head); el = el->next)
+
+#define list_for_each_safe(el, el1, head)                \
+    for(el = (head)->next, el1 = el->next; el != (head); \
+        el = el1, el1 = el->next)
+
+#define list_for_each_prev(el, head) \
+  for(el = (head)->prev; el != (head); el = el->prev)
+
+#define list_for_each_prev_safe(el, el1, head)           \
+    for(el = (head)->prev, el1 = el->prev; el != (head); \
+        el = el1, el1 = el->prev)
+
+#endif /* LIST_H */
diff --git a/qjs.c b/qjs.c
new file mode 100644
index 0000000..fbff078
--- /dev/null
+++ b/qjs.c
@@ -0,0 +1,485 @@
+/*
+ * QuickJS stand alone interpreter
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ * Copyright (c) 2017-2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#if defined(__APPLE__)
+#include <malloc/malloc.h>
+#elif defined(__linux__)
+#include <malloc.h>
+#endif
+
+#include "cutils.h"
+#include "quickjs-libc.h"
+
+extern const uint8_t qjsc_repl[];
+extern const uint32_t qjsc_repl_size;
+#ifdef CONFIG_BIGNUM
+extern const uint8_t qjsc_qjscalc[];
+extern const uint32_t qjsc_qjscalc_size;
+#endif
+
+static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
+                    const char *filename, int eval_flags)
+{
+    JSValue val;
+    int ret;
+
+    if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
+        /* for the modules, we compile then run to be able to set
+           import.meta */
+        val = JS_Eval(ctx, buf, buf_len, filename,
+                      eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
+        if (!JS_IsException(val)) {
+            js_module_set_import_meta(ctx, val, TRUE, TRUE);
+            val = JS_EvalFunction(ctx, val);
+        }
+    } else {
+        val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
+    }
+    if (JS_IsException(val)) {
+        js_std_dump_error(ctx);
+        ret = -1;
+    } else {
+        ret = 0;
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static int eval_file(JSContext *ctx, const char *filename, int module)
+{
+    uint8_t *buf;
+    int ret, eval_flags;
+    size_t buf_len;
+    
+    buf = js_load_file(ctx, &buf_len, filename);
+    if (!buf) {
+        perror(filename);
+        exit(1);
+    }
+
+    if (module < 0) {
+        module = (has_suffix(filename, ".mjs") ||
+                  JS_DetectModule((const char *)buf, buf_len));
+    }
+    if (module)
+        eval_flags = JS_EVAL_TYPE_MODULE;
+    else
+        eval_flags = JS_EVAL_TYPE_GLOBAL;
+    ret = eval_buf(ctx, buf, buf_len, filename, eval_flags);
+    js_free(ctx, buf);
+    return ret;
+}
+
+#if defined(__APPLE__)
+#define MALLOC_OVERHEAD  0
+#else
+#define MALLOC_OVERHEAD  8
+#endif
+
+struct trace_malloc_data {
+    uint8_t *base;
+};
+
+static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr,
+                                                struct trace_malloc_data *dp)
+{
+    return ptr - dp->base;
+}
+
+/* default memory allocation functions with memory limitation */
+static inline size_t js_trace_malloc_usable_size(void *ptr)
+{
+#if defined(__APPLE__)
+    return malloc_size(ptr);
+#elif defined(_WIN32)
+    return _msize(ptr);
+#elif defined(EMSCRIPTEN)
+    return 0;
+#elif defined(__linux__)
+    return malloc_usable_size(ptr);
+#else
+    /* change this to `return 0;` if compilation fails */
+    return malloc_usable_size(ptr);
+#endif
+}
+
+static void __attribute__((format(printf, 2, 3)))
+    js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...)
+{
+    va_list ap;
+    int c;
+
+    va_start(ap, fmt);
+    while ((c = *fmt++) != '\0') {
+        if (c == '%') {
+            /* only handle %p and %zd */
+            if (*fmt == 'p') {
+                uint8_t *ptr = va_arg(ap, void *);
+                if (ptr == NULL) {
+                    printf("NULL");
+                } else {
+                    printf("H%+06lld.%zd",
+                           js_trace_malloc_ptr_offset(ptr, s->opaque),
+                           js_trace_malloc_usable_size(ptr));
+                }
+                fmt++;
+                continue;
+            }
+            if (fmt[0] == 'z' && fmt[1] == 'd') {
+                size_t sz = va_arg(ap, size_t);
+                printf("%zd", sz);
+                fmt += 2;
+                continue;
+            }
+        }
+        putc(c, stdout);
+    }
+    va_end(ap);
+}
+
+static void js_trace_malloc_init(struct trace_malloc_data *s)
+{
+    free(s->base = malloc(8));
+}
+
+static void *js_trace_malloc(JSMallocState *s, size_t size)
+{
+    void *ptr;
+
+    /* Do not allocate zero bytes: behavior is platform dependent */
+    assert(size != 0);
+
+    if (unlikely(s->malloc_size + size > s->malloc_limit))
+        return NULL;
+    ptr = malloc(size);
+    js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr);
+    if (ptr) {
+        s->malloc_count++;
+        s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
+    }
+    return ptr;
+}
+
+static void js_trace_free(JSMallocState *s, void *ptr)
+{
+    if (!ptr)
+        return;
+
+    js_trace_malloc_printf(s, "F %p\n", ptr);
+    s->malloc_count--;
+    s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
+    free(ptr);
+}
+
+static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size)
+{
+    size_t old_size;
+
+    if (!ptr) {
+        if (size == 0)
+            return NULL;
+        return js_trace_malloc(s, size);
+    }
+    old_size = js_trace_malloc_usable_size(ptr);
+    if (size == 0) {
+        js_trace_malloc_printf(s, "R %zd %p\n", size, ptr);
+        s->malloc_count--;
+        s->malloc_size -= old_size + MALLOC_OVERHEAD;
+        free(ptr);
+        return NULL;
+    }
+    if (s->malloc_size + size - old_size > s->malloc_limit)
+        return NULL;
+
+    js_trace_malloc_printf(s, "R %zd %p", size, ptr);
+
+    ptr = realloc(ptr, size);
+    js_trace_malloc_printf(s, " -> %p\n", ptr);
+    if (ptr) {
+        s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size;
+    }
+    return ptr;
+}
+
+static const JSMallocFunctions trace_mf = {
+    js_trace_malloc,
+    js_trace_free,
+    js_trace_realloc,
+#if defined(__APPLE__)
+    malloc_size,
+#elif defined(_WIN32)
+    (size_t (*)(const void *))_msize,
+#elif defined(EMSCRIPTEN)
+    NULL,
+#elif defined(__linux__)
+    (size_t (*)(const void *))malloc_usable_size,
+#else
+    /* change this to `NULL,` if compilation fails */
+    malloc_usable_size,
+#endif
+};
+
+#ifdef CONFIG_BIGNUM
+#define PROG_NAME "qjsbn"
+#else
+#define PROG_NAME "qjs"
+#endif
+
+void help(void)
+{
+    printf("QuickJS version " CONFIG_VERSION "\n"
+           "usage: " PROG_NAME " [options] [file]\n"
+           "-h  --help         list options\n"
+           "-e  --eval EXPR    evaluate EXPR\n"
+           "-i  --interactive  go to interactive mode\n"
+           "-m  --module       load as ES6 module (default=autodetect)\n"
+           "    --script       load as ES6 script (default=autodetect)\n"
+           "    --std          make 'std' and 'os' available to the loaded script\n"
+#ifdef CONFIG_BIGNUM
+           "    --qjscalc      load the QJSCalc runtime (default if invoked as qjscalc)\n"
+#endif
+           "-T  --trace        trace memory allocation\n"
+           "-d  --dump         dump the memory usage stats\n"
+           "-q  --quit         just instantiate the interpreter and quit\n");
+    exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    JSRuntime *rt;
+    JSContext *ctx;
+    struct trace_malloc_data trace_data = { NULL };
+    int optind;
+    char *expr = NULL;
+    int interactive = 0;
+    int dump_memory = 0;
+    int trace_memory = 0;
+    int empty_run = 0;
+    int module = -1;
+    int load_std = 0;
+#ifdef CONFIG_BIGNUM
+    int load_jscalc;
+#endif
+
+#ifdef CONFIG_BIGNUM
+    /* load jscalc runtime if invoked as 'qjscalc' */
+    {
+        const char *p, *exename;
+        exename = argv[0];
+        p = strrchr(exename, '/');
+        if (p)
+            exename = p + 1;
+        load_jscalc = !strcmp(exename, "qjscalc");
+    }
+#endif
+    
+    /* cannot use getopt because we want to pass the command line to
+       the script */
+    optind = 1;
+    while (optind < argc && *argv[optind] == '-') {
+        char *arg = argv[optind] + 1;
+        const char *longopt = "";
+        /* a single - is not an option, it also stops argument scanning */
+        if (!*arg)
+            break;
+        optind++;
+        if (*arg == '-') {
+            longopt = arg + 1;
+            arg += strlen(arg);
+            /* -- stops argument scanning */
+            if (!*longopt)
+                break;
+        }
+        for (; *arg || *longopt; longopt = "") {
+            char opt = *arg;
+            if (opt)
+                arg++;
+            if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) {
+                help();
+                continue;
+            }
+            if (opt == 'e' || !strcmp(longopt, "eval")) {
+                if (*arg) {
+                    expr = arg;
+                    break;
+                }
+                if (optind < argc) {
+                    expr = argv[optind++];
+                    break;
+                }
+                fprintf(stderr, "qjs: missing expression for -e\n");
+                exit(2);
+            }
+            if (opt == 'i' || !strcmp(longopt, "interactive")) {
+                interactive++;
+                continue;
+            }
+            if (opt == 'm' || !strcmp(longopt, "module")) {
+                module = 1;
+                continue;
+            }
+            if (!strcmp(longopt, "script")) {
+                module = 0;
+                continue;
+            }
+            if (opt == 'd' || !strcmp(longopt, "dump")) {
+                dump_memory++;
+                continue;
+            }
+            if (opt == 'T' || !strcmp(longopt, "trace")) {
+                trace_memory++;
+                continue;
+            }
+            if (!strcmp(longopt, "std")) {
+                load_std = 1;
+                continue;
+            }
+#ifdef CONFIG_BIGNUM
+            if (!strcmp(longopt, "qjscalc")) {
+                load_jscalc = 1;
+                continue;
+            }
+#endif
+            if (opt == 'q' || !strcmp(longopt, "quit")) {
+                empty_run++;
+                continue;
+            }
+            if (opt) {
+                fprintf(stderr, "qjs: unknown option '-%c'\n", opt);
+            } else {
+                fprintf(stderr, "qjs: unknown option '--%s'\n", longopt);
+            }
+            help();
+        }
+    }
+
+    if (trace_memory) {
+        js_trace_malloc_init(&trace_data);
+        rt = JS_NewRuntime2(&trace_mf, &trace_data);
+    } else {
+        rt = JS_NewRuntime();
+    }
+    if (!rt) {
+        fprintf(stderr, "qjs: cannot allocate JS runtime\n");
+        exit(2);
+    }
+    ctx = JS_NewContext(rt);
+    if (!ctx) {
+        fprintf(stderr, "qjs: cannot allocate JS context\n");
+        exit(2);
+    }
+
+    /* loader for ES6 modules */
+    JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
+                           
+    if (!empty_run) {
+#ifdef CONFIG_BIGNUM
+        if (load_jscalc) {
+            js_std_eval_binary(ctx, qjsc_qjscalc, qjsc_qjscalc_size, 0);
+        }
+#endif
+        js_std_add_helpers(ctx, argc - optind, argv + optind);
+
+        /* system modules */
+        js_init_module_std(ctx, "std");
+        js_init_module_os(ctx, "os");
+
+        /* make 'std' and 'os' visible to non module code */
+        if (load_std) {
+            const char *str = "import * as std from 'std';\n"
+                "import * as os from 'os';\n"
+                "globalThis.std = std;\n"
+                "globalThis.os = os;\n";
+            eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
+        }
+
+        if (expr) {
+            if (eval_buf(ctx, expr, strlen(expr), "<cmdline>", 0))
+                goto fail;
+        } else
+        if (optind >= argc) {
+            /* interactive mode */
+            interactive = 1;
+        } else {
+            const char *filename;
+            filename = argv[optind];
+            if (eval_file(ctx, filename, module))
+                goto fail;
+        }
+        if (interactive) {
+            js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0);
+        }
+        js_std_loop(ctx);
+    }
+    
+    if (dump_memory) {
+        JSMemoryUsage stats;
+        JS_ComputeMemoryUsage(rt, &stats);
+        JS_DumpMemoryUsage(stdout, &stats, rt);
+    }
+    js_std_free_handlers(rt);
+    JS_FreeContext(ctx);
+    JS_FreeRuntime(rt);
+
+    if (empty_run && dump_memory) {
+        clock_t t[5];
+        double best[5];
+        int i, j;
+        for (i = 0; i < 100; i++) {
+            t[0] = clock();
+            rt = JS_NewRuntime();
+            t[1] = clock();
+            ctx = JS_NewContext(rt);
+            t[2] = clock();
+            JS_FreeContext(ctx);
+            t[3] = clock();
+            JS_FreeRuntime(rt);
+            t[4] = clock();
+            for (j = 4; j > 0; j--) {
+                double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC;
+                if (i == 0 || best[j] > ms)
+                    best[j] = ms;
+            }
+        }
+        printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n",
+               best[1] + best[2] + best[3] + best[4],
+               best[1], best[2], best[3], best[4]);
+    }
+    return 0;
+ fail:
+    js_std_free_handlers(rt);
+    JS_FreeContext(ctx);
+    JS_FreeRuntime(rt);
+    return 1;
+}
diff --git a/qjsc.c b/qjsc.c
new file mode 100644
index 0000000..b0f20f7
--- /dev/null
+++ b/qjsc.c
@@ -0,0 +1,696 @@
+/*
+ * QuickJS command line compiler
+ * 
+ * Copyright (c) 2018-2019 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#if !defined(_WIN32)
+#include <sys/wait.h>
+#endif
+
+#include "cutils.h"
+#include "quickjs-libc.h"
+
+typedef struct {
+    char *name;
+    char *short_name;
+    int flags;
+} namelist_entry_t;
+
+typedef struct namelist_t {
+    namelist_entry_t *array;
+    int count;
+    int size;
+} namelist_t;
+
+typedef struct {
+    const char *option_name;
+    const char *init_name;
+} FeatureEntry;
+
+static namelist_t cname_list;
+static namelist_t cmodule_list;
+static namelist_t init_module_list;
+static uint64_t feature_bitmap;
+static FILE *outfile;
+static BOOL byte_swap;
+static BOOL dynamic_export;
+static const char *c_ident_prefix = "qjsc_";
+
+#define FE_ALL (-1)
+
+static const FeatureEntry feature_list[] = {
+    { "date", "Date" },
+    { "eval", "Eval" },
+    { "string-normalize", "StringNormalize" },
+    { "regexp", "RegExp" },
+    { "json", "JSON" },
+    { "proxy", "Proxy" },
+    { "map", "MapSet" },
+    { "typedarray", "TypedArrays" },
+    { "promise", "Promise" },
+#define FE_MODULE_LOADER 9
+    { "module-loader", NULL },
+};
+
+void namelist_add(namelist_t *lp, const char *name, const char *short_name,
+                  int flags)
+{
+    namelist_entry_t *e;
+    if (lp->count == lp->size) {
+        size_t newsize = lp->size + (lp->size >> 1) + 4;
+        namelist_entry_t *a =
+            realloc(lp->array, sizeof(lp->array[0]) * newsize);
+        /* XXX: check for realloc failure */
+        lp->array = a;
+        lp->size = newsize;
+    }
+    e =  &lp->array[lp->count++];
+    e->name = strdup(name);
+    if (short_name)
+        e->short_name = strdup(short_name);
+    else
+        e->short_name = NULL;
+    e->flags = flags;
+}
+
+void namelist_free(namelist_t *lp)
+{
+    while (lp->count > 0) {
+        namelist_entry_t *e = &lp->array[--lp->count];
+        free(e->name);
+        free(e->short_name);
+    }
+    free(lp->array);
+    lp->array = NULL;
+    lp->size = 0;
+}
+
+namelist_entry_t *namelist_find(namelist_t *lp, const char *name)
+{
+    int i;
+    for(i = 0; i < lp->count; i++) {
+        namelist_entry_t *e = &lp->array[i];
+        if (!strcmp(e->name, name))
+            return e;
+    }
+    return NULL;
+}
+
+static void get_c_name(char *buf, size_t buf_size, const char *file)
+{
+    const char *p, *r;
+    size_t len, i;
+    int c;
+    char *q;
+    
+    p = strrchr(file, '/');
+    if (!p)
+        p = file;
+    else
+        p++;
+    r = strrchr(p, '.');
+    if (!r)
+        len = strlen(p);
+    else
+        len = r - p;
+    pstrcpy(buf, buf_size, c_ident_prefix);
+    q = buf + strlen(buf);
+    for(i = 0; i < len; i++) {
+        c = p[i];
+        if (!((c >= '0' && c <= '9') ||
+              (c >= 'A' && c <= 'Z') ||
+              (c >= 'a' && c <= 'z'))) {
+            c = '_';
+        }
+        if ((q - buf) < buf_size - 1)
+            *q++ = c;
+    }
+    *q = '\0';
+}
+
+static void dump_hex(FILE *f, const uint8_t *buf, size_t len)
+{
+    size_t i, col;
+    col = 0;
+    for(i = 0; i < len; i++) {
+        fprintf(f, " 0x%02x,", buf[i]);
+        if (++col == 8) {
+            fprintf(f, "\n");
+            col = 0;
+        }
+    }
+    if (col != 0)
+        fprintf(f, "\n");
+}
+
+static void output_object_code(JSContext *ctx,
+                               FILE *fo, JSValueConst obj, const char *c_name,
+                               BOOL load_only)
+{
+    uint8_t *out_buf;
+    size_t out_buf_len;
+    int flags;
+    flags = JS_WRITE_OBJ_BYTECODE;
+    if (byte_swap)
+        flags |= JS_WRITE_OBJ_BSWAP;
+    out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags);
+    if (!out_buf) {
+        js_std_dump_error(ctx);
+        exit(1);
+    }
+
+    namelist_add(&cname_list, c_name, NULL, load_only);
+    
+    fprintf(fo, "const uint32_t %s_size = %u;\n\n", 
+            c_name, (unsigned int)out_buf_len);
+    fprintf(fo, "const uint8_t %s[%u] = {\n",
+            c_name, (unsigned int)out_buf_len);
+    dump_hex(fo, out_buf, out_buf_len);
+    fprintf(fo, "};\n\n");
+
+    js_free(ctx, out_buf);
+}
+
+static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m)
+{
+    /* should never be called when compiling JS code */
+    abort();
+}
+
+static void find_unique_cname(char *cname, size_t cname_size)
+{
+    char cname1[1024];
+    int suffix_num;
+    size_t len, max_len;
+    assert(cname_size >= 32);
+    /* find a C name not matching an existing module C name by
+       adding a numeric suffix */
+    len = strlen(cname);
+    max_len = cname_size - 16;
+    if (len > max_len)
+        cname[max_len] = '\0';
+    suffix_num = 1;
+    for(;;) {
+        snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num);
+        if (!namelist_find(&cname_list, cname1))
+            break;
+        suffix_num++;
+    }
+    pstrcpy(cname, cname_size, cname1);
+}
+
+JSModuleDef *jsc_module_loader(JSContext *ctx,
+                              const char *module_name, void *opaque)
+{
+    JSModuleDef *m;
+    namelist_entry_t *e;
+
+    /* check if it is a declared C or system module */
+    e = namelist_find(&cmodule_list, module_name);
+    if (e) {
+        /* add in the static init module list */
+        namelist_add(&init_module_list, e->name, e->short_name, 0);
+        /* create a dummy module */
+        m = JS_NewCModule(ctx, module_name, js_module_dummy_init);
+    } else if (has_suffix(module_name, ".so")) {
+        fprintf(stderr, "Warning: binary module '%s' will be dynamically loaded\n", module_name);
+        /* create a dummy module */
+        m = JS_NewCModule(ctx, module_name, js_module_dummy_init);
+        /* the resulting executable will export its symbols for the
+           dynamic library */
+        dynamic_export = TRUE;
+    } else {
+        size_t buf_len;
+        uint8_t *buf;
+        JSValue func_val;
+        char cname[1024];
+        
+        buf = js_load_file(ctx, &buf_len, module_name);
+        if (!buf) {
+            JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
+                                   module_name);
+            return NULL;
+        }
+        
+        /* compile the module */
+        func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
+                           JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+        js_free(ctx, buf);
+        if (JS_IsException(func_val))
+            return NULL;
+        get_c_name(cname, sizeof(cname), module_name);
+        if (namelist_find(&cname_list, cname)) {
+            find_unique_cname(cname, sizeof(cname));
+        }
+        output_object_code(ctx, outfile, func_val, cname, TRUE);
+        
+        /* the module is already referenced, so we must free it */
+        m = JS_VALUE_GET_PTR(func_val);
+        JS_FreeValue(ctx, func_val);
+    }
+    return m;
+}
+
+static void compile_file(JSContext *ctx, FILE *fo,
+                         const char *filename,
+                         const char *c_name1,
+                         int module)
+{
+    uint8_t *buf;
+    char c_name[1024];
+    int eval_flags;
+    JSValue obj;
+    size_t buf_len;
+    
+    buf = js_load_file(ctx, &buf_len, filename);
+    if (!buf) {
+        fprintf(stderr, "Could not load '%s'\n", filename);
+        exit(1);
+    }
+    eval_flags = JS_EVAL_FLAG_COMPILE_ONLY;
+    if (module < 0) {
+        module = (has_suffix(filename, ".mjs") ||
+                  JS_DetectModule((const char *)buf, buf_len));
+    }
+    if (module)
+        eval_flags |= JS_EVAL_TYPE_MODULE;
+    else
+        eval_flags |= JS_EVAL_TYPE_GLOBAL;
+    obj = JS_Eval(ctx, (const char *)buf, buf_len, filename, eval_flags);
+    if (JS_IsException(obj)) {
+        js_std_dump_error(ctx);
+        exit(1);
+    }
+    js_free(ctx, buf);
+    if (c_name1) {
+        pstrcpy(c_name, sizeof(c_name), c_name1);
+    } else {
+        get_c_name(c_name, sizeof(c_name), filename);
+    }
+    output_object_code(ctx, fo, obj, c_name, FALSE);
+    JS_FreeValue(ctx, obj);
+}
+
+static const char main_c_template1[] =
+    "int main(int argc, char **argv)\n"
+    "{\n"
+    "  JSRuntime *rt;\n"
+    "  JSContext *ctx;\n"
+    "  rt = JS_NewRuntime();\n"
+    ;
+
+static const char main_c_template2[] =
+    "  js_std_loop(ctx);\n"
+    "  JS_FreeContext(ctx);\n"
+    "  JS_FreeRuntime(rt);\n"
+    "  return 0;\n"
+    "}\n";
+
+#ifdef CONFIG_BIGNUM
+#define PROG_NAME "qjscbn"
+#else
+#define PROG_NAME "qjsc"
+#endif
+
+void help(void)
+{
+    printf("QuickJS Compiler version " CONFIG_VERSION "\n"
+           "usage: " PROG_NAME " [options] [files]\n"
+           "\n"
+           "options are:\n"
+           "-c          only output bytecode in a C file\n"
+           "-e          output main() and bytecode in a C file (default = executable output)\n"
+           "-o output   set the output filename\n"
+           "-N cname    set the C name of the generated data\n"
+           "-m          compile as Javascript module (default=autodetect)\n"
+           "-M module_name[,cname] add initialization code for an external C module\n"
+           "-x          byte swapped output\n"
+           "-p prefix   set the prefix of the generated C names\n"
+           );
+#ifdef CONFIG_LTO
+    {
+        int i;
+        printf("-flto       use link time optimization\n");
+        printf("-fno-[");
+        for(i = 0; i < countof(feature_list); i++) {
+            if (i != 0)
+                printf("|");
+            printf("%s", feature_list[i].option_name);
+        }
+        printf("]\n"
+               "            disable selected language features (smaller code size)\n");
+    }
+#endif
+    exit(1);
+}
+
+#if defined(CONFIG_CC) && !defined(_WIN32)
+
+int exec_cmd(char **argv)
+{
+    int pid, status, ret;
+
+    pid = fork();
+    if (pid == 0) {
+        execvp(argv[0], argv);
+        exit(1);
+    } 
+
+    for(;;) {
+        ret = waitpid(pid, &status, 0);
+        if (ret == pid && WIFEXITED(status))
+            break;
+    }
+    return WEXITSTATUS(status);
+}
+
+static int output_executable(const char *out_filename, const char *cfilename,
+                             BOOL use_lto, BOOL verbose, const char *exename)
+{
+    const char *argv[64];
+    const char **arg, *bn_suffix, *lto_suffix;
+    char libjsname[1024];
+    char exe_dir[1024], inc_dir[1024], lib_dir[1024], buf[1024], *p;
+    int ret;
+    
+    /* get the directory of the executable */
+    pstrcpy(exe_dir, sizeof(exe_dir), exename);
+    p = strrchr(exe_dir, '/');
+    if (p) {
+        *p = '\0';
+    } else {
+        pstrcpy(exe_dir, sizeof(exe_dir), ".");
+    }
+
+    /* if 'quickjs.h' is present at the same path as the executable, we
+       use it as include and lib directory */
+    snprintf(buf, sizeof(buf), "%s/quickjs.h", exe_dir);
+    if (access(buf, R_OK) == 0) {
+        pstrcpy(inc_dir, sizeof(inc_dir), exe_dir);
+        pstrcpy(lib_dir, sizeof(lib_dir), exe_dir);
+    } else {
+        snprintf(inc_dir, sizeof(inc_dir), "%s/include/quickjs", CONFIG_PREFIX);
+        snprintf(lib_dir, sizeof(lib_dir), "%s/lib/quickjs", CONFIG_PREFIX);
+    }
+    
+    lto_suffix = "";
+#ifdef CONFIG_BIGNUM
+    bn_suffix = ".bn";
+#else
+    bn_suffix = "";
+#endif
+    
+    arg = argv;
+    *arg++ = CONFIG_CC;
+    *arg++ = "-O2";
+#ifdef CONFIG_LTO
+    if (use_lto) {
+        *arg++ = "-flto";
+        lto_suffix = ".lto";
+    }
+#endif
+    /* XXX: use the executable path to find the includes files and
+       libraries */
+    *arg++ = "-D";
+    *arg++ = "_GNU_SOURCE";
+    *arg++ = "-I";
+    *arg++ = inc_dir;
+    *arg++ = "-o";
+    *arg++ = out_filename;
+    if (dynamic_export)
+        *arg++ = "-rdynamic";
+    *arg++ = cfilename;
+    snprintf(libjsname, sizeof(libjsname), "%s/libquickjs%s%s.a",
+             lib_dir, bn_suffix, lto_suffix);
+    *arg++ = libjsname;
+    *arg++ = "-lm";
+    *arg++ = "-ldl";
+    *arg = NULL;
+    
+    if (verbose) {
+        for(arg = argv; *arg != NULL; arg++)
+            printf("%s ", *arg);
+        printf("\n");
+    }
+    
+    ret = exec_cmd((char **)argv);
+    unlink(cfilename);
+    return ret;
+}
+#else
+static int output_executable(const char *out_filename, const char *cfilename,
+                             BOOL use_lto, BOOL verbose, const char *exename)
+{
+    fprintf(stderr, "Executable output is not supported for this target\n");
+    exit(1);
+    return 0;
+}
+#endif
+
+
+typedef enum {
+    OUTPUT_C,
+    OUTPUT_C_MAIN,
+    OUTPUT_EXECUTABLE,
+} OutputTypeEnum;
+
+int main(int argc, char **argv)
+{
+    int c, i, verbose;
+    const char *out_filename, *cname;
+    char cfilename[1024];
+    FILE *fo;
+    JSRuntime *rt;
+    JSContext *ctx;
+    BOOL use_lto;
+    int module;
+    OutputTypeEnum output_type;
+    
+    out_filename = NULL;
+    output_type = OUTPUT_EXECUTABLE;
+    cname = NULL;
+    feature_bitmap = FE_ALL;
+    module = -1;
+    byte_swap = FALSE;
+    verbose = 0;
+    use_lto = FALSE;
+
+    /* add system modules */
+    namelist_add(&cmodule_list, "std", "std", 0);
+    namelist_add(&cmodule_list, "os", "os", 0);
+
+    for(;;) {
+        c = getopt(argc, argv, "ho:cN:f:mxevM:p:");
+        if (c == -1)
+            break;
+        switch(c) {
+        case 'h':
+            help();
+        case 'o':
+            out_filename = optarg;
+            break;
+        case 'c':
+            output_type = OUTPUT_C;
+            break;
+        case 'e':
+            output_type = OUTPUT_C_MAIN;
+            break;
+        case 'N':
+            cname = optarg;
+            break;
+        case 'f':
+            {
+                const char *p;
+                p = optarg;
+                if (!strcmp(optarg, "lto")) {
+                    use_lto = TRUE;
+                } else if (strstart(p, "no-", &p)) {
+                    use_lto = TRUE;
+                    for(i = 0; i < countof(feature_list); i++) {
+                        if (!strcmp(p, feature_list[i].option_name)) {
+                            feature_bitmap &= ~((uint64_t)1 << i);
+                            break;
+                        }
+                    }
+                    if (i == countof(feature_list))
+                        goto bad_feature;
+                } else {
+                bad_feature:
+                    fprintf(stderr, "unsupported feature: %s\n", optarg);
+                    exit(1);
+                }
+            }
+            break;
+        case 'm':
+            module = 1;
+            break;
+        case 'M':
+            {
+                char *p;
+                char path[1024];
+                char cname[1024];
+                pstrcpy(path, sizeof(path), optarg);
+                p = strchr(path, ',');
+                if (p) {
+                    *p = '\0';
+                    pstrcpy(cname, sizeof(cname), p + 1);
+                } else {
+                    get_c_name(cname, sizeof(cname), path);
+                }
+                namelist_add(&cmodule_list, path, cname, 0);
+            }
+            break;
+        case 'x':
+            byte_swap = TRUE;
+            break;
+        case 'v':
+            verbose++;
+            break;
+        case 'p':
+            c_ident_prefix = optarg;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (optind >= argc)
+        help();
+
+    if (!out_filename) {
+        if (output_type == OUTPUT_EXECUTABLE) {
+            out_filename = "a.out";
+        } else {
+            out_filename = "out.c";
+        }
+    }
+
+    if (output_type == OUTPUT_EXECUTABLE) {
+#if defined(_WIN32) || defined(__ANDROID__)
+        /* XXX: find a /tmp directory ? */
+        snprintf(cfilename, sizeof(cfilename), "out%d.c", getpid());
+#else
+        snprintf(cfilename, sizeof(cfilename), "/tmp/out%d.c", getpid());
+#endif
+    } else {
+        pstrcpy(cfilename, sizeof(cfilename), out_filename);
+    }
+    
+    fo = fopen(cfilename, "w");
+    if (!fo) {
+        perror(cfilename);
+        exit(1);
+    }
+    outfile = fo;
+    
+    rt = JS_NewRuntime();
+    ctx = JS_NewContextRaw(rt);
+    JS_AddIntrinsicEval(ctx);
+    JS_AddIntrinsicRegExpCompiler(ctx);
+    
+    /* loader for ES6 modules */
+    JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL);
+
+    fprintf(fo, "/* File generated automatically by the QuickJS compiler. */\n"
+            "\n"
+            );
+    
+    if (output_type != OUTPUT_C) {
+        fprintf(fo, "#include \"quickjs-libc.h\"\n"
+                "\n"
+                );
+    } else {
+        fprintf(fo, "#include <inttypes.h>\n"
+                "\n"
+                );
+    }
+
+    for(i = optind; i < argc; i++) {
+        const char *filename = argv[i];
+        compile_file(ctx, fo, filename, cname, module);
+        cname = NULL;
+    }
+
+    if (output_type != OUTPUT_C) {
+        fputs(main_c_template1, fo);
+        fprintf(fo, "  ctx = JS_NewContextRaw(rt);\n");
+
+        /* add the module loader if necessary */
+        if (feature_bitmap & (1 << FE_MODULE_LOADER)) {
+            fprintf(fo, "  JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n");
+        }
+        
+        /* add the basic objects */
+        
+        fprintf(fo, "  JS_AddIntrinsicBaseObjects(ctx);\n");
+        for(i = 0; i < countof(feature_list); i++) {
+            if ((feature_bitmap & ((uint64_t)1 << i)) &&
+                feature_list[i].init_name) {
+                fprintf(fo, "  JS_AddIntrinsic%s(ctx);\n",
+                        feature_list[i].init_name);
+            }
+        }
+
+        fprintf(fo, "  js_std_add_helpers(ctx, argc, argv);\n");
+
+        for(i = 0; i < init_module_list.count; i++) {
+            namelist_entry_t *e = &init_module_list.array[i];
+            /* initialize the static C modules */
+            
+            fprintf(fo,
+                    "  {\n"
+                    "    extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n"
+                    "    js_init_module_%s(ctx, \"%s\");\n"
+                    "  }\n",
+                    e->short_name, e->short_name, e->name);
+        }
+
+        for(i = 0; i < cname_list.count; i++) {
+            namelist_entry_t *e = &cname_list.array[i];
+            fprintf(fo, "  js_std_eval_binary(ctx, %s, %s_size, %s);\n",
+                    e->name, e->name,
+                    e->flags ? "1" : "0");
+        }
+        fputs(main_c_template2, fo);
+    }
+    
+    JS_FreeContext(ctx);
+    JS_FreeRuntime(rt);
+
+    fclose(fo);
+
+    if (output_type == OUTPUT_EXECUTABLE) {
+        return output_executable(out_filename, cfilename, use_lto, verbose,
+                                 argv[0]);
+    }
+    namelist_free(&cname_list);
+    namelist_free(&cmodule_list);
+    namelist_free(&init_module_list);
+    return 0;
+}
diff --git a/qjscalc.js b/qjscalc.js
new file mode 100644
index 0000000..87382ec
--- /dev/null
+++ b/qjscalc.js
@@ -0,0 +1,2442 @@
+/*
+ * QuickJS Javascript Calculator
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+"use strict";
+"use math";
+
+var Integer, Float, Fraction, Complex, Mod, Polynomial, PolyMod, RationalFunction, Series, Matrix;
+
+(function(global) {
+    /* the types index are used to dispatch the operator functions */
+    var OT_INT = 0;
+    var OT_FRACTION = 10;
+    var OT_FLOAT64 = 19;
+    var OT_FLOAT = 20;
+    var OT_COMPLEX = 30;
+    var OT_MOD = 40;
+    var OT_POLY = 50;
+    var OT_POLYMOD = 55;
+    var OT_RFUNC = 60;
+    var OT_SERIES = 70;
+    var OT_ARRAY = 80;
+
+    global.Integer = global.BigInt;
+    global.algebraicMode = true;
+    
+    /* add non enumerable properties */
+    function add_props(obj, props) {
+        var i, val, prop, tab, desc;
+        tab = Reflect.ownKeys(props);
+        for(i = 0; i < tab.length; i++) {
+            prop = tab[i];
+            desc = Object.getOwnPropertyDescriptor(props, prop);
+            desc.enumerable = false;
+            if ("value" in desc) {
+                if (typeof desc.value !== "function") {
+                    desc.writable = false;
+                    desc.configurable = false;
+                }
+            } else {
+                /* getter/setter */
+                desc.configurable = false;
+            }
+            Object.defineProperty(obj, prop, desc);
+        }
+    }
+    
+    /* Integer */
+
+    function generic_pow(a, b) {
+        var r, is_neg, i;
+        if (!Integer.isInteger(b)) {
+            return exp(log(a) * b);
+        }
+        if (Array.isArray(a) && !(a instanceof Polynomial ||
+                                  a instanceof Series)) {
+            r = idn(Matrix.check_square(a));
+        } else {
+            r = 1;
+        }
+        if (b == 0)
+            return r;
+        is_neg = false;
+        if (b < 0) {
+            is_neg = true;
+            b = -b;
+        }
+        r = a;
+        for(i = Integer.floorLog2(b) - 1; i >= 0; i--) {
+            r *= r;
+            if ((b >> i) & 1)
+                r *= a;
+        }
+        if (is_neg) {
+            if (typeof r.inverse != "function")
+                throw "negative powers are not supported for this type";
+            r = r.inverse();
+        }
+        return r;
+    }
+    
+    var small_primes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499 ];
+
+    function miller_rabin_test(n, t) {
+        var d, r, s, i, j, a;
+        d = n - 1;
+        s = 0;
+        while (d & 1) {
+            d >>= 1;
+            s++;
+        }
+        t = Math.min(t, small_primes.length);
+        loop: for(j = 0; j < t; j++) {
+            a = small_primes[j];
+            r = Integer.pmod(a, d, n);
+            if (r == 1 || r == (n - 1))
+                continue;
+            for(i = 1; i < s; i++) {
+                r = (r * r) % n;
+                if (r == 1)
+                    return false;
+                if (r == (n - 1))
+                    continue loop;
+            }
+            return false; /* n is composite */
+        }
+        return true; /* n is probably prime with probability (1-0.5^t) */
+    }
+
+    function fact_rec(a, b) {  /* assumes a <= b */
+        var i, r;
+        if ((b - a) <= 5) {
+            r = a;
+            for(i = a + 1; i <= b; i++)
+                r *= i;
+            return r;
+        } else {
+            /* to avoid a quadratic running time it is better to
+               multiply numbers of similar size */
+            i = (a + b) >> 1;
+            return fact_rec(a, i) * fact_rec(i + 1, b);
+        }
+    }
+    
+    add_props(Integer, {
+        isInteger(a) {
+            return typeof a === "bigint";
+        },
+        [Symbol.operatorOrder]: OT_INT,
+        [Symbol.operatorDiv](a, b) {
+            if (algebraicMode) {
+                return Fraction.toFraction(a, b);
+            } else {
+                return Float(a) / Float(b);
+            }
+        },
+        [Symbol.operatorPow](a, b) {
+            if (algebraicMode) {
+                return generic_pow(a, b);
+            } else {
+                return Float(a) ** Float(b);
+            }
+        },
+        gcd(a, b) {
+            var r;
+            while (b != 0) {
+                r = a % b;
+                a = b;
+                b = r;
+            }
+            return a;
+        },
+        fact(n) {
+            return n <= 0 ? 1 : fact_rec(1, n);
+        },
+        /* binomial coefficient */
+        comb(n, k) {
+            if (k < 0 || k > n)
+                return 0;
+            if (k > n - k)
+                k = n - k;
+            if (k == 0)
+                return 1;
+            return Integer.tdiv(fact_rec(n - k + 1, n), fact_rec(1, k));
+        },
+        /* inverse of x modulo y */
+        invmod(x, y) {
+            var q, u, v, a, c, t;
+            u = x;
+            v = y;
+            c = 1;
+            a = 0;
+            while (u != 0) {
+                t = Integer.fdivrem(v, u);
+                q = t[0];
+                v = u;
+                u = t[1];
+                t = c;
+                c = a - q * c;
+                a = t;
+            }
+            /* v = gcd(x, y) */
+            if (v != 1)
+                throw RangeError("not invertible");
+            return a % y;
+        },
+        /* return a ^ b modulo m */
+        pmod(a, b, m) {
+            var r;
+            if (b == 0)
+                return 1;
+            if (b < 0) {
+                a = Integer.invmod(a, m);
+                b = -b;
+            }
+            r = 1;
+            for(;;) {
+                if (b & 1) {
+                    r = (r * a) % m;
+                }
+                b >>= 1;
+                if (b == 0)
+                    break;
+                a = (a * a) % m;
+            }
+            return r;
+        },
+
+        /* return true if n is prime (or probably prime with
+           probability 1-0.5^t) */
+        isPrime(n, t) {
+            var i, d, n1;
+            if (!Integer.isInteger(n))
+                throw TypeError("invalid type");
+            if (n <= 1)
+                return false;
+            n1 = small_primes.length;
+            /* XXX: need Integer.sqrt() */
+            for(i = 0; i < n1; i++) {
+                d = small_primes[i];
+                if (d == n)
+                    return true;
+                if (d > n)
+                    return false;
+                if ((n % d) == 0)
+                    return false;
+            }
+            if (n < d * d)
+                return true;
+            if (typeof t == "undefined")
+                t = 64;
+            return miller_rabin_test(n, t);
+        },
+        nextPrime(n) {
+            if (!Integer.isInteger(n))
+                throw TypeError("invalid type");
+            if (n < 1)
+                n = 1;
+            for(;;) {
+                n++;
+                if (Integer.isPrime(n))
+                    return n;
+            }
+        },
+        factor(n) {
+            var r, d;
+            if (!Integer.isInteger(n))
+                throw TypeError("invalid type");
+            r = [];
+            if (abs(n) <= 1) {
+                r.push(n);
+                return r;
+            }
+            if (n < 0) {
+                r.push(-1);
+                n = -n;
+            }
+            
+            while ((n % 2) == 0) {
+                n >>= 1;
+                r.push(2);
+            }
+
+            d = 3;
+            while (n != 1) {
+                if (Integer.isPrime(n)) {
+                    r.push(n);
+                    break;
+                }
+                /* we are sure there is at least one divisor, so one test */
+                for(;;) {
+                    if ((n % d) == 0)
+                        break;
+                    d += 2;
+                }
+                for(;;) {
+                    r.push(d);
+                    n = Integer.tdiv(n, d);
+                    if ((n % d) != 0)
+                        break;
+                }
+            }
+            return r;
+        },
+    });
+
+    add_props(Integer.prototype, {
+        inverse() {
+            return 1 / this;
+        },
+        norm2() {
+            return this * this;
+        },
+        abs() {
+            return Math.abs(this);
+        },
+        conj() {
+            return this;
+        },
+        arg() {
+            if (this >= 0)
+                return 0;
+            else
+                return Float.PI;
+        },
+        exp() {
+            if (this == 0)
+                return 1;
+            else
+                return Float.exp(this);
+        },
+        log() {
+            if (this == 1)
+                return 0;
+            else
+                return Float(this).log();
+        },
+    });
+
+    /* Fraction */
+
+    Fraction = function Fraction(a, b)
+    {
+        var d, r, obj;
+
+        if (new.target)
+            throw TypeError("not a constructor");
+        if (a instanceof Fraction)
+            return a;
+        if (!Integer.isInteger(a))
+            throw TypeError("integer expected");
+        if (typeof b === "undefined") {
+            b = 1;
+        } else {
+            if (!Integer.isInteger(b))
+                throw TypeError("integer expected");
+            if (b == 0)
+                throw RangeError("division by zero");
+            d = Integer.gcd(a, b);
+            if (d != 1) {
+                a = Integer.tdiv(a, d);
+                b = Integer.tdiv(b, d);
+            }
+            
+            /* the fractions are normalized with den > 0 */
+            if (b < 0) {
+                a = -a;
+                b = -b;
+            }
+        }
+        obj = Object.create(Fraction.prototype);
+        obj.num = a;
+        obj.den = b;
+        return obj;
+    }
+
+    add_props(Fraction, {
+        [Symbol.operatorOrder]: OT_FRACTION,
+        /* (internal use) simplify 'a' to an integer when possible */
+        toFraction(a, b) {
+            var r = Fraction(a, b);
+            if (algebraicMode && r.den == 1)
+                return r.num;
+            else
+                return r;
+        },
+
+        [Symbol.operatorAdd](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            return Fraction.toFraction(a.num * b.den + a.den * b.num, a.den * b.den);
+        },
+        [Symbol.operatorSub](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            return Fraction.toFraction(a.num * b.den - a.den * b.num, a.den * b.den);
+        },
+        [Symbol.operatorMul](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            return Fraction.toFraction(a.num * b.num, a.den * b.den);
+        },
+        [Symbol.operatorDiv](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            return Fraction.toFraction(a.num * b.den, a.den * b.num);
+        },
+        [Symbol.operatorMathMod](a, b) {
+            var a1 = Fraction(a);
+            var b1 = Fraction(b);
+            return a - Integer.ediv(a1.num * b1.den, a1.den * b1.num) * b;
+        },
+        [Symbol.operatorMod](a, b) {
+            var a1 = Fraction(a);
+            var b1 = Fraction(b);
+            return a - Integer.tdiv(a1.num * b1.den, a1.den * b1.num) * b;
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorCmpEQ](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            /* we assume the fractions are normalized */
+            return (a.num == b.num && a.den == b.den);
+        },
+        [Symbol.operatorCmpLT](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            return (a.num * b.den < b.num * a.den);
+        },
+        [Symbol.operatorCmpLE](a, b) {
+            a = Fraction(a);
+            b = Fraction(b);
+            return (a.num * b.den <= b.num * a.den);
+        },
+    });
+
+    add_props(Fraction.prototype, {
+        [Symbol.toPrimitive](hint) {
+            if (hint === "integer") {
+                return Integer.tdiv(this.num, this.den);
+            } else if (hint === "string") {
+                return this.toString();
+            } else {
+                return Float(this.num) / this.den;
+            }
+        },
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            return Fraction(-this.num, this.den);
+        },
+        inverse() {
+            return Fraction(this.den, this.num);
+        },
+        toString() {
+            return this.num + "/" + this.den;
+        },
+        norm2() {
+            return this * this;
+        },
+        abs() {
+            if (this.num < 0)
+                return this[Symbol.operatorNeg]();
+            else
+                return this;
+        },
+        conj() {
+            return this;
+        },
+        arg() {
+            if (this.num >= 0)
+                return 0;
+            else
+                return Float.PI;
+        },
+        exp() {
+            return Float.exp(Float(this));
+        },
+        log() {
+            return Float(this).log();
+        },
+    });
+
+    /* Number (Float64) */
+
+    add_props(Number, {
+        [Symbol.operatorOrder]: OT_FLOAT64,
+        /* operators are needed for fractions */
+        [Symbol.operatorAdd](a, b) {
+            return Number(a) + Number(b);
+        },
+        [Symbol.operatorSub](a, b) {
+            return Number(a) - Number(b);
+        },
+        [Symbol.operatorMul](a, b) {
+            return Number(a) * Number(b);
+        },
+        [Symbol.operatorDiv](a, b) {
+            return Number(a) / Number(b);
+        },
+        [Symbol.operatorPow](a, b) {
+            return Number(a) ** Number(b);
+        },
+    });
+              
+    add_props(Number.prototype, {
+        inverse() {
+            return 1.0 / this;
+        },
+        norm2() {
+            return this * this;
+        },
+        abs() {
+            return Math.abs(this);
+        },
+        conj() {
+            return this;
+        },
+        arg() {
+            if (this >= 0)
+                return 0;
+            else
+                return Float.PI;
+        },
+        exp() {
+            return Float.exp(this);
+        },
+        log() {
+            if (this < 0) {
+                return Complex(this).log();
+            } else {
+                return Float.log(this);
+            }
+        },
+    });
+    
+    /* Float */
+
+    global.Float = global.BigFloat;
+
+    var const_tab = [];
+    
+    /* we cache the constants for small precisions */
+    function get_const(n) {
+        var t, c, p;
+        t = const_tab[n];
+        p = BigFloatEnv.prec;
+        if (t && t.prec == p) {
+            return t.val;
+        } else {
+            switch(n) {
+            case 0: c = Float.exp(1); break;
+            case 1: c = Float.log(10); break;
+//            case 2: c = Float.log(2); break;
+            case 3: c = 1/Float.log(2); break;
+            case 4: c = 1/Float.log(10); break;
+//            case 5: c = Float.atan(1) * 4; break;
+            case 6: c = Float.sqrt(0.5); break;
+            case 7: c = Float.sqrt(2); break;
+            }
+            if (p <= 1024) {
+                const_tab[n] = { prec: p, val: c };
+            }
+            return c;
+        }
+    }
+    
+    add_props(Float, {
+        isFloat(a) {
+            return typeof a === "number" || typeof a === "bigfloat";
+        },
+        bestappr(u, b) {
+            var num1, num0, den1, den0, u, num, den, n;
+
+            if (typeof b === "undefined")
+                throw TypeError("second argument expected");
+            num1 = 1;
+            num0 = 0;
+            den1 = 0;
+            den0 = 1;
+            for(;;) {
+                n = Integer(Float.floor(u));
+                num = n * num1 + num0;
+                den = n * den1 + den0;
+                if (den > b)
+                    break;
+                u = 1.0 / (u - n);
+                num0 = num1;
+                num1 = num;
+                den0 = den1;
+                den1 = den;
+            }
+            return Fraction(num1, den1);
+        },
+        /* similar constants as Math.x */
+        get E() { return get_const(0); },
+        get LN10() { return get_const(1); },
+//        get LN2() { return get_const(2); },
+        get LOG2E() { return get_const(3); },
+        get LOG10E() { return get_const(4); },
+//        get PI() { return get_const(5); },
+        get SQRT1_2() { return get_const(6); },
+        get SQRT2() { return get_const(7); },
+    });
+
+    add_props(Float, {
+        [Symbol.operatorOrder]: OT_FLOAT,
+        /* operators are needed for fractions */
+        [Symbol.operatorAdd](a, b) {
+            return Float(a) + Float(b);
+        },
+        [Symbol.operatorSub](a, b) {
+            return Float(a) - Float(b);
+        },
+        [Symbol.operatorMul](a, b) {
+            return Float(a) * Float(b);
+        },
+        [Symbol.operatorDiv](a, b) {
+            return Float(a) / Float(b);
+        },
+        [Symbol.operatorPow](a, b) {
+            return Float(a) ** Float(b);
+        },
+    });
+              
+    add_props(Float.prototype, {
+        inverse() {
+            return 1.0 / this;
+        },
+        norm2() {
+            return this * this;
+        },
+        abs() {
+            return Math.abs(this);
+        },
+        conj() {
+            return this;
+        },
+        arg() {
+            if (this >= 0)
+                return 0;
+            else
+                return Float.PI;
+        },
+        exp() {
+            return Float.exp(this);
+        },
+        log() {
+            if (this < 0) {
+                return Complex(this).log();
+            } else {
+                return Float.log(this);
+            }
+        },
+    });
+       
+    /* Complex */
+    
+    Complex = function Complex(re, im)
+    {
+        var obj;
+        if (new.target)
+            throw TypeError("not a constructor");
+        if (re instanceof Complex)
+            return re;
+        if (typeof im === "undefined") {
+            im = 0;
+        }
+        obj = Object.create(Complex.prototype);
+        obj.re = re;
+        obj.im = im;
+        return obj;
+    }
+
+    add_props(Complex, {
+        [Symbol.operatorOrder]: OT_COMPLEX,
+        /* simplify to real number when possible */
+        toComplex(re, im) {
+            if (algebraicMode && im == 0)
+                return re;
+            else
+                return Complex(re, im);
+        },
+        
+        [Symbol.operatorAdd](a, b) {
+            a = Complex(a);
+            b = Complex(b);
+            return Complex.toComplex(a.re + b.re, a.im + b.im);
+        },
+        [Symbol.operatorSub](a, b) {
+            a = Complex(a);
+            b = Complex(b);
+            return Complex.toComplex(a.re - b.re, a.im - b.im);
+        },
+        [Symbol.operatorMul](a, b) {
+            a = Complex(a);
+            b = Complex(b);
+            return Complex.toComplex(a.re * b.re - a.im * b.im,
+                                     a.re * b.im + a.im * b.re);
+        },
+        [Symbol.operatorDiv](a, b) {
+            a = Complex(a);
+            b = Complex(b);
+            return a * b.inverse();
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorCmpEQ](a, b) {
+            a = Complex(a);
+            b = Complex(b);
+            return a.re == b.re && a.im == b.im;
+        }
+    });
+
+    add_props(Complex.prototype, {
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            return Complex(-this.re, -this.im);
+        },
+        inverse() {
+            var c = this.norm2();
+            return Complex(this.re / c, -this.im / c);
+        },
+        toString() {
+            var v, s = "", a = this;
+            if (a.re != 0)
+                s += a.re.toString();
+            if (a.im == 1) {
+                if (s != "")
+                    s += "+";
+                s += "I";
+            } else if (a.im == -1) {
+                s += "-I";
+            } else {
+                v = a.im.toString();
+                if (v[0] != "-" && s != "")
+                    s += "+";
+                s += v + "*I";
+            }
+            return s;
+        },
+        norm2() {
+            return this.re * this.re + this.im * this.im;
+        },
+        abs() {
+            return Float.sqrt(norm2(this));
+        },
+        conj() {
+            return Complex(this.re, -this.im);
+        },
+        arg() {
+            return Float.atan2(this.im, this.re);
+        },
+        exp() {
+            var arg = this.im, r = this.re.exp();
+            return Complex(r * cos(arg), r * sin(arg));
+        },
+        log() {
+            return Complex(abs(this).log(), atan2(this.im, this.re));
+        },
+    });
+
+    /* Mod */
+
+    Mod = function Mod(a, m) {
+        var obj, t;
+        if (new.target)
+            throw TypeError("not a constructor");
+        obj = Object.create(Mod.prototype);
+        if (Integer.isInteger(m)) {
+            if (m <= 0)
+                throw RangeError("the modulo cannot be <= 0");
+            if (Integer.isInteger(a)) {
+                a %= m;
+            } else if (a instanceof Fraction) {
+                return Mod(a.num, m) / a.den;
+            } else {
+                throw TypeError("invalid types");
+            }
+        } else {
+            throw TypeError("invalid types");
+        }
+        obj.res = a;
+        obj.mod = m;
+        return obj;
+    };
+
+    add_props(Mod.prototype, {
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            return Mod(-this.res, this.mod);
+        },
+        inverse() {
+            var a = this, m = a.mod;
+            if (Integer.isInteger(m)) {
+                return Mod(Integer.invmod(a.res, m), m);
+            } else {
+                throw TypeError("unsupported type");
+            }
+        },
+        toString() {
+            return "Mod(" + this.res + "," + this.mod + ")";
+        },
+    });
+
+    add_props(Mod, {
+        [Symbol.operatorOrder]: OT_MOD,
+        [Symbol.operatorAdd](a, b) {
+            if (!(a instanceof Mod)) {
+                return Mod(a + b.res, b.mod);
+            } else if (!(b instanceof Mod)) {
+                return Mod(a.res + b, a.mod);
+            } else {
+                if (a.mod != b.mod)
+                    throw TypeError("different modulo for binary operator");
+                return Mod(a.res + b.res, a.mod);
+            }
+        },
+        [Symbol.operatorSub](a, b) {
+            if (!(a instanceof Mod)) {
+                return Mod(a - b.res, b.mod);
+            } else if (!(b instanceof Mod)) {
+                return Mod(a.res - b, a.mod);
+            } else {
+                if (a.mod != b.mod)
+                    throw TypeError("different modulo for binary operator");
+                return Mod(a.res - b.res, a.mod);
+            }
+        },
+        [Symbol.operatorMul](a, b) {
+            if (!(a instanceof Mod)) {
+                return Mod(a * b.res, b.mod);
+            } else if (!(b instanceof Mod)) {
+                return Mod(a.res * b, a.mod);
+            } else {
+                if (a.mod != b.mod)
+                    throw TypeError("different modulo for binary operator");
+                return Mod(a.res * b.res, a.mod);
+            }
+        },
+        [Symbol.operatorDiv](a, b) {
+            if (!(b instanceof Mod))
+                b = Mod(b, a.mod);
+            return Mod[Symbol.operatorMul](a, b.inverse());
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorCmpEQ](a, b) {
+            if (!(a instanceof Mod) ||
+                !(b instanceof Mod))
+                return false;
+            return (a.mod == b.mod && a.res == b.res);
+        }
+    });
+            
+    /* Polynomial */
+
+    Polynomial = function Polynomial(a)
+    {
+        if (new.target)
+            throw TypeError("not a constructor");
+        if (a instanceof Polynomial) {
+            return a;
+        } else if (Array.isArray(a)) {
+            if (a.length == 0)
+                a = [ 0 ];
+            Object.setPrototypeOf(a, Polynomial.prototype);
+            return a.trim();
+        } else if (a.constructor[Symbol.operatorOrder] <= OT_MOD) {
+            a = [a];
+            Object.setPrototypeOf(a, Polynomial.prototype);
+            return a;
+        } else {
+            throw TypeError("invalid type");
+        }
+    }
+
+    function number_need_paren(c)
+    {
+        return !(Integer.isInteger(c) ||
+                 Float.isFloat(c) ||
+                 c instanceof Fraction ||
+                 (c instanceof Complex && c.re == 0));
+    }
+
+    /* string for c*X^i */
+    function monomial_toString(c, i)
+    {
+        var str1;
+        if (i == 0) {
+            str1 = c.toString();
+        } else {
+            if (c == 1) {
+                str1 = "";
+            } else if (c == -1) {
+                str1 = "-";
+            } else {
+                if (number_need_paren(c)) {
+                    str1 = "(" + c + ")";
+                } else {
+                    str1 = String(c);
+                }
+                str1 += "*";
+            }
+            str1 += "X";
+            if (i != 1) {
+                str1 += "^" + i;
+            }
+        }
+        return str1;
+    }
+
+    /* find one complex root of 'p' starting from z at precision eps using
+       at most max_it iterations. Return null if could not find root. */
+    function poly_root_laguerre1(p, z, max_it)
+    {
+        var p1, p2, i, z0, z1, z2, d, t0, t1, d1, d2, e, el, zl;
+
+        d = p.deg();
+        if (d == 1) {
+            /* monomial case */
+            return -p[0] / p[1];
+        }
+        /* trivial zero */
+        if (p[0] == 0)
+            return 0.0;
+        
+        p1 = p.deriv();
+        p2 = p1.deriv();
+        el = 0.0;
+        zl = 0.0;
+        for(i = 0; i < max_it; i++) {
+            z0 = p.apply(z);
+            if (z0 == 0)
+                return z; /* simple exit case */
+
+            /* Ward stopping criteria */
+            e = abs(z - zl);
+//            print("e", i, e);
+            if (i >= 2 && e >= el) {
+                if (abs(zl) < 1e-4) {
+                    if (e < 1e-7)
+                        return zl;
+                } else {
+                    if (e < abs(zl) * 1e-3)
+                        return zl;
+                }
+            }
+            el = e;
+            zl = z;
+            
+            z1 = p1.apply(z);
+            z2 = p2.apply(z);
+            t0 = (d - 1) * z1;
+            t0 = t0 * t0;
+            t1 = d * (d - 1) * z0 * z2;
+            t0 = sqrt(t0 - t1);
+            d1 = z1 + t0;
+            d2 = z1 - t0;
+            if (norm2(d2) > norm2(d1))
+                d1 = d2;
+            if (d1 == 0) 
+                return null;
+            z = z - d * z0 / d1;
+        }
+        return null;
+    }
+
+    function poly_roots(p)
+    {
+        var d, i, roots, j, z, eps;
+        var start_points = [ 0.1, -1.4, 1.7 ];
+
+        if (!(p instanceof Polynomial))
+            throw TypeError("polynomial expected");
+        d = p.deg();
+        if (d <= 0)
+            return [];
+        eps = 2.0 ^ (-BigFloatEnv.prec);
+        roots = [];
+        for(i = 0; i < d; i++) {
+            /* XXX: should select another start point if error */
+            for(j = 0; j < 3; j++) {
+                z = poly_root_laguerre1(p, start_points[j], 100);
+                if (z !== null)
+                    break;
+            }
+            if (j == 3)
+                throw RangeError("error in root finding algorithm");
+            roots[i] = z;
+            p = Polynomial.divrem(p, X - z)[0];
+        }
+        return roots;
+    }
+
+    add_props(Polynomial.prototype, {
+        trim() {
+            var a = this, i;
+            i = a.length;
+            while (i > 1 && a[i - 1] == 0)
+                i--;
+            a.length = i;
+            return a;
+        },
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            var r, i, n, a;
+            a = this;
+            n = a.length;
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = -a[i];
+            return Polynomial(r);
+        },
+        conj() {
+            var r, i, n, a;
+            a = this;
+            n = a.length;
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = a[i].conj();
+            return Polynomial(r);
+        },
+        inverse() {
+            return RationalFunction(Polynomial([1]), this);
+        },
+        toString() {
+            var i, str, str1, c, a = this;
+            if (a.length == 1) {
+                return a[0].toString();
+            }
+            str="";
+            for(i = a.length - 1; i >= 0; i--) {
+                c = a[i];
+                if (c == 0 ||
+                    (c instanceof Mod) && c.res == 0)
+                    continue;
+                str1 = monomial_toString(c, i);
+                if (str1[0] != "-") {
+                    if (str != "")
+                        str += "+";
+                }
+                str += str1;
+            }
+            return str;
+        },
+        deg() {
+            if (this.length == 1 && this[0] == 0)
+                return -Infinity;
+            else
+                return this.length - 1;
+        },
+        apply(b) {
+            var i, n, r, a = this;
+            n = a.length - 1;
+            r = a[n];
+            while (n > 0) {
+                n--;
+                r = r * b + a[n];
+            }
+            return r;
+        },
+        deriv() {
+            var a = this, n, r, i;
+            n = a.length;
+            if (n == 1) {
+                return Polynomial(0);
+            } else {
+                r = [];
+                for(i = 1; i < n; i++) {
+                    r[i - 1] = i * a[i];
+                }
+                return Polynomial(r);
+            }
+        },
+        integ() {
+            var a = this, n, r, i;
+            n = a.length;
+            r = [0];
+            for(i = 0; i < n; i++) {
+                r[i + 1] = a[i] / (i + 1);
+            }
+            return Polynomial(r);
+        },
+        norm2() {
+            var a = this, n, r, i;
+            n = a.length;
+            r = 0;
+            for(i = 0; i < n; i++) {
+                r += a[i].norm2();
+            }
+            return r;
+        },
+    });
+
+    add_props(Polynomial, {
+        [Symbol.operatorOrder]: OT_POLY,
+        [Symbol.operatorAdd](a, b) {
+            var tmp, r, i, n1, n2;
+            a = Polynomial(a);
+            b = Polynomial(b);
+            if (a.length < b.length) {
+                tmp = a;
+                a = b;
+                b = tmp;
+            }
+            n1 = b.length;
+            n2 = a.length;
+            r = [];
+            for(i = 0; i < n1; i++)
+                r[i] = a[i] + b[i];
+            for(i = n1; i < n2; i++)
+                r[i] = a[i];
+            return Polynomial(r);
+        },
+        [Symbol.operatorSub](a, b) {
+            return Polynomial[Symbol.operatorAdd](a, -b);
+        },
+        [Symbol.operatorMul](a, b) {
+            var i, j, n1, n2, n, r;
+            a = Polynomial(a);
+            b = Polynomial(b);
+            n1 = a.length;
+            n2 = b.length;
+            n = n1 + n2 - 1;
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = 0;
+            for(i = 0; i < n1; i++) {
+                for(j = 0; j < n2; j++) {
+                    r[i + j] += a[i] * b[j];
+                }
+            }
+            return Polynomial(r);
+        },
+        [Symbol.operatorDiv](a, b) {
+            if (b.constructor[Symbol.operatorOrder] <= OT_COMPLEX)
+                return a * (1 / b);
+            else
+                return RationalFunction(Polynomial(a),
+                                        Polynomial(b));
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorMathMod](a, b) {
+            return Polynomial.divrem(a, b)[1];
+        },
+        [Symbol.operatorMod](a, b) {
+            return Polynomial.divrem(a, b)[1];
+        },
+        [Symbol.operatorCmpEQ](a, b) {
+            var n, i;
+            if (!(a instanceof Polynomial) ||
+                !(b instanceof Polynomial))
+                return false;
+            n = a.length;
+            if (n != b.length)
+                return false;
+            for(i = 0; i < n; i++) {
+                if (a[i] != b[i])
+                    return false;
+            }
+            return true;
+        },
+        divrem(a, b) {
+            var n1, n2, i, j, q, r, n, c;
+            if (b.deg() < 0)
+                throw RangeError("division by zero");
+            n1 = a.length;
+            n2 = b.length;
+            if (n1 < n2)
+                return [Polynomial([0]), a];
+            r = Array.prototype.dup.call(a);
+            q = [];
+            n2--;
+            n = n1 - n2;
+            for(i = 0; i < n; i++)
+                q[i] = 0;
+            for(i = n - 1; i >= 0; i--) {
+                c = r[i + n2];
+                if (c != 0) {
+                    c = c / b[n2];
+                    r[i + n2] = 0;
+                    for(j = 0; j < n2; j++) {
+                        r[i + j] -= b[j] * c;
+                    }
+                    q[i] = c;
+                }
+            }
+            return [Polynomial(q), Polynomial(r)];
+        },
+        gcd(a, b) {
+            var t;
+            while (b.deg() >= 0) {
+                t = Polynomial.divrem(a, b);
+                a = b;
+                b = t[1];
+            }
+            /* convert to monic form */
+            return a / a[a.length - 1];
+        },
+        invmod(x, y) {
+            var q, u, v, a, c, t;
+            u = x;
+            v = y;
+            c = Polynomial([1]);
+            a = Polynomial([0]);
+            while (u.deg() >= 0) {
+                t = Polynomial.divrem(v, u);
+                q = t[0];
+                v = u;
+                u = t[1];
+                t = c;
+                c = a - q * c;
+                a = t;
+            }
+            /* v = gcd(x, y) */
+            if (v.deg() > 0)
+                throw RangeError("not invertible");
+            return Polynomial.divrem(a, y)[1];
+        },
+        roots(p) {
+            return poly_roots(p);
+        }
+    });
+
+    /* Polynomial Modulo Q */
+
+    PolyMod = function PolyMod(a, m) {
+        var obj, t;
+        if (new.target)
+            throw TypeError("not a constructor");
+        obj = Object.create(PolyMod.prototype);
+        if (m instanceof Polynomial) {
+            if (m.deg() <= 0)
+                throw RangeError("the modulo cannot have a degree <= 0");
+            if (a instanceof RationalFunction) {
+                return PolyMod(a.num, m) / a.den;
+            } else {
+                a = Polynomial(a);
+                t = Polynomial.divrem(a, m);
+                a = t[1];
+            }
+        } else {
+            throw TypeError("invalid types");
+        }
+        obj.res = a;
+        obj.mod = m;
+        return obj;
+    };
+
+    add_props(PolyMod.prototype, {
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            return PolyMod(-this.res, this.mod);
+        },
+        inverse() {
+            var a = this, m = a.mod;
+            if (m instanceof Polynomial) {
+                return PolyMod(Polynomial.invmod(a.res, m), m);
+            } else {
+                throw TypeError("unsupported type");
+            }
+        },
+        toString() {
+            return "PolyMod(" + this.res + "," + this.mod + ")";
+        },
+    });
+
+    add_props(PolyMod, {
+        [Symbol.operatorOrder]: OT_POLYMOD,
+        [Symbol.operatorAdd](a, b) {
+            if (!(a instanceof PolyMod)) {
+                return PolyMod(a + b.res, b.mod);
+            } else if (!(b instanceof PolyMod)) {
+                return PolyMod(a.res + b, a.mod);
+            } else {
+                if (a.mod != b.mod)
+                    throw TypeError("different modulo for binary operator");
+                return PolyMod(a.res + b.res, a.mod);
+            }
+        },
+        [Symbol.operatorSub](a, b) {
+            if (!(a instanceof PolyMod)) {
+                return PolyMod(a - b.res, b.mod);
+            } else if (!(b instanceof PolyMod)) {
+                return PolyMod(a.res - b, a.mod);
+            } else {
+                if (a.mod != b.mod)
+                    throw TypeError("different modulo for binary operator");
+                return PolyMod(a.res - b.res, a.mod);
+            }
+        },
+        [Symbol.operatorMul](a, b) {
+            if (!(a instanceof PolyMod)) {
+                return PolyMod(a * b.res, b.mod);
+            } else if (!(b instanceof PolyMod)) {
+                return PolyMod(a.res * b, a.mod);
+            } else {
+                if (a.mod != b.mod)
+                    throw TypeError("different modulo for binary operator");
+                return PolyMod(a.res * b.res, a.mod);
+            }
+        },
+        [Symbol.operatorDiv](a, b) {
+            if (!(b instanceof PolyMod))
+                b = PolyMod(b, a.mod);
+            return PolyMod[Symbol.operatorMul](a, b.inverse());
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorCmpEQ](a, b) {
+            if (!(a instanceof PolyMod) ||
+                !(b instanceof PolyMod))
+                return false;
+            return (a.mod == b.mod && a.res == b.res);
+        }
+    });
+
+    /* Rational function */
+    
+    RationalFunction = function RationalFunction(a, b)
+    {
+        var t, r, d, obj;
+        if (new.target)
+            throw TypeError("not a constructor");
+        if (!(a instanceof Polynomial) ||
+            !(b instanceof Polynomial))
+            throw TypeError("polynomial expected");
+        t = Polynomial.divrem(a, b);
+        r = t[1];
+        if (r.deg() < 0)
+            return t[0]; /* no need for a fraction */
+        d = Polynomial.gcd(b, r);
+        if (d.deg() > 0) {
+            a = Polynomial.divrem(a, d)[0];
+            b = Polynomial.divrem(b, d)[0];
+        }
+        obj = Object.create(RationalFunction.prototype);
+        obj.num = a;
+        obj.den = b;
+        return obj;
+    }
+
+    add_props(RationalFunction.prototype, {
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            return RationalFunction(-this.num, this.den);
+        },
+        inverse() {
+            return RationalFunction(this.den, this.num);
+        },
+        conj() {
+            return RationalFunction(this.num.conj(), this.den.conj());
+        },
+        toString() {
+            var str;
+            if (this.num.deg() <= 0 &&
+                !number_need_paren(this.num[0]))
+                str = this.num.toString();
+            else
+                str = "(" + this.num.toString() + ")";
+            str += "/(" + this.den.toString() + ")"
+            return str;
+        },
+        apply(b) {
+            return this.num.apply(b) / this.den.apply(b);
+        },
+        deriv() {
+            var n = this.num, d = this.den;
+            return RationalFunction(n.deriv() * d - n * d.deriv(), d * d);
+        },
+    });
+
+    add_props(RationalFunction, {
+        [Symbol.operatorOrder]: OT_RFUNC,
+        /* This function always return a RationalFunction object even
+           if it could simplified to a polynomial, so it is not
+           equivalent to RationalFunction(a) */
+        toRationalFunction(a) {
+            var obj;
+            if (a instanceof RationalFunction) {
+                return a;
+            } else {
+                obj = Object.create(RationalFunction.prototype);
+                obj.num = Polynomial(a);
+                obj.den = Polynomial(1);
+                return obj;
+            }
+        },
+        [Symbol.operatorAdd](a, b) {
+            a = RationalFunction.toRationalFunction(a);
+            b = RationalFunction.toRationalFunction(b);
+            return RationalFunction(a.num * b.den + a.den * b.num, a.den * b.den);
+        },
+        [Symbol.operatorSub](a, b) {
+            a = RationalFunction.toRationalFunction(a);
+            b = RationalFunction.toRationalFunction(b);
+            return RationalFunction(a.num * b.den - a.den * b.num, a.den * b.den);
+        },
+        [Symbol.operatorMul](a, b) {
+            a = RationalFunction.toRationalFunction(a);
+            b = RationalFunction.toRationalFunction(b);
+            return RationalFunction(a.num * b.num, a.den * b.den);
+        },
+        [Symbol.operatorDiv](a, b) {
+            a = RationalFunction.toRationalFunction(a);
+            b = RationalFunction.toRationalFunction(b);
+            return RationalFunction(a.num * b.den, a.den * b.num);
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorCmpEQ](a, b) {
+            a = RationalFunction.toRationalFunction(a);
+            b = RationalFunction.toRationalFunction(b);
+            /* we assume the fractions are normalized */
+            return (a.num == b.num && a.den == b.den);
+        },
+    });
+              
+    /* Power series */
+
+    /* 'a' is an array */
+    function get_emin(a) {
+        var i, n;
+        n = a.length;
+        for(i = 0; i < n; i++) {
+            if (a[i] != 0)
+                return i;
+        }
+        return n;
+    };
+    
+    /* n is the maximum number of terms if 'a' is not a serie */
+    Series = function Series(a, n) {
+        var emin, r, i;
+        
+        if (a instanceof Series) {
+            return a;
+        } else if (a.constructor[Symbol.operatorOrder] <= OT_POLY) {
+            if (n <= 0) {
+                /* XXX: should still use the polynomial degree */
+                return Series.zero(0, 0);
+            } else {
+                a = Polynomial(a);
+                emin = get_emin(a);
+                r = Series.zero(n, emin);
+                n = Math.min(a.length - emin, n);
+                for(i = 0; i < n; i++)
+                    r[i] = a[i + emin];
+                return r;
+            }
+        } else if (a instanceof RationalFunction) {
+            return Series(a.num, n) / a.den;
+        } else {
+            throw TypeError("invalid type");
+        }
+    };
+
+    add_props(Series.prototype, {
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            var obj, n, i;
+            n = this.length;
+            obj = Series.zero(this.length, this.emin);
+            for(i = 0; i < n; i++) {
+                obj[i] = -this[i];
+            }
+            return obj;
+        },
+        conj() {
+            var obj, n, i;
+            n = this.length;
+            obj = Series.zero(this.length, this.emin);
+            for(i = 0; i < n; i++) {
+                obj[i] = this[i].conj();
+            }
+            return obj;
+        },
+        inverse() {
+            var r, n, i, j, sum, v1 = this;
+            n = v1.length;
+            if (n == 0)
+                throw RangeError("division by zero");
+            r = Series.zero(n, -v1.emin);
+            r[0] = 1 / v1[0];
+            for(i = 1; i < n; i++) {
+                sum = 0;
+                for(j = 1; j <= i; j++) {
+                    sum += v1[j] * r[i - j];
+                }
+                r[i] = -sum * r[0];
+            }
+            return r;
+        },
+        /* remove leading zero terms */
+        trim() {
+            var i, j, n, r, v1 = this;
+            n = v1.length;
+            i = 0;
+            while (i < n && v1[i] == 0)
+                i++;
+            if (i == 0)
+                return v1;
+            for(j = i; j < n; j++)
+                v1[j - i] = v1[j];
+            v1.length = n - i;
+            v1.__proto__.emin += i;
+            return v1;
+        },
+        toString() {
+            var i, j, str, str1, c, a = this, emin, n;
+            str="";
+            emin = this.emin;
+            n = this.length;
+            for(j = 0; j < n; j++) {
+                i = j + emin;
+                c = a[j];
+                if (c != 0) {
+                    str1 = monomial_toString(c, i);
+                    if (str1[0] != "-") {
+                        if (str != "")
+                            str += "+";
+                    }
+                    str += str1;
+                }
+            }
+            if (str != "")
+                str += "+";
+            str += "O(" + monomial_toString(1, n + emin) + ")";
+            return str;
+        },
+        apply(b) {
+            var i, n, r, a = this;
+            n = a.length;
+            if (n == 0)
+                return 0;
+            r = a[--n];
+            while (n > 0) {
+                n--;
+                r = r * b + a[n];
+            }
+            if (a.emin != 0)
+                r *= b ^ a.emin;
+            return r;
+        },
+        deriv() {
+            var a = this, n = a.length, emin = a.emin, r, i, j;
+            if (n == 0 && emin == 0) {
+                return Series.zero(0, 0);
+            } else {
+                r = Series.zero(n, emin - 1);
+                for(i = 0; i < n; i++) {
+                    j = emin + i;
+                    if (j == 0)
+                        r[i] = 0;
+                    else
+                        r[i] = j * a[i];
+                }
+                return r.trim();
+            }
+        },
+        integ() {
+            var a = this, n = a.length, emin = a.emin, i, j, r;
+            r = Series.zero(n, emin + 1);
+            for(i = 0; i < n; i++) {
+                j = emin + i;
+                if (j == -1) {
+                    if (a[i] != 0)
+                        throw RangError("cannot represent integ(1/X)");
+                } else {
+                    r[i] = a[i] / (j + 1);
+                }
+            }
+            return r.trim();
+        },
+        exp() {
+            var c, i, r, n, a = this;
+            if (a.emin < 0)
+                throw RangeError("negative exponent in exp");
+            n = a.emin + a.length;
+            if (a.emin > 0 || a[0] == 0) {
+                c = 1;
+            } else {
+                c = global.exp(a[0]);
+                a -= a[0];
+            }
+            r = Series.zero(n, 0);
+            for(i = 0; i < n; i++) {
+                r[i] = c / fact(i);
+            }
+            return r.apply(a);
+        },
+        log() {
+            var a = this, r;
+            if (a.emin != 0)
+                throw Range("log argument must have a non zero constant term");
+            r = integ(deriv(a) / a);
+            /* add the constant term */
+            r += global.log(a[0]);
+            return r;
+        },
+    });
+
+    add_props(Series, {
+        [Symbol.operatorOrder]: OT_SERIES,
+        /* new series of length n and first exponent emin */
+        zero(n, emin) {
+            var r, i, obj;
+
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = 0;
+            /* we return an array and store emin in its prototype */
+            obj = Object.create(Series.prototype);
+            obj.emin = emin;
+            Object.setPrototypeOf(r, obj);
+            return r;
+        },
+        [Symbol.operatorAdd](v1, v2) {
+            var tmp, d, emin, n, r, i, j, v2_emin, c1, c2;
+            if (!(v1 instanceof Series)) {
+                tmp = v1;
+                v1 = v2;
+                v2 = tmp;
+            }
+            d = v1.emin + v1.length;
+            if (v2.constructor[Symbol.operatorOrder] <= OT_POLY) {
+                v2 = Polynomial(v2);
+                if (d <= 0)
+                    return v1;
+                v2_emin = 0;
+            } else if (v2 instanceof RationalFunction) {
+                /* compute the emin of the rational fonction */
+                i = get_emin(v2.num) - get_emin(v2.den);
+                if (d <= i)
+                    return v1;
+                /* compute the serie with the required terms */
+                v2 = Series(v2, d - i);
+                v2_emin = v2.emin;
+            } else {
+                v2_emin = v2.emin;
+                d = Math.min(d, v2_emin + v2.length);
+            }
+            emin = Math.min(v1.emin, v2_emin);
+            n = d - emin;
+            r = Series.zero(n, emin);
+            /* XXX: slow */
+            for(i = emin; i < d; i++) {
+                j = i - v1.emin;
+                if (j >= 0 && j < v1.length)
+                    c1 = v1[j];
+                else
+                    c1 = 0;
+                j = i - v2_emin;
+                if (j >= 0 && j < v2.length)
+                    c2 = v2[j];
+                else
+                    c2 = 0;
+                r[i - emin] = c1 + c2;
+            }
+            return r.trim();
+        },
+        [Symbol.operatorSub](a, b) {
+            return Series[Symbol.operatorAdd](a, -b);
+        },
+        [Symbol.operatorMul](v1, v2) {
+            var n, i, j, r, n, emin, n1, n2, k;
+            if (!(v1 instanceof Series))
+                v1 = Series(v1, v2.length);
+            else if (!(v2 instanceof Series))
+                v2 = Series(v2, v1.length);
+            emin = v1.emin + v2.emin;
+            n = Math.min(v1.length, v2.length);
+            n1 = v1.length;
+            n2 = v2.length;
+            r = Series.zero(n, emin);
+            for(i = 0; i < n1; i++) {
+                k = Math.min(n2, n - i);
+                for(j = 0; j < k; j++) {
+                    r[i + j] += v1[i] * v2[j];
+                }
+            }
+            return r.trim();
+        },
+        [Symbol.operatorDiv](v1, v2) {
+            if (!(v2 instanceof Series))
+                v2 = Series(v2, v1.length);
+            return Series[Symbol.operatorMul](v1, v2.inverse());
+        },
+        [Symbol.operatorPow](a, b) {
+            if (Integer.isInteger(b)) {
+                return generic_pow(a, b);
+            } else {
+                if (!(a instanceof Series))
+                    a = Series(a, b.length);
+                return exp(log(a) * b);
+            }
+        },
+        [Symbol.operatorCmpEQ](a, b) {
+            var n, i;
+            if (!(a instanceof Series) ||
+                !(b instanceof Series))
+                return false;
+            if (a.emin != b.emin)
+                return false;
+            n = a.length;
+            if (n != b.length)
+                return false;
+            for(i = 0; i < n; i++) {
+                if (a[i] != b[i])
+                    return false;
+            }
+            return true;
+        },
+        O(a) {
+            function ErrorO() {
+                return TypeError("invalid O() argument");
+            }
+            var n;
+            if (a.constructor[Symbol.operatorOrder] <= OT_POLY) {
+                a = Polynomial(a);
+                n = a.deg();
+                if (n < 0)
+                    throw ErrorO();
+            } else if (a instanceof RationalFunction) {
+                if (a.num.deg() != 0)
+                    throw ErrorO();
+                n = a.den.deg();
+                if (n < 0)
+                    throw ErrorO();
+                n = -n;
+            } else
+                throw ErrorO();
+            return Series.zero(0, n);
+        },
+    });
+    
+    /* Array (Matrix) */
+
+    Matrix = function Matrix(h, w) {
+        var i, j, r, rl;
+        if (typeof w === "undefined")
+            w = h;
+        r = [];
+        for(i = 0; i < h; i++) {
+            rl = [];
+            for(j = 0; j < w; j++)
+                rl[j] = 0;
+            r[i] = rl;
+        }
+        return r;
+    };
+
+    add_props(Matrix, {
+        idn(n) {
+            var r, i;
+            r = Matrix(n, n);
+            for(i = 0; i < n; i++)
+                r[i][i] = 1;
+            return r;
+        },
+        diag(a) {
+            var r, i, n;
+            n = a.length;
+            r = Matrix(n, n);
+            for(i = 0; i < n; i++)
+                r[i][i] = a[i];
+            return r;
+        },
+        hilbert(n) {
+            var i, j, r;
+            r = Matrix(n);
+            for(i = 0; i < n; i++) {
+                for(j = 0; j < n; j++) {
+                    r[i][j] = 1 / (1 + i + j);
+                }
+            }
+            return r;
+        },
+        trans(a) {
+            var h, w, r, i, j;
+            if (!Array.isArray(a))
+                throw TypeError("matrix expected");
+            h = a.length;
+            if (!Array.isArray(a[0])) {
+                w = 1;
+                r = Matrix(w, h);
+                for(i = 0; i < h; i++) {
+                    r[0][i] = a[i];
+                }
+            } else {
+                w = a[0].length;
+                r = Matrix(w, h);
+                for(i = 0; i < h; i++) {
+                    for(j = 0; j < w; j++) {
+                        r[j][i] = a[i][j];
+                    }
+                }
+            }
+            return r;
+        },
+        check_square(a) {
+            var a, n;
+            if (!Array.isArray(a))
+                throw TypeError("array expected");
+            n = a.length;
+            if (!Array.isArray(a[0]) || n != a[0].length)
+                throw TypeError("square matrix expected");
+            return n;
+        },
+        trace(a) {
+            var n, r, i;
+            n = Matrix.check_square(a);
+            r = a[0][0];
+            for(i = 1; i < n; i++) {
+                r += a[i][i];
+            }
+            return r;
+        },
+        charpoly(a) {
+            var n, p, c, i, j, coef;
+            n = Matrix.check_square(a);
+            p = [];
+            for(i = 0; i < n + 1; i++)
+                p[i] = 0;
+            p[n] = 1;
+            c = Matrix.idn(n);
+            for(i = 0; i < n; i++) {
+                c = c * a;
+                coef = -trace(c) / (i + 1);
+                p[n - i - 1] = coef;
+                for(j = 0; j < n; j++)
+                    c[j][j] += coef;
+            }
+            return Polynomial(p);
+        },
+        eigenvals(a) {
+            return Polynomial.roots(Matrix.charpoly(a));
+        },
+        det(a) {
+            var n, i, j, k, s, src, v, c;
+            
+            n = Matrix.check_square(a);
+            s = 1;
+            src = a.dup();
+            for(i=0;i<n;i++) {
+                for(j = i; j < n; j++) {
+                    if (src[j][i] != 0)
+                        break;
+                }
+                if (j == n)
+                    return 0;
+                if (j != i) {
+                    for(k = 0;k < n; k++) {
+                        v = src[j][k];
+                        src[j][k] = src[i][k];
+                        src[i][k] = v;
+                    }
+                    s = -s;
+                }
+                c = src[i][i].inverse();
+                for(j = i + 1; j < n; j++) {
+                    v = c * src[j][i];
+                    for(k = 0;k < n; k++) {
+                        src[j][k] -= src[i][k] * v;
+                    }
+                }
+            }
+            c = s;
+            for(i=0;i<n;i++)
+                c *= src[i][i];
+            return c;
+        },
+        inverse(a) {
+            var n, dst, src, i, j, k, n2, r, c, v;
+            n = Matrix.check_square(a);
+            src = a.dup();
+            dst = Matrix.idn(n);
+            for(i=0;i<n;i++) {
+                for(j = i; j < n; j++) {
+                    if (src[j][i] != 0)
+                        break;
+                }
+                if (j == n)
+                    throw RangeError("matrix is not invertible");
+                if (j != i) {
+                    /* swap lines in src and dst */
+                    v = src[j];
+                    src[j] = src[i];
+                    src[i] = v;
+                    v = dst[j];
+                    dst[j] = dst[i];
+                    dst[i] = v;
+                }
+
+                c = src[i][i].inverse();
+                for(k = 0; k < n; k++) {
+                    src[i][k] *= c;
+                    dst[i][k] *= c;
+                }
+                
+                for(j = 0; j < n; j++) {
+                    if (j != i) {
+                        c = src[j][i];
+                        for(k = i; k < n; k++) {
+                            src[j][k] -= src[i][k] * c;
+                        }
+                        for(k = 0; k < n; k++) {
+                            dst[j][k] -= dst[i][k] * c;
+                        }
+                    }
+                }
+            }
+            return dst;
+        },
+        rank(a) {
+            var src, i, j, k, w, h, l, c;
+            
+            if (!Array.isArray(a) ||
+                !Array.isArray(a[0]))
+                throw TypeError("matrix expected");
+            h = a.length;
+            w = a[0].length;
+            src = a.dup();
+            l = 0;
+            for(i=0;i<w;i++) {
+                for(j = l; j < h; j++) {
+                    if (src[j][i] != 0)
+                        break;
+                }
+                if (j == h)
+                    continue;
+                if (j != l) {
+                    /* swap lines */
+                    for(k = 0; k < w; k++) {
+                        v = src[j][k];
+                        src[j][k] = src[l][k];
+                        src[l][k] = v;
+                    }
+                }
+                
+                c = src[l][i].inverse();
+                for(k = 0; k < w; k++) {
+                    src[l][k] *= c;
+                }
+                
+                for(j = l + 1; j < h; j++) {
+                    c = src[j][i];
+                    for(k = i; k < w; k++) {
+                        src[j][k] -= src[l][k] * c;
+                    }
+                }
+                l++;
+            }
+            return l;
+        },
+        ker(a) {
+            var src, i, j, k, w, h, l, m, r, im_cols, ker_dim, c;
+            
+            if (!Array.isArray(a) ||
+                !Array.isArray(a[0]))
+                throw TypeError("matrix expected");
+            h = a.length;
+            w = a[0].length;
+            src = a.dup();
+            im_cols = [];
+            l = 0;
+            for(i=0;i<w;i++) {
+                im_cols[i] = false;
+                for(j = l; j < h; j++) {
+                    if (src[j][i] != 0)
+                        break;
+                }
+                if (j == h)
+                    continue;
+                im_cols[i] = true;
+                if (j != l) {
+                    /* swap lines */
+                    for(k = 0; k < w; k++) {
+                        v = src[j][k];
+                        src[j][k] = src[l][k];
+                        src[l][k] = v;
+                    }
+                }
+                
+                c = src[l][i].inverse();
+                for(k = 0; k < w; k++) {
+                    src[l][k] *= c;
+                }
+                
+                for(j = 0; j < h; j++) {
+                    if (j != l) {
+                        c = src[j][i];
+                        for(k = i; k < w; k++) {
+                            src[j][k] -= src[l][k] * c;
+                        }
+                    }
+                }
+                l++;
+                //        log_str("m=" + cval_toString(v1) + "\n");
+            }
+            //    log_str("im cols="+im_cols+"\n");
+
+            /* build the kernel vectors */
+            ker_dim = w - l;
+            r = Matrix(w, ker_dim);
+            k = 0;
+            for(i = 0; i < w; i++) {
+                if (!im_cols[i]) {
+                    /* select this column from the matrix */
+                    l = 0;
+                    m = 0;
+                    for(j = 0; j < w; j++) {
+                        if (im_cols[j]) {
+                            r[j][k] = -src[m][i];
+                            m++;
+                        } else {
+                            if (l == k) {
+                                r[j][k] = 1;
+                            } else {
+                                r[j][k] = 0;
+                            }
+                            l++;
+                        }
+                    }
+                    k++;
+                }
+            }
+            return r;
+        },
+        dp(a, b) {
+            var i, n, r;
+            n = a.length;
+            if (n != b.length)
+                throw TypeError("incompatible array length");
+            /* XXX: could do complex product */
+            r = 0;
+            for(i = 0; i < n; i++) {
+                r += a[i] * b[i];
+            }
+            return r;
+        },
+        /* cross product */
+        cp(v1, v2) {
+            var r;
+            if (v1.length != 3 || v2.length != 3)
+                throw TypeError("vectors must have 3 elements");
+            r = [];
+            r[0] = v1[1] * v2[2] - v1[2] * v2[1];
+            r[1] = v1[2] * v2[0] - v1[0] * v2[2];
+            r[2] = v1[0] * v2[1] - v1[1] * v2[0];
+            return r;
+        },
+    });
+
+    add_props(Array, {
+        [Symbol.operatorOrder]: OT_ARRAY,
+        [Symbol.operatorAdd](a, b) {
+            var r, i, n;
+            if (!Array.isArray(a) || !Array.isArray(b))
+                throw TypeError("array expected");
+            n = a.length;
+            if (n != b.length)
+                throw TypeError("incompatible array size");
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = a[i] + b[i];
+            return r;
+        },
+        [Symbol.operatorSub](a, b) {
+            var r, i, n;
+            n = a.length;
+            if (!Array.isArray(a) || !Array.isArray(b))
+                throw TypeError("array expected");
+            if (n != b.length)
+                throw TypeError("incompatible array size");
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = a[i] - b[i];
+            return r;
+        },
+        scalar_mul(a, b) {
+            var r, i, n;
+            n = a.length;
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = a[i] * b;
+            return r;
+        },
+        [Symbol.operatorMul](a, b) {
+            var h, w, l, i, j, k, r, rl, sum, a_mat, b_mat, a_is_array, b_is_array;
+            a_is_array = Array.isArray(a);
+            b_is_array = Array.isArray(b);
+            if (!a_is_array && !b_is_array) {
+                throw TypeError("array expected");
+            } else if (!a_is_array && b_is_array) {
+                return Array.scalar_mul(b, a);
+            } else if (a_is_array && !b_is_array) {
+                return Array.scalar_mul(a, b);
+            }
+            h = a.length;
+            a_mat = Array.isArray(a[0]);
+            if (a_mat) {
+                l = a[0].length;
+            } else {
+                l = 1;
+            }
+            if (l != b.length)
+                throw RangeError("incompatible matrix size");
+            b_mat = Array.isArray(b[0]);
+            if (b_mat)
+                w = b[0].length;
+            else
+                w = 1;
+            r = [];
+            if (a_mat && b_mat) {
+                for(i = 0; i < h; i++) {
+                    rl = [];
+                    for(j = 0; j < w; j++) {
+                        sum = 0;
+                        for(k = 0; k < l; k++) {
+                            sum += a[i][k] * b[k][j];
+                        }
+                        rl[j] = sum;
+                    }
+                    r[i] = rl;
+                }
+            } else if (a_mat && !b_mat) {
+                for(i = 0; i < h; i++) {
+                    sum = 0;
+                    for(k = 0; k < l; k++) {
+                        sum += a[i][k] * b[k];
+                    }
+                    r[i] = sum;
+                }
+            } else if (!a_mat && b_mat) {
+                for(i = 0; i < h; i++) {
+                    rl = [];
+                    for(j = 0; j < w; j++) {
+                        rl[j] = a[i] * b[0][j];
+                    }
+                    r[i] = rl;
+                }
+            } else {
+                for(i = 0; i < h; i++) {
+                    r[i] = a[i] * b[0];
+                }
+            }
+            return r;
+        },
+        [Symbol.operatorDiv](a, b) {
+            return Array[Symbol.operatorMul](a, b.inverse());
+        },
+        [Symbol.operatorPow]: generic_pow,
+        [Symbol.operatorCmpEQ](a, b) {
+            var n, i;
+            n = a.length;
+            if (n != b.length)
+                return false;
+            for(i = 0; i < n; i++) {
+                if (a[i] != b[i])
+                    return false;
+            }
+            return true;
+        },
+    });
+
+    add_props(Array.prototype, {
+        [Symbol.operatorPlus]() {
+            return this;
+        },
+        [Symbol.operatorNeg]() {
+            var i, n, r;
+            n = this.length;
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = -this[i];
+            return r;
+        },
+        conj() {
+            var i, n, r;
+            n = this.length;
+            r = [];
+            for(i = 0; i < n; i++)
+                r[i] = this[i].conj();
+            return r;
+        },
+        dup() {
+            var r, i, n, el, a = this;
+            r = [];
+            n = a.length;
+            for(i = 0; i < n; i++) {
+                el = a[i];
+                if (Array.isArray(el))
+                    el = el.dup();
+                r[i] = el;
+            }
+            return r;
+        },
+        inverse() {
+            return Matrix.inverse(this);
+        },
+        norm2: Polynomial.prototype.norm2,
+    });
+
+})(this);
+
+/* global definitions */
+var I = Complex(0, 1);
+var X = Polynomial([0, 1]);
+var O = Series.O;
+
+Object.defineProperty(this, "PI", { get: function () { return Float.PI } });
+
+/* put frequently used functions in the global context */
+var gcd = Integer.gcd;
+var fact = Integer.fact;
+var comb = Integer.comb;
+var pmod = Integer.pmod;
+var invmod = Integer.invmod;
+var factor = Integer.factor;
+var isprime = Integer.isPrime;
+var nextprime = Integer.nextPrime;
+
+function deriv(a)
+{
+    return a.deriv();
+}
+
+function integ(a)
+{
+    return a.integ();
+}
+
+function norm2(a)
+{
+    return a.norm2();
+}
+
+function abs(a)
+{
+    return a.abs();
+}
+
+function conj(a)
+{
+    return a.conj();
+}
+
+function arg(a)
+{
+    return a.arg();
+}
+
+function inverse(a)
+{
+    return a.inverse();
+}
+
+function trunc(a)
+{
+    if (Integer.isInteger(a)) {
+        return a;
+    } else if (a instanceof Fraction) {
+        return Integer.tdiv(a.num, a.den);
+    } else if (a instanceof Polynomial) {
+        return a;
+    } else if (a instanceof RationalFunction) {
+        return Polynomial.divrem(a.num, a.den)[0];
+    } else {
+        return Float.ceil(a);
+    }
+}
+
+function floor(a)
+{
+    if (Integer.isInteger(a)) {
+        return a;
+    } else if (a instanceof Fraction) {
+        return Integer.fdiv(a.num, a.den);
+    } else {
+        return Float.floor(a);
+    }
+}
+
+function ceil(a)
+{
+    if (Integer.isInteger(a)) {
+        return a;
+    } else if (a instanceof Fraction) {
+        return Integer.cdiv(a.num, a.den);
+    } else {
+        return Float.ceil(a);
+    }
+}
+
+function sqrt(a)
+{
+    var t, u, re, im;
+    if (a instanceof Series) {
+        return a ^ (1/2);
+    } else if (a instanceof Complex) {
+        t = abs(a);
+        u = a.re;
+        re = sqrt((t + u) / 2);
+        im = sqrt((t - u) / 2);
+        if (a.im < 0)
+            im = -im;
+        return Complex.toComplex(re, im);
+    } else {
+        a = Float(a);
+        if (a < 0) {
+            return Complex(0, Float.sqrt(-a));
+        } else {
+            return Float.sqrt(a);
+        }
+    }
+}
+
+function exp(a)
+{
+    return a.exp();
+}
+
+function log(a)
+{
+    return a.log();
+}
+
+function log2(a)
+{
+    return log(a) * Float.LOG2E;
+}
+
+function log10(a)
+{
+    return log(a) * Float.LOG10E;
+}
+
+function todb(a)
+{
+    return log10(a) * 10;
+}
+
+function fromdb(a)
+{
+    return 10 ^ (a / 10);
+}
+
+function sin(a)
+{
+    var t;
+    if (a instanceof Complex || a instanceof Series) {
+        t = exp(a * I);
+        return (t - 1/t) / (2 * I);
+    } else {
+        return Float.sin(Float(a));
+    }
+}
+
+function cos(a)
+{
+    var t;
+    if (a instanceof Complex || a instanceof Series) {
+        t = exp(a * I);
+        return (t + 1/t) / 2;
+    } else {
+        return Float.cos(Float(a));
+    }
+}
+
+function tan(a)
+{
+    if (a instanceof Complex || a instanceof Series) {
+        return sin(a) / cos(a);
+    } else {
+        return Float.tan(Float(a));
+    }
+}
+
+function asin(a)
+{
+    return Float.asin(Float(a));
+}
+
+function acos(a)
+{
+    return Float.acos(Float(a));
+}
+
+function atan(a)
+{
+    return Float.atan(Float(a));
+}
+
+function atan2(a, b)
+{
+    return Float.atan2(Float(a), Float(b));
+}
+
+function sinc(a)
+{
+    if (a == 0) {
+        return 1;
+    } else {
+        a *= Float.PI;
+        return sin(a) / a;
+    }
+}
+
+function todeg(a)
+{
+    return a * 180 / Float.PI;
+}
+
+function fromdeg(a)
+{
+    return a * Float.PI / 180;
+}
+
+var idn = Matrix.idn;
+var diag = Matrix.diag;
+var trans = Matrix.trans;
+var trace = Matrix.trace;
+var charpoly = Matrix.charpoly;
+var eigenvals = Matrix.eigenvals;
+var det = Matrix.det;
+var rank = Matrix.rank;
+var ker = Matrix.ker;
+var cp = Matrix.cp;
+var dp = Matrix.dp;
+
+var polroots = Polynomial.roots;
+var bestappr = Float.bestappr;
diff --git a/quickjs-atom.h b/quickjs-atom.h
new file mode 100644
index 0000000..233294c
--- /dev/null
+++ b/quickjs-atom.h
@@ -0,0 +1,284 @@
+/*
+ * QuickJS atom definitions
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ * Copyright (c) 2017-2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifdef DEF
+
+/* Note: first atoms are considered as keywords in the parser */
+DEF(null, "null") /* must be first */
+DEF(false, "false")
+DEF(true, "true")
+DEF(if, "if")
+DEF(else, "else")
+DEF(return, "return")
+DEF(var, "var")
+DEF(this, "this")
+DEF(delete, "delete")
+DEF(void, "void")
+DEF(typeof, "typeof")
+DEF(new, "new")
+DEF(in, "in")
+DEF(instanceof, "instanceof")
+DEF(do, "do")
+DEF(while, "while")
+DEF(for, "for")
+DEF(break, "break")
+DEF(continue, "continue")
+DEF(switch, "switch")
+DEF(case, "case")
+DEF(default, "default")
+DEF(throw, "throw")
+DEF(try, "try")
+DEF(catch, "catch")
+DEF(finally, "finally")
+DEF(function, "function")
+DEF(debugger, "debugger")
+DEF(with, "with")
+/* FutureReservedWord */
+DEF(class, "class")
+DEF(const, "const")
+DEF(enum, "enum")
+DEF(export, "export")
+DEF(extends, "extends")
+DEF(import, "import")
+DEF(super, "super")
+/* FutureReservedWords when parsing strict mode code */
+DEF(implements, "implements")
+DEF(interface, "interface")
+DEF(let, "let")
+DEF(package, "package")
+DEF(private, "private")
+DEF(protected, "protected")
+DEF(public, "public")
+DEF(static, "static")
+DEF(yield, "yield")
+DEF(await, "await")
+
+/* empty string */
+DEF(empty_string, "")
+/* identifiers */
+DEF(length, "length")
+DEF(fileName, "fileName")
+DEF(lineNumber, "lineNumber")
+DEF(message, "message")
+DEF(stack, "stack")
+DEF(name, "name")
+DEF(toString, "toString")
+DEF(toLocaleString, "toLocaleString")
+DEF(valueOf, "valueOf")
+DEF(eval, "eval")
+DEF(prototype, "prototype")
+DEF(constructor, "constructor")
+DEF(configurable, "configurable")
+DEF(writable, "writable")
+DEF(enumerable, "enumerable")
+DEF(value, "value")
+DEF(get, "get")
+DEF(set, "set")
+DEF(of, "of")
+DEF(__proto__, "__proto__")
+DEF(undefined, "undefined")
+DEF(number, "number")
+DEF(boolean, "boolean")
+DEF(string, "string")
+DEF(object, "object")
+DEF(symbol, "symbol")
+DEF(integer, "integer")
+DEF(unknown, "unknown")
+DEF(arguments, "arguments")
+DEF(callee, "callee")
+DEF(caller, "caller")
+DEF(_eval_, "<eval>")
+DEF(_ret_, "<ret>")
+DEF(_var_, "<var>")
+DEF(_with_, "<with>")
+DEF(lastIndex, "lastIndex")
+DEF(target, "target")
+DEF(index, "index")
+DEF(input, "input")
+DEF(defineProperties, "defineProperties")
+DEF(apply, "apply")
+DEF(join, "join")
+DEF(concat, "concat")
+DEF(split, "split")
+DEF(construct, "construct")
+DEF(getPrototypeOf, "getPrototypeOf")
+DEF(setPrototypeOf, "setPrototypeOf")
+DEF(isExtensible, "isExtensible")
+DEF(preventExtensions, "preventExtensions")
+DEF(has, "has")
+DEF(deleteProperty, "deleteProperty")
+DEF(defineProperty, "defineProperty")
+DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor")
+DEF(ownKeys, "ownKeys")
+DEF(add, "add")
+DEF(done, "done")
+DEF(next, "next")
+DEF(values, "values")
+DEF(source, "source")
+DEF(flags, "flags")
+DEF(global, "global")
+DEF(unicode, "unicode")
+DEF(raw, "raw")
+DEF(new_target, "new.target")
+DEF(this_active_func, "this.active_func")
+DEF(home_object, "<home_object>")
+DEF(computed_field, "<computed_field>")
+DEF(static_computed_field, "<static_computed_field>") /* must come after computed_fields */
+DEF(class_fields_init, "<class_fields_init>")
+DEF(brand, "<brand>")
+DEF(hash_constructor, "#constructor")
+DEF(as, "as")
+DEF(from, "from")
+DEF(meta, "meta")
+DEF(_default_, "*default*")
+DEF(_star_, "*")
+DEF(Module, "Module")
+DEF(then, "then")
+DEF(resolve, "resolve")
+DEF(reject, "reject")
+DEF(promise, "promise")
+DEF(proxy, "proxy")
+DEF(revoke, "revoke")
+DEF(async, "async")
+DEF(exec, "exec")
+DEF(groups, "groups")
+DEF(status, "status")
+DEF(reason, "reason")
+DEF(globalThis, "globalThis")
+#ifdef CONFIG_BIGNUM
+DEF(bigint, "bigint")
+DEF(bigfloat, "bigfloat")
+#endif
+#ifdef CONFIG_ATOMICS
+DEF(not_equal, "not-equal")
+DEF(timed_out, "timed-out")
+DEF(ok, "ok")
+#endif
+DEF(toJSON, "toJSON")
+/* class names */
+DEF(Object, "Object")
+DEF(Array, "Array")
+DEF(Error, "Error")
+DEF(Number, "Number")
+DEF(String, "String")
+DEF(Boolean, "Boolean")
+DEF(Symbol, "Symbol")
+DEF(Arguments, "Arguments")
+DEF(Math, "Math")
+DEF(JSON, "JSON")
+DEF(Date, "Date")
+DEF(Function, "Function")
+DEF(GeneratorFunction, "GeneratorFunction")
+DEF(ForInIterator, "ForInIterator")
+DEF(RegExp, "RegExp")
+DEF(ArrayBuffer, "ArrayBuffer")
+DEF(SharedArrayBuffer, "SharedArrayBuffer")
+/* must keep same order as class IDs for typed arrays */
+DEF(Uint8ClampedArray, "Uint8ClampedArray") 
+DEF(Int8Array, "Int8Array")
+DEF(Uint8Array, "Uint8Array")
+DEF(Int16Array, "Int16Array")
+DEF(Uint16Array, "Uint16Array")
+DEF(Int32Array, "Int32Array")
+DEF(Uint32Array, "Uint32Array")
+#ifdef CONFIG_BIGNUM
+DEF(BigInt64Array, "BigInt64Array")
+DEF(BigUint64Array, "BigUint64Array")
+#endif
+DEF(Float32Array, "Float32Array")
+DEF(Float64Array, "Float64Array")
+DEF(DataView, "DataView")
+#ifdef CONFIG_BIGNUM
+DEF(BigInt, "BigInt")
+DEF(BigFloat, "BigFloat")
+DEF(BigFloatEnv, "BigFloatEnv")
+#endif
+DEF(Map, "Map")
+DEF(Set, "Set") /* Map + 1 */
+DEF(WeakMap, "WeakMap") /* Map + 2 */
+DEF(WeakSet, "WeakSet") /* Map + 3 */
+DEF(Map_Iterator, "Map Iterator")
+DEF(Set_Iterator, "Set Iterator")
+DEF(Array_Iterator, "Array Iterator")
+DEF(String_Iterator, "String Iterator")
+DEF(RegExp_String_Iterator, "RegExp String Iterator")
+DEF(Generator, "Generator")
+DEF(Proxy, "Proxy")
+DEF(Promise, "Promise")
+DEF(PromiseResolveFunction, "PromiseResolveFunction")
+DEF(PromiseRejectFunction, "PromiseRejectFunction")
+DEF(AsyncFunction, "AsyncFunction")
+DEF(AsyncFunctionResolve, "AsyncFunctionResolve")
+DEF(AsyncFunctionReject, "AsyncFunctionReject")
+DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction")
+DEF(AsyncGenerator, "AsyncGenerator")
+DEF(EvalError, "EvalError")
+DEF(RangeError, "RangeError")
+DEF(ReferenceError, "ReferenceError")
+DEF(SyntaxError, "SyntaxError")
+DEF(TypeError, "TypeError")
+DEF(URIError, "URIError")
+DEF(InternalError, "InternalError")
+/* private symbols */
+DEF(Private_brand, "<brand>")
+/* symbols */
+DEF(Symbol_toPrimitive, "Symbol.toPrimitive")
+DEF(Symbol_iterator, "Symbol.iterator")
+DEF(Symbol_match, "Symbol.match")
+DEF(Symbol_matchAll, "Symbol.matchAll")
+DEF(Symbol_replace, "Symbol.replace")
+DEF(Symbol_search, "Symbol.search")
+DEF(Symbol_split, "Symbol.split")
+DEF(Symbol_toStringTag, "Symbol.toStringTag")
+DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable")
+DEF(Symbol_hasInstance, "Symbol.hasInstance")
+DEF(Symbol_species, "Symbol.species")
+DEF(Symbol_unscopables, "Symbol.unscopables")
+DEF(Symbol_asyncIterator, "Symbol.asyncIterator")
+#ifdef CONFIG_BIGNUM
+DEF(Symbol_operatorOrder, "Symbol.operatorOrder")
+DEF(Symbol_operatorAdd, "Symbol.operatorAdd")
+DEF(Symbol_operatorSub, "Symbol.operatorSub")
+DEF(Symbol_operatorMul, "Symbol.operatorMul")
+DEF(Symbol_operatorDiv, "Symbol.operatorDiv")
+DEF(Symbol_operatorMod, "Symbol.operatorMod")
+DEF(Symbol_operatorPow, "Symbol.operatorPow")
+DEF(Symbol_operatorShl, "Symbol.operatorShl")
+DEF(Symbol_operatorShr, "Symbol.operatorShr")
+DEF(Symbol_operatorAnd, "Symbol.operatorAnd")
+DEF(Symbol_operatorOr, "Symbol.operatorOr")
+DEF(Symbol_operatorXor, "Symbol.operatorXor")
+DEF(Symbol_operatorCmpLT, "Symbol.operatorCmpLT")
+DEF(Symbol_operatorCmpLE, "Symbol.operatorCmpLE")
+DEF(Symbol_operatorCmpEQ, "Symbol.operatorCmpEQ")
+DEF(Symbol_operatorPlus, "Symbol.operatorPlus")
+DEF(Symbol_operatorNeg, "Symbol.operatorNeg")
+DEF(Symbol_operatorNot, "Symbol.operatorNot")
+DEF(Symbol_operatorInc, "Symbol.operatorInc")
+DEF(Symbol_operatorDec, "Symbol.operatorDec")
+DEF(Symbol_operatorMathMod, "Symbol.operatorMathMod")
+#endif
+
+#endif /* DEF */
diff --git a/quickjs-libc.c b/quickjs-libc.c
new file mode 100644
index 0000000..eba52cf
--- /dev/null
+++ b/quickjs-libc.c
@@ -0,0 +1,2772 @@
+/*
+ * QuickJS C library
+ * 
+ * Copyright (c) 2017-2019 Fabrice Bellard
+ * Copyright (c) 2017-2019 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#if defined(_WIN32)
+#include <windows.h>
+#include <conio.h>
+#else
+#include <dlfcn.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#if defined(__APPLE__)
+typedef sig_t sighandler_t;
+#endif
+#endif
+
+#include "cutils.h"
+#include "list.h"
+#include "quickjs-libc.h"
+
+static void js_std_dbuf_init(JSContext *ctx, DynBuf *s)
+{
+    dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt);
+}
+
+/* TODO:
+   - add worker
+   - add minimal VT100 emulation for win32
+   - add socket calls
+*/
+
+typedef struct {
+    struct list_head link;
+    int fd;
+    JSValue rw_func[2];
+} JSOSRWHandler;
+
+typedef struct {
+    struct list_head link;
+    int sig_num;
+    JSValue func;
+} JSOSSignalHandler;
+
+typedef struct {
+    struct list_head link;
+    BOOL has_object;
+    int64_t timeout;
+    JSValue func;
+} JSOSTimer;
+
+/* initialize the lists so js_std_free_handlers() can always be called */
+static struct list_head os_rw_handlers = LIST_HEAD_INIT(os_rw_handlers);
+static struct list_head os_signal_handlers = LIST_HEAD_INIT(os_signal_handlers);
+static struct list_head os_timers = LIST_HEAD_INIT(os_timers);
+static uint64_t os_pending_signals;
+static int eval_script_recurse;
+static int (*os_poll_func)(JSContext *ctx);
+
+static JSValue js_printf_internal(JSContext *ctx,
+                                  int argc, JSValueConst *argv, FILE *fp)
+{
+    char fmtbuf[32];
+    uint8_t cbuf[UTF8_CHAR_LEN_MAX+1];
+    JSValue res;
+    DynBuf dbuf;
+    const char *fmt_str;
+    const uint8_t *fmt, *fmt_end;
+    const uint8_t *p;
+    char *q;
+    int i, c, len;
+    size_t fmt_len;
+    int32_t int32_arg;
+    int64_t int64_arg;
+    double double_arg;
+    const char *string_arg;
+    enum { PART_FLAGS, PART_WIDTH, PART_DOT, PART_PREC, PART_MODIFIER } part;
+    int modsize;
+    /* Use indirect call to dbuf_printf to prevent gcc warning */
+    int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf;
+
+    js_std_dbuf_init(ctx, &dbuf);
+
+    if (argc > 0) {
+        fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]);
+        if (!fmt_str)
+            goto fail;
+
+        i = 1;
+        fmt = (const uint8_t *)fmt_str;
+        fmt_end = fmt + fmt_len;
+        while (fmt < fmt_end) {
+            for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++)
+                continue;
+            dbuf_put(&dbuf, p, fmt - p);
+            if (fmt >= fmt_end)
+                break;
+            q = fmtbuf;
+            *q++ = *fmt++;  /* copy '%' */
+            part = PART_FLAGS;
+            modsize = 0;
+            for (;;) {
+                if (q >= fmtbuf + sizeof(fmtbuf) - 1)
+                    goto invalid;
+
+                c = *fmt++;
+                *q++ = c;
+                *q = '\0';
+
+                switch (c) {
+                case '1': case '2': case '3':
+                case '4': case '5': case '6':
+                case '7': case '8': case '9':
+                    if (part != PART_PREC) {
+                        if (part <= PART_WIDTH)
+                            part = PART_WIDTH;
+                        else 
+                            goto invalid;
+                    }
+                    continue;
+
+                case '0': case '#': case '+': case '-': case ' ': case '\'':
+                    if (part > PART_FLAGS)
+                        goto invalid;
+                    continue;
+
+                case '.':
+                    if (part > PART_DOT)
+                        goto invalid;
+                    part = PART_DOT;
+                    continue;
+
+                case '*':
+                    if (part < PART_WIDTH)
+                        part = PART_DOT;
+                    else if (part == PART_DOT)
+                        part = PART_MODIFIER;
+                    else
+                        goto invalid;
+
+                    if (i >= argc)
+                        goto missing;
+
+                    if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
+                        goto fail;
+                    q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg);
+                    continue;
+
+                case 'h':
+                    if (modsize != 0 && modsize != -1)
+                        goto invalid;
+                    modsize--;
+                    part = PART_MODIFIER;
+                    continue;
+                case 'l':
+                    q--;
+                    if (modsize != 0 && modsize != 1)
+                        goto invalid;
+                    modsize++;
+                    part = PART_MODIFIER;
+                    continue;
+
+                case 'c':
+                    if (i >= argc)
+                        goto missing;
+                    if (JS_IsString(argv[i])) {
+                        string_arg = JS_ToCString(ctx, argv[i++]);
+                        if (!string_arg)
+                            goto fail;
+                        int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p);
+                        JS_FreeCString(ctx, string_arg);
+                    } else {
+                        if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
+                            goto fail;
+                    }
+                    /* handle utf-8 encoding explicitly */
+                    if ((unsigned)int32_arg > 0x10FFFF)
+                        int32_arg = 0xFFFD;
+                    /* ignore conversion flags, width and precision */
+                    len = unicode_to_utf8(cbuf, int32_arg);
+                    dbuf_put(&dbuf, cbuf, len);
+                    break;
+
+                case 'd':
+                case 'i':
+                case 'o':
+                case 'u':
+                case 'x':
+                case 'X':
+                    if (i >= argc)
+                        goto missing;
+                    if (modsize > 0) {
+                        if (JS_ToInt64(ctx, &int64_arg, argv[i++]))
+                            goto fail;
+                        q[1] = q[-1];
+                        q[-1] = q[0] = 'l';
+                        q[2] = '\0';
+                        dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg);
+                    } else {
+                        if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
+                            goto fail;
+                        dbuf_printf_fun(&dbuf, fmtbuf, int32_arg);
+                    }
+                    break;
+
+                case 's':
+                    if (i >= argc)
+                        goto missing;
+                    string_arg = JS_ToCString(ctx, argv[i++]);
+                    if (!string_arg)
+                        goto fail;
+                    dbuf_printf_fun(&dbuf, fmtbuf, string_arg);
+                    JS_FreeCString(ctx, string_arg);
+                    break;
+
+                case 'e':
+                case 'f':
+                case 'g':
+                case 'a':
+                case 'E':
+                case 'F':
+                case 'G':
+                case 'A':
+                    if (i >= argc)
+                        goto missing;
+                    if (JS_ToFloat64(ctx, &double_arg, argv[i++]))
+                        goto fail;
+                    dbuf_printf_fun(&dbuf, fmtbuf, double_arg);
+                    break;
+
+                case '%':
+                    dbuf_putc(&dbuf, '%');
+                    break;
+
+                default:
+                    /* XXX: should support an extension mechanism */
+                invalid:
+                    JS_ThrowTypeError(ctx, "invalid conversion specifier in format string");
+                    goto fail;
+                missing:
+                    JS_ThrowReferenceError(ctx, "missing argument for conversion specifier");
+                    goto fail;
+                }
+                break;
+            }
+        }
+        JS_FreeCString(ctx, fmt_str);
+    }
+    if (dbuf.error) {
+        res = JS_ThrowOutOfMemory(ctx);
+    } else {
+        if (fp) {
+            len = fwrite(dbuf.buf, 1, dbuf.size, fp);
+            res = JS_NewInt32(ctx, len);
+        } else {
+            res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size);
+        }
+    }
+    dbuf_free(&dbuf);
+    return res;
+
+fail:
+    dbuf_free(&dbuf);
+    return JS_EXCEPTION;
+}
+
+uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename)
+{
+    FILE *f;
+    uint8_t *buf;
+    size_t buf_len;
+    long lret;
+    
+    f = fopen(filename, "rb");
+    if (!f)
+        return NULL;
+    if (fseek(f, 0, SEEK_END) < 0)
+        goto fail;
+    lret = ftell(f);
+    if (lret < 0)
+        goto fail;
+    /* XXX: on Linux, ftell() return LONG_MAX for directories */
+    if (lret == LONG_MAX) {
+        errno = EISDIR;
+        goto fail;
+    }
+    buf_len = lret;
+    if (fseek(f, 0, SEEK_SET) < 0)
+        goto fail;
+    if (ctx)
+        buf = js_malloc(ctx, buf_len + 1);
+    else
+        buf = malloc(buf_len + 1);
+    if (!buf)
+        goto fail;
+    if (fread(buf, 1, buf_len, f) != buf_len) {
+        errno = EIO;
+        if (ctx)
+            js_free(ctx, buf);
+        else
+            free(buf);
+    fail:
+        fclose(f);
+        return NULL;
+    }
+    buf[buf_len] = '\0';
+    fclose(f);
+    *pbuf_len = buf_len;
+    return buf;
+}
+
+/* load and evaluate a file */
+static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    uint8_t *buf;
+    const char *filename;
+    JSValue ret;
+    size_t buf_len;
+    
+    filename = JS_ToCString(ctx, argv[0]);
+    if (!filename)
+        return JS_EXCEPTION;
+    buf = js_load_file(ctx, &buf_len, filename);
+    if (!buf) {
+        JS_ThrowReferenceError(ctx, "could not load '%s'", filename);
+        JS_FreeCString(ctx, filename);
+        return JS_EXCEPTION;
+    }
+    ret = JS_Eval(ctx, (char *)buf, buf_len, filename,
+                  JS_EVAL_TYPE_GLOBAL);
+    js_free(ctx, buf);
+    JS_FreeCString(ctx, filename);
+    return ret;
+}
+
+typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx,
+                                        const char *module_name);
+
+
+#if defined(_WIN32)
+static JSModuleDef *js_module_loader_so(JSContext *ctx,
+                                        const char *module_name)
+{
+    JS_ThrowReferenceError(ctx, "shared library modules are not supported yet");
+    return NULL;
+}
+#else
+static JSModuleDef *js_module_loader_so(JSContext *ctx,
+                                        const char *module_name)
+{
+    JSModuleDef *m;
+    void *hd;
+    JSInitModuleFunc *init;
+    char *filename;
+    
+    if (!strchr(module_name, '/')) {
+        /* must add a '/' so that the DLL is not searched in the
+           system library paths */
+        filename = js_malloc(ctx, strlen(module_name) + 2 + 1);
+        if (!filename)
+            return NULL;
+        strcpy(filename, "./");
+        strcpy(filename + 2, module_name);
+    } else {
+        filename = (char *)module_name;
+    }
+    
+    /* C module */
+    hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
+    if (filename != module_name)
+        js_free(ctx, filename);
+    if (!hd) {
+        JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library",
+                               module_name);
+        goto fail;
+    }
+
+    init = dlsym(hd, "js_init_module");
+    if (!init) {
+        JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found",
+                               module_name);
+        goto fail;
+    }
+
+    m = init(ctx, module_name);
+    if (!m) {
+        JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error",
+                               module_name);
+    fail:
+        if (hd)
+            dlclose(hd);
+        return NULL;
+    }
+    return m;
+}
+#endif /* !_WIN32 */
+
+int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
+                              JS_BOOL use_realpath, JS_BOOL is_main)
+{
+    JSModuleDef *m;
+    char buf[PATH_MAX + 16];
+    JSValue meta_obj;
+    JSAtom module_name_atom;
+    const char *module_name;
+    
+    assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE);
+    m = JS_VALUE_GET_PTR(func_val);
+
+    module_name_atom = JS_GetModuleName(ctx, m);
+    module_name = JS_AtomToCString(ctx, module_name_atom);
+    JS_FreeAtom(ctx, module_name_atom);
+    if (!module_name)
+        return -1;
+    if (!strchr(module_name, ':')) {
+        strcpy(buf, "file://");
+#if !defined(_WIN32)
+        /* realpath() cannot be used with modules compiled with qjsc
+           because the corresponding module source code is not
+           necessarily present */
+        if (use_realpath) {
+            char *res = realpath(module_name, buf + strlen(buf));
+            if (!res) {
+                JS_ThrowTypeError(ctx, "realpath failure");
+                JS_FreeCString(ctx, module_name);
+                return -1;
+            }
+        } else
+#endif
+        {
+            pstrcat(buf, sizeof(buf), module_name);
+        }
+    } else {
+        pstrcpy(buf, sizeof(buf), module_name);
+    }
+    JS_FreeCString(ctx, module_name);
+    
+    meta_obj = JS_GetImportMeta(ctx, m);
+    if (JS_IsException(meta_obj))
+        return -1;
+    JS_DefinePropertyValueStr(ctx, meta_obj, "url",
+                              JS_NewString(ctx, buf),
+                              JS_PROP_C_W_E);
+    JS_DefinePropertyValueStr(ctx, meta_obj, "main",
+                              JS_NewBool(ctx, is_main),
+                              JS_PROP_C_W_E);
+    JS_FreeValue(ctx, meta_obj);
+    return 0;
+}
+
+JSModuleDef *js_module_loader(JSContext *ctx,
+                              const char *module_name, void *opaque)
+{
+    JSModuleDef *m;
+
+    if (has_suffix(module_name, ".so")) {
+        m = js_module_loader_so(ctx, module_name);
+    } else {
+        size_t buf_len;
+        uint8_t *buf;
+        JSValue func_val;
+    
+        buf = js_load_file(ctx, &buf_len, module_name);
+        if (!buf) {
+            JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
+                                   module_name);
+            return NULL;
+        }
+        
+        /* compile the module */
+        func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
+                           JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+        js_free(ctx, buf);
+        if (JS_IsException(func_val))
+            return NULL;
+        /* XXX: could propagate the exception */
+        js_module_set_import_meta(ctx, func_val, TRUE, FALSE);
+        /* the module is already referenced, so we must free it */
+        m = JS_VALUE_GET_PTR(func_val);
+        JS_FreeValue(ctx, func_val);
+    }
+    return m;
+}
+
+static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    int status;
+    if (JS_ToInt32(ctx, &status, argv[0]))
+        status = -1;
+    exit(status);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    const char *name, *str;
+    name = JS_ToCString(ctx, argv[0]);
+    if (!name)
+        return JS_EXCEPTION;
+    str = getenv(name);
+    JS_FreeCString(ctx, name);
+    if (!str)
+        return JS_UNDEFINED;
+    else
+        return JS_NewString(ctx, str);
+}
+
+static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val,
+                         int argc, JSValueConst *argv)
+{
+    JS_RunGC(JS_GetRuntime(ctx));
+    return JS_UNDEFINED;
+}
+
+static int interrupt_handler(JSRuntime *rt, void *opaque)
+{
+    return (os_pending_signals >> SIGINT) & 1;
+}
+
+static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    const char *str;
+    size_t len;
+    JSValue ret;
+    str = JS_ToCStringLen(ctx, &len, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+    if (++eval_script_recurse == 1) {
+        /* install the interrupt handler */
+        JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL);
+    }
+    ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL);
+    JS_FreeCString(ctx, str);
+    if (--eval_script_recurse == 0) {
+        /* remove the interrupt handler */
+        JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL);
+        os_pending_signals &= ~((uint64_t)1 << SIGINT);
+        /* convert the uncatchable "interrupted" error into a normal error
+           so that it can be caught by the REPL */
+        if (JS_IsException(ret))
+            JS_ResetUncatchableError(ctx);
+    }
+    return ret;
+}
+
+static JSClassID js_std_file_class_id;
+
+typedef struct {
+    FILE *f;
+    BOOL close_in_finalizer;
+    BOOL is_popen;
+} JSSTDFile;
+
+static void js_std_file_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id);
+    if (s) {
+        if (s->f && s->close_in_finalizer) {
+            if (s->is_popen)
+                pclose(s->f);
+            else
+                fclose(s->f);
+        }
+        js_free_rt(rt, s);
+    }
+}
+
+static JSValue js_new_std_error(JSContext *ctx, int err)
+{
+    JSValue obj;
+    /* XXX: could add a specific Error prototype */
+    obj = JS_NewError(ctx);
+    JS_DefinePropertyValueStr(ctx, obj, "message",
+                              JS_NewString(ctx, strerror(err)),
+                              JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    JS_DefinePropertyValueStr(ctx, obj, "errno",
+                              JS_NewInt32(ctx, err),
+                              JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    return obj;
+}
+
+static JSValue js_std_error_constructor(JSContext *ctx, JSValueConst new_target,
+                                        int argc, JSValueConst *argv)
+{
+    int err;
+    if (JS_ToInt32(ctx, &err, argv[0]))
+        return JS_EXCEPTION;
+    return js_new_std_error(ctx, err);
+}
+
+static JSValue js_std_error_strerror(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    int err;
+    if (JS_ToInt32(ctx, &err, argv[0]))
+        return JS_EXCEPTION;
+    return JS_NewString(ctx, strerror(err));
+}
+
+static JSValue js_std_throw_errno(JSContext *ctx, int err)
+{
+    JSValue obj;
+    obj = js_new_std_error(ctx, err);
+    if (JS_IsException(obj))
+        obj = JS_NULL;
+    return JS_Throw(ctx, obj);
+}
+
+static JSValue js_new_std_file(JSContext *ctx, FILE *f,
+                               BOOL close_in_finalizer,
+                               BOOL is_popen)
+{
+    JSSTDFile *s;
+    JSValue obj;
+    obj = JS_NewObjectClass(ctx, js_std_file_class_id);
+    if (JS_IsException(obj))
+        return obj;
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    s->close_in_finalizer = close_in_finalizer;
+    s->is_popen = is_popen;
+    s->f = f;
+    JS_SetOpaque(obj, s);
+    return obj;
+}
+
+static JSValue js_std_open(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    const char *filename, *mode = NULL;
+    FILE *f;
+
+    filename = JS_ToCString(ctx, argv[0]);
+    if (!filename)
+        goto fail;
+    mode = JS_ToCString(ctx, argv[1]);
+    if (!mode)
+        goto fail;
+    if (mode[strspn(mode, "rwa+b")] != '\0') {
+        js_std_throw_errno(ctx, EINVAL);
+        goto fail;
+    }
+
+    f = fopen(filename, mode);
+    JS_FreeCString(ctx, filename);
+    JS_FreeCString(ctx, mode);
+    if (!f)
+        return js_std_throw_errno(ctx, errno);
+    return js_new_std_file(ctx, f, TRUE, FALSE);
+ fail:
+    JS_FreeCString(ctx, filename);
+    JS_FreeCString(ctx, mode);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    const char *filename, *mode = NULL;
+    FILE *f;
+
+    filename = JS_ToCString(ctx, argv[0]);
+    if (!filename)
+        goto fail;
+    mode = JS_ToCString(ctx, argv[1]);
+    if (!mode)
+        goto fail;
+    if (mode[strspn(mode, "rw")] != '\0') {
+        js_std_throw_errno(ctx, EINVAL);
+        goto fail;
+    }
+
+    f = popen(filename, mode);
+    JS_FreeCString(ctx, filename);
+    JS_FreeCString(ctx, mode);
+    if (!f)
+        return js_std_throw_errno(ctx, errno);
+    return js_new_std_file(ctx, f, TRUE, TRUE);
+ fail:
+    JS_FreeCString(ctx, filename);
+    JS_FreeCString(ctx, mode);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    const char *mode;
+    FILE *f;
+    int fd;
+
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    mode = JS_ToCString(ctx, argv[1]);
+    if (!mode)
+        goto fail;
+    if (mode[strspn(mode, "rwa+")] != '\0') {
+        js_std_throw_errno(ctx, EINVAL);
+        goto fail;
+    }
+
+    f = fdopen(fd, mode);
+    JS_FreeCString(ctx, mode);
+    if (!f)
+        return js_std_throw_errno(ctx, errno);
+    return js_new_std_file(ctx, f, TRUE, FALSE);
+ fail:
+    JS_FreeCString(ctx, mode);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    FILE *f;
+    f = tmpfile();
+    if (!f)
+        return js_std_throw_errno(ctx, errno);
+    return js_new_std_file(ctx, f, TRUE, FALSE);
+}
+
+static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    return js_printf_internal(ctx, argc, argv, NULL);
+}
+
+static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    return js_printf_internal(ctx, argc, argv, stdout);
+}
+
+static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj)
+{
+    JSSTDFile *s = JS_GetOpaque2(ctx, obj, js_std_file_class_id);
+    if (!s)
+        return NULL;
+    if (!s->f) {
+        js_std_throw_errno(ctx, EBADF);
+        return NULL;
+    }
+    return s->f;
+}
+
+static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv, int magic)
+{
+    FILE *f;
+    int i;
+    const char *str;
+
+    if (magic == 0) {
+        f = stdout;
+    } else {
+        f = js_std_file_get(ctx, this_val);
+        if (!f)
+            return JS_EXCEPTION;
+    }
+    
+    for(i = 0; i < argc; i++) {
+        str = JS_ToCString(ctx, argv[i]);
+        if (!str)
+            return JS_EXCEPTION;
+        fputs(str, f);
+        JS_FreeCString(ctx, str);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSSTDFile *s = JS_GetOpaque2(ctx, this_val, js_std_file_class_id);
+    if (!s)
+        return JS_EXCEPTION;
+    if (!s->f)
+        return js_std_throw_errno(ctx, EBADF);
+    /* XXX: could return exit code */
+    if (s->is_popen)
+        pclose(s->f);
+    else
+        fclose(s->f);
+    s->f = NULL;
+    return JS_UNDEFINED;
+}
+
+static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    if (!f)
+        return JS_EXCEPTION;
+    return js_printf_internal(ctx, argc, argv, f);
+}
+
+static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    if (!f)
+        return JS_EXCEPTION;
+    fflush(f);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    int64_t pos;
+    if (!f)
+        return JS_EXCEPTION;
+#if defined(__linux__)
+    pos = ftello(f);
+#else
+    pos = ftell(f);
+#endif
+    return JS_NewInt64(ctx, pos);
+}
+
+static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    int64_t pos;
+    int whence, ret;
+    if (!f)
+        return JS_EXCEPTION;
+    if (JS_ToInt64(ctx, &pos, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &whence, argv[1]))
+        return JS_EXCEPTION;
+#if defined(__linux__)
+    ret = fseeko(f, pos, whence);
+#else
+    ret = fseek(f, pos, whence);
+#endif
+    if (ret < 0)
+        return js_std_throw_errno(ctx, EBADF);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    if (!f)
+        return JS_EXCEPTION;
+    return JS_NewBool(ctx, feof(f));
+}
+
+static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    if (!f)
+        return JS_EXCEPTION;
+    return JS_NewInt32(ctx, fileno(f));
+}
+
+static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv, int magic)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    uint64_t pos, len;
+    size_t size, ret;
+    uint8_t *buf;
+    
+    if (!f)
+        return JS_EXCEPTION;
+    if (JS_ToIndex(ctx, &pos, argv[1]))
+        return JS_EXCEPTION;
+    if (JS_ToIndex(ctx, &len, argv[2]))
+        return JS_EXCEPTION;
+    buf = JS_GetArrayBuffer(ctx, &size, argv[0]);
+    if (!buf)
+        return JS_EXCEPTION;
+    if (pos + len > size)
+        return JS_ThrowRangeError(ctx, "read/write array buffer overflow");
+    if (magic)
+        ret = fwrite(buf + pos, 1, len, f);
+    else
+        ret = fread(buf + pos, 1, len, f);
+    return JS_NewInt64(ctx, ret);
+}
+
+/* XXX: could use less memory and go faster */
+static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    int c;
+    DynBuf dbuf;
+    JSValue obj;
+    
+    if (!f)
+        return JS_EXCEPTION;
+
+    js_std_dbuf_init(ctx, &dbuf);
+    for(;;) {
+        c = fgetc(f);
+        if (c == EOF) {
+            if (dbuf.size == 0) {
+                /* EOF */
+                dbuf_free(&dbuf);
+                return JS_NULL;
+            } else {
+                break;
+            }
+        }
+        if (c == '\n')
+            break;
+        if (dbuf_putc(&dbuf, c)) {
+            dbuf_free(&dbuf);
+            return JS_ThrowOutOfMemory(ctx);
+        }
+    }
+    obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size);
+    dbuf_free(&dbuf);
+    return obj;
+}
+
+/* XXX: could use less memory and go faster */
+static JSValue js_std_file_readAsString(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    int c;
+    DynBuf dbuf;
+    JSValue obj;
+    uint64_t max_size64;
+    size_t max_size;
+    JSValueConst max_size_val;
+    
+    if (!f)
+        return JS_EXCEPTION;
+
+    if (argc >= 1)
+        max_size_val = argv[0];
+    else
+        max_size_val = JS_UNDEFINED;
+    max_size = (size_t)-1;
+    if (!JS_IsUndefined(max_size_val)) {
+        if (JS_ToIndex(ctx, &max_size64, max_size_val))
+            return JS_EXCEPTION;
+        if (max_size64 < max_size)
+            max_size = max_size64;
+    }
+
+    js_std_dbuf_init(ctx, &dbuf);
+    while (max_size != 0) {
+        c = fgetc(f);
+        if (c == EOF)
+            break;
+        if (dbuf_putc(&dbuf, c)) {
+            dbuf_free(&dbuf);
+            return JS_EXCEPTION;
+        }
+        max_size--;
+    }
+    obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size);
+    dbuf_free(&dbuf);
+    return obj;
+}
+
+static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    if (!f)
+        return JS_EXCEPTION;
+    return JS_NewInt32(ctx, fgetc(f));
+}
+
+static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    FILE *f = js_std_file_get(ctx, this_val);
+    int c;
+    if (!f)
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &c, argv[0]))
+        return JS_EXCEPTION;
+    c = fputc(c, f);
+    return JS_NewInt32(ctx, c);
+}
+
+/* urlGet */
+
+#define URL_GET_PROGRAM "curl -s -i"
+#define URL_GET_BUF_SIZE 4096
+
+static int http_get_header_line(FILE *f, char *buf, size_t buf_size,
+                                DynBuf *dbuf)
+{
+    int c;
+    char *p;
+    
+    p = buf;
+    for(;;) {
+        c = fgetc(f);
+        if (c < 0)
+            return -1;
+        if ((p - buf) < buf_size - 1)
+            *p++ = c;
+        if (dbuf)
+            dbuf_putc(dbuf, c);
+        if (c == '\n')
+            break;
+    }
+    *p = '\0';
+    return 0;
+}
+
+static int http_get_status(const char *buf)
+{
+    const char *p = buf;
+    while (*p != ' ' && *p != '\0')
+        p++;
+    if (*p != ' ')
+        return 0;
+    while (*p == ' ')
+        p++;
+    return atoi(p);
+}
+
+static int get_bool_option(JSContext *ctx, BOOL *pbool,
+                           JSValueConst obj,
+                           const char *option)
+{
+    JSValue val;
+    val = JS_GetPropertyStr(ctx, obj, option);
+    if (JS_IsException(val))
+        return -1;
+    if (!JS_IsUndefined(val)) {
+        *pbool = JS_ToBool(ctx, val);
+    }
+    JS_FreeValue(ctx, val);
+    return 0;
+}
+
+static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    const char *url;
+    DynBuf cmd_buf;
+    DynBuf data_buf_s, *data_buf = &data_buf_s;
+    DynBuf header_buf_s, *header_buf = &header_buf_s;
+    char *buf; 
+    size_t i, len;
+    int c, status;
+    JSValue response = JS_UNDEFINED, ret_obj;
+    JSValueConst options_obj;
+    FILE *f;
+    BOOL binary_flag, full_flag;
+    
+    url = JS_ToCString(ctx, argv[0]);
+    if (!url)
+        return JS_EXCEPTION;
+    
+    binary_flag = FALSE;
+    full_flag = FALSE;
+    
+    if (argc >= 2) {
+        options_obj = argv[1];
+
+        if (get_bool_option(ctx, &binary_flag, options_obj, "binary"))
+            goto fail_obj;
+
+        if (get_bool_option(ctx, &full_flag, options_obj, "full")) {
+        fail_obj:
+            JS_FreeCString(ctx, url);
+            return JS_EXCEPTION;
+        }
+    }
+    
+    js_std_dbuf_init(ctx, &cmd_buf);
+    dbuf_printf(&cmd_buf, "%s ''", URL_GET_PROGRAM);
+    len = strlen(url);
+    for(i = 0; i < len; i++) {
+        c = url[i];
+        if (c == '\'' || c == '\\')
+            dbuf_putc(&cmd_buf, '\\');
+        dbuf_putc(&cmd_buf, c);
+    }
+    JS_FreeCString(ctx, url);
+    dbuf_putstr(&cmd_buf, "''");
+    dbuf_putc(&cmd_buf, '\0');
+    if (dbuf_error(&cmd_buf)) {
+        dbuf_free(&cmd_buf);
+        return JS_EXCEPTION;
+    }
+    //    printf("%s\n", (char *)cmd_buf.buf);
+    f = popen((char *)cmd_buf.buf, "r");
+    dbuf_free(&cmd_buf);
+    if (!f) {
+        return js_std_throw_errno(ctx, errno);
+    }
+
+    js_std_dbuf_init(ctx, data_buf);
+    js_std_dbuf_init(ctx, header_buf);
+    
+    buf = js_malloc(ctx, URL_GET_BUF_SIZE);
+    if (!buf)
+        goto fail;
+
+    /* get the HTTP status */
+    if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0)
+        goto bad_header;
+    status = http_get_status(buf);
+    if (!full_flag && !(status >= 200 && status <= 299)) {
+        js_std_throw_errno(ctx, ENOENT);
+        goto fail;
+    }
+    
+    /* wait until there is an empty line */
+    for(;;) {
+        if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) {
+        bad_header:
+            js_std_throw_errno(ctx, EINVAL);
+            goto fail;
+        }
+        if (!strcmp(buf, "\r\n"))
+            break;
+    }
+    if (dbuf_error(header_buf))
+        goto fail;
+    header_buf->size -= 2; /* remove the trailing CRLF */
+
+    /* download the data */
+    for(;;) {
+        len = fread(buf, 1, URL_GET_BUF_SIZE, f);
+        if (len == 0)
+            break;
+        dbuf_put(data_buf, (uint8_t *)buf, len);
+    }
+    js_free(ctx, buf);
+    buf = NULL;
+    pclose(f);
+    f = NULL;
+
+    if (dbuf_error(data_buf))
+        goto fail;
+    if (binary_flag) {
+        response = JS_NewArrayBufferCopy(ctx,
+                                         data_buf->buf, data_buf->size);
+    } else {
+        response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size);
+    }
+    dbuf_free(data_buf);
+    data_buf = NULL;
+    if (JS_IsException(response))
+        goto fail;
+
+    if (full_flag) {
+        ret_obj = JS_NewObject(ctx);
+        if (JS_IsException(ret_obj))
+            goto fail;
+        JS_DefinePropertyValueStr(ctx, ret_obj, "response",
+                                  response,
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders",
+                                  JS_NewStringLen(ctx, (char *)header_buf->buf,
+                                                  header_buf->size),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, ret_obj, "status",
+                                  JS_NewInt32(ctx, status),
+                                  JS_PROP_C_W_E);
+    } else {
+        ret_obj = response;
+    }
+    dbuf_free(header_buf);
+    return ret_obj;
+ fail:
+    if (f)
+        pclose(f);
+    js_free(ctx, buf);
+    if (data_buf)
+        dbuf_free(data_buf);
+    if (header_buf)
+        dbuf_free(header_buf);
+    JS_FreeValue(ctx, response);
+    return JS_EXCEPTION;
+}
+
+static JSClassDef js_std_file_class = {
+    "FILE",
+    .finalizer = js_std_file_finalizer,
+}; 
+
+static const JSCFunctionListEntry js_std_funcs[] = {
+    JS_CFUNC_DEF("exit", 1, js_std_exit ),
+    JS_CFUNC_DEF("gc", 0, js_std_gc ),
+    JS_CFUNC_DEF("evalScript", 1, js_evalScript ),
+    JS_CFUNC_DEF("loadScript", 1, js_loadScript ),
+    JS_CFUNC_DEF("getenv", 1, js_std_getenv ),
+    JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ),
+
+    /* FILE I/O */
+    JS_CFUNC_DEF("open", 2, js_std_open ),
+    JS_CFUNC_DEF("popen", 2, js_std_popen ),
+    JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ),
+    JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ),
+    JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ),
+    JS_CFUNC_DEF("printf", 1, js_std_printf ),
+    JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ),
+    JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ),
+    JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ),
+    JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ),
+
+    /* setenv, ... */
+};
+
+static const JSCFunctionListEntry js_std_error_funcs[] = {
+    JS_CFUNC_DEF("strerror", 1, js_std_error_strerror ),
+    /* various errno values */
+#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE )
+    DEF(EINVAL),
+    DEF(EIO),
+    DEF(EACCES),
+    DEF(EEXIST),
+    DEF(ENOSPC),
+    DEF(ENOSYS),
+    DEF(EBUSY),
+    DEF(ENOENT),
+    DEF(EPERM),
+    DEF(EPIPE),
+    DEF(EBADF),
+#undef DEF
+};
+
+static const JSCFunctionListEntry js_std_file_proto_funcs[] = {
+    JS_CFUNC_DEF("close", 0, js_std_file_close ),
+    JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ),
+    JS_CFUNC_DEF("printf", 1, js_std_file_printf ),
+    JS_CFUNC_DEF("flush", 0, js_std_file_flush ),
+    JS_CFUNC_DEF("tell", 0, js_std_file_tell ),
+    JS_CFUNC_DEF("seek", 2, js_std_file_seek ),
+    JS_CFUNC_DEF("eof", 0, js_std_file_eof ),
+    JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ),
+    JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ),
+    JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ),
+    JS_CFUNC_DEF("getline", 0, js_std_file_getline ),
+    JS_CFUNC_DEF("readAsString", 0, js_std_file_readAsString ),
+    JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ),
+    JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ),
+    /* setvbuf, ferror, clearerr, ...  */
+};
+
+static int js_std_init(JSContext *ctx, JSModuleDef *m)
+{
+    JSValue proto, obj;
+    
+    /* FILE class */
+    /* the class ID is created once */
+    JS_NewClassID(&js_std_file_class_id);
+    /* the class is created once per runtime */
+    JS_NewClass(JS_GetRuntime(ctx), js_std_file_class_id, &js_std_file_class);
+    proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs,
+                               countof(js_std_file_proto_funcs));
+    JS_SetClassProto(ctx, js_std_file_class_id, proto);
+
+    JS_SetModuleExportList(ctx, m, js_std_funcs,
+                           countof(js_std_funcs));
+    JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE, FALSE));
+    JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE, FALSE));
+    JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE, FALSE));
+    
+    obj = JS_NewCFunction2(ctx, js_std_error_constructor,
+                           "Error", 1, JS_CFUNC_constructor, 0);
+    JS_SetPropertyFunctionList(ctx, obj, js_std_error_funcs,
+                               countof(js_std_error_funcs));
+    JS_SetModuleExport(ctx, m, "Error", obj);
+
+    return 0;
+}
+
+JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name)
+{
+    JSModuleDef *m;
+    m = JS_NewCModule(ctx, module_name, js_std_init);
+    if (!m)
+        return NULL;
+    JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs));
+    JS_AddModuleExport(ctx, m, "in");
+    JS_AddModuleExport(ctx, m, "out");
+    JS_AddModuleExport(ctx, m, "err");
+    JS_AddModuleExport(ctx, m, "Error");
+    return m;
+}
+
+/**********************************************************/
+/* 'os' object */
+
+static JSValue js_os_return(JSContext *ctx, ssize_t ret)
+{
+    if (ret < 0)
+        ret = -errno;
+    return JS_NewInt64(ctx, ret);
+}
+
+static JSValue js_os_open(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    const char *filename;
+    int flags, mode, ret;
+
+    filename = JS_ToCString(ctx, argv[0]);
+    if (!filename)
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &flags, argv[1]))
+        goto fail;
+    if (argc >= 3 && !JS_IsUndefined(argv[2])) {
+        if (JS_ToInt32(ctx, &mode, argv[2])) {
+        fail:
+            JS_FreeCString(ctx, filename);
+            return JS_EXCEPTION;
+        }
+    } else {
+        mode = 0666;
+    }
+#if defined(_WIN32)
+    /* force binary mode by default */
+    if (!(flags & O_TEXT))
+        flags |= O_BINARY;
+#endif
+    ret = open(filename, flags, mode);
+    JS_FreeCString(ctx, filename);
+    return js_os_return(ctx, ret);
+}
+
+static JSValue js_os_close(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    int fd, ret;
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    ret = close(fd);
+    return js_os_return(ctx, ret);
+}
+
+static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    int fd, whence, ret;
+    int64_t pos;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToInt64(ctx, &pos, argv[1]))
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &whence, argv[2]))
+        return JS_EXCEPTION;
+    ret = lseek(fd, pos, whence);
+    return js_os_return(ctx, ret);
+}
+
+static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv, int magic)
+{
+    int fd;
+    uint64_t pos, len;
+    size_t size;
+    ssize_t ret;
+    uint8_t *buf;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToIndex(ctx, &pos, argv[2]))
+        return JS_EXCEPTION;
+    if (JS_ToIndex(ctx, &len, argv[3]))
+        return JS_EXCEPTION;
+    buf = JS_GetArrayBuffer(ctx, &size, argv[1]);
+    if (!buf)
+        return JS_EXCEPTION;
+    if (pos + len > size)
+        return JS_ThrowRangeError(ctx, "read/write array buffer overflow");
+    if (magic)
+        ret = write(fd, buf + pos, len);
+    else
+        ret = read(fd, buf + pos, len);
+    return js_os_return(ctx, ret);
+}
+
+static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    int fd;
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    return JS_NewBool(ctx, isatty(fd) == 1);
+}
+
+#if defined(_WIN32)
+static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    int fd;
+    HANDLE handle;
+    CONSOLE_SCREEN_BUFFER_INFO info;
+    JSValue obj;
+
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    handle = (HANDLE)_get_osfhandle(fd);
+    
+    if (!GetConsoleScreenBufferInfo(handle, &info))
+        return JS_NULL;
+    obj = JS_NewArray(ctx);
+    if (JS_IsException(obj))
+        return obj;
+    JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E);
+    JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E);
+    return obj;
+}
+
+static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    int fd;
+    HANDLE handle;
+
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    handle = (HANDLE)_get_osfhandle(fd);
+    
+    SetConsoleMode(handle, ENABLE_WINDOW_INPUT);
+    return JS_UNDEFINED;
+}
+#else
+static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    int fd;
+    struct winsize ws;
+    JSValue obj;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    if (ioctl(fd, TIOCGWINSZ, &ws) == 0 &&
+        ws.ws_col >= 4 && ws.ws_row >= 4) {
+        obj = JS_NewArray(ctx);
+        if (JS_IsException(obj))
+            return obj;
+        JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E);
+        JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E);
+        return obj;
+    } else {
+        return JS_NULL;
+    }
+}
+
+static struct termios oldtty;
+
+static void term_exit(void)
+{
+    tcsetattr(0, TCSANOW, &oldtty);
+}
+
+/* XXX: should add a way to go back to normal mode */
+static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    struct termios tty;
+    int fd;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    
+    memset(&tty, 0, sizeof(tty));
+    tcgetattr(fd, &tty);
+    oldtty = tty;
+
+    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
+                          |INLCR|IGNCR|ICRNL|IXON);
+    tty.c_oflag |= OPOST;
+    tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
+    tty.c_cflag &= ~(CSIZE|PARENB);
+    tty.c_cflag |= CS8;
+    tty.c_cc[VMIN] = 1;
+    tty.c_cc[VTIME] = 0;
+
+    tcsetattr(fd, TCSANOW, &tty);
+
+    atexit(term_exit);
+    return JS_UNDEFINED;
+}
+
+#endif /* !_WIN32 */
+
+static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    const char *filename;
+    int ret;
+    
+    filename = JS_ToCString(ctx, argv[0]);
+    if (!filename)
+        return JS_EXCEPTION;
+    ret = remove(filename);
+    JS_FreeCString(ctx, filename);
+    return js_os_return(ctx, ret);
+}
+
+static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    const char *oldpath, *newpath;
+    int ret;
+    
+    oldpath = JS_ToCString(ctx, argv[0]);
+    if (!oldpath)
+        return JS_EXCEPTION;
+    newpath = JS_ToCString(ctx, argv[1]);
+    if (!newpath) {
+        JS_FreeCString(ctx, oldpath);
+        return JS_EXCEPTION;
+    }
+    ret = rename(oldpath, newpath);
+    JS_FreeCString(ctx, oldpath);
+    JS_FreeCString(ctx, newpath);
+    return js_os_return(ctx, ret);
+}
+
+static JSOSRWHandler *find_rh(int fd)
+{
+    JSOSRWHandler *rh;
+    struct list_head *el;
+    list_for_each(el, &os_rw_handlers) {
+        rh = list_entry(el, JSOSRWHandler, link);
+        if (rh->fd == fd)
+            return rh;
+    }
+    return NULL;
+}
+
+static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh)
+{
+    int i;
+    list_del(&rh->link);
+    for(i = 0; i < 2; i++) {
+        JS_FreeValueRT(rt, rh->rw_func[i]);
+    }
+    js_free_rt(rt, rh);
+}
+
+static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv, int magic)
+{
+    JSOSRWHandler *rh;
+    int fd;
+    JSValueConst func;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    func = argv[1];
+    if (JS_IsNull(func)) {
+        rh = find_rh(fd);
+        if (rh) {
+            JS_FreeValue(ctx, rh->rw_func[magic]);
+            rh->rw_func[magic] = JS_NULL;
+            if (JS_IsNull(rh->rw_func[0]) &&
+                JS_IsNull(rh->rw_func[1])) {
+                /* remove the entry */
+                free_rw_handler(JS_GetRuntime(ctx), rh);
+            }
+        }
+    } else {
+        if (!JS_IsFunction(ctx, func))
+            return JS_ThrowTypeError(ctx, "not a function");
+        rh = find_rh(fd);
+        if (!rh) {
+            rh = js_mallocz(ctx, sizeof(*rh));
+            if (!rh)
+                return JS_EXCEPTION;
+            rh->fd = fd;
+            rh->rw_func[0] = JS_NULL;
+            rh->rw_func[1] = JS_NULL;
+            list_add_tail(&rh->link, &os_rw_handlers);
+        }
+        JS_FreeValue(ctx, rh->rw_func[magic]);
+        rh->rw_func[magic] = JS_DupValue(ctx, func);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSOSSignalHandler *find_sh(int sig_num)
+{
+    JSOSSignalHandler *sh;
+    struct list_head *el;
+    list_for_each(el, &os_signal_handlers) {
+        sh = list_entry(el, JSOSSignalHandler, link);
+        if (sh->sig_num == sig_num)
+            return sh;
+    }
+    return NULL;
+}
+
+static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh)
+{
+    list_del(&sh->link);
+    JS_FreeValueRT(rt, sh->func);
+    js_free_rt(rt, sh);
+}
+
+static void os_signal_handler(int sig_num)
+{
+    os_pending_signals |= ((uint64_t)1 << sig_num);
+}
+
+#if defined(_WIN32)
+typedef void (*sighandler_t)(int sig_num);
+#endif
+
+static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    JSOSSignalHandler *sh;
+    uint32_t sig_num;
+    JSValueConst func;
+    sighandler_t handler;
+    
+    if (JS_ToUint32(ctx, &sig_num, argv[0]))
+        return JS_EXCEPTION;
+    if (sig_num >= 64)
+        return JS_ThrowRangeError(ctx, "invalid signal number");
+    func = argv[1];
+    /* func = null: SIG_DFL, func = undefined, SIG_IGN */
+    if (JS_IsNull(func) || JS_IsUndefined(func)) {
+        sh = find_sh(sig_num);
+        if (sh) {
+            free_sh(JS_GetRuntime(ctx), sh);
+        }
+        if (JS_IsNull(func))
+            handler = SIG_DFL;
+        else
+            handler = SIG_IGN;
+        signal(sig_num, handler);
+    } else {
+        if (!JS_IsFunction(ctx, func))
+            return JS_ThrowTypeError(ctx, "not a function");
+        sh = find_sh(sig_num);
+        if (!sh) {
+            sh = js_mallocz(ctx, sizeof(*sh));
+            if (!sh)
+                return JS_EXCEPTION;
+            sh->sig_num = sig_num;
+            list_add_tail(&sh->link, &os_signal_handlers);
+        }
+        JS_FreeValue(ctx, sh->func);
+        sh->func = JS_DupValue(ctx, func);
+        signal(sig_num, os_signal_handler);
+    }
+    return JS_UNDEFINED;
+}
+
+#if defined(__linux__) || defined(__APPLE__)
+static int64_t get_time_ms(void)
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000);
+}
+#else
+/* more portable, but does not work if the date is updated */
+static int64_t get_time_ms(void)
+{
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
+}
+#endif
+
+static void unlink_timer(JSRuntime *rt, JSOSTimer *th)
+{
+    if (th->link.prev) {
+        list_del(&th->link);
+        th->link.prev = th->link.next = NULL;
+    }
+}
+
+static void free_timer(JSRuntime *rt, JSOSTimer *th)
+{
+    JS_FreeValueRT(rt, th->func);
+    js_free_rt(rt, th);
+}
+
+static JSClassID js_os_timer_class_id;
+
+static void js_os_timer_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
+    if (th) {
+        th->has_object = FALSE;
+        if (!th->link.prev)
+            free_timer(rt, th);
+    }
+}
+
+static void js_os_timer_mark(JSRuntime *rt, JSValueConst val,
+                             JS_MarkFunc *mark_func)
+{
+    JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
+    if (th) {
+        JS_MarkValue(rt, th->func, mark_func);
+    }
+}
+
+static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    int64_t delay;
+    JSValueConst func;
+    JSOSTimer *th;
+    JSValue obj;
+
+    func = argv[0];
+    if (!JS_IsFunction(ctx, func))
+        return JS_ThrowTypeError(ctx, "not a function");
+    if (JS_ToInt64(ctx, &delay, argv[1]))
+        return JS_EXCEPTION;
+    obj = JS_NewObjectClass(ctx, js_os_timer_class_id);
+    if (JS_IsException(obj))
+        return obj;
+    th = js_mallocz(ctx, sizeof(*th));
+    if (!th) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    th->has_object = TRUE;
+    th->timeout = get_time_ms() + delay;
+    th->func = JS_DupValue(ctx, func);
+    list_add_tail(&th->link, &os_timers);
+    JS_SetOpaque(obj, th);
+    return obj;
+}
+
+static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSOSTimer *th = JS_GetOpaque2(ctx, argv[0], js_os_timer_class_id);
+    if (!th)
+        return JS_EXCEPTION;
+    unlink_timer(JS_GetRuntime(ctx), th);
+    return JS_UNDEFINED;
+}
+
+static JSClassDef js_os_timer_class = {
+    "OSTimer",
+    .finalizer = js_os_timer_finalizer,
+    .gc_mark = js_os_timer_mark,
+}; 
+
+static void call_handler(JSContext *ctx, JSValueConst func)
+{
+    JSValue ret, func1;
+    /* 'func' might be destroyed when calling itself (if it frees the
+       handler), so must take extra care */
+    func1 = JS_DupValue(ctx, func);
+    ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL);
+    JS_FreeValue(ctx, func1);
+    if (JS_IsException(ret))
+        js_std_dump_error(ctx);
+    JS_FreeValue(ctx, ret);
+}
+
+#if defined(_WIN32)
+
+static int js_os_poll(JSContext *ctx)
+{
+    int min_delay, console_fd;
+    int64_t cur_time, delay;
+    JSOSRWHandler *rh;
+    struct list_head *el;
+    
+    /* XXX: handle signals if useful */
+
+    if (list_empty(&os_rw_handlers) && list_empty(&os_timers))
+        return -1; /* no more events */
+    
+    /* XXX: only timers and basic console input are supported */
+    if (!list_empty(&os_timers)) {
+        cur_time = get_time_ms();
+        min_delay = 10000;
+        list_for_each(el, &os_timers) {
+            JSOSTimer *th = list_entry(el, JSOSTimer, link);
+            delay = th->timeout - cur_time;
+            if (delay <= 0) {
+                JSValue func;
+                /* the timer expired */
+                func = th->func;
+                th->func = JS_UNDEFINED;
+                unlink_timer(JS_GetRuntime(ctx), th);
+                if (!th->has_object)
+                    free_timer(JS_GetRuntime(ctx), th);
+                call_handler(ctx, func);
+                JS_FreeValue(ctx, func);
+                return 0;
+            } else if (delay < min_delay) {
+                min_delay = delay;
+            }
+        }
+    } else {
+        min_delay = -1;
+    }
+
+    console_fd = -1;
+    list_for_each(el, &os_rw_handlers) {
+        rh = list_entry(el, JSOSRWHandler, link);
+        if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) {
+            console_fd = rh->fd;
+            break;
+        }
+    }
+
+    if (console_fd >= 0) {
+        DWORD ti, ret;
+        HANDLE handle;
+        if (min_delay == -1)
+            ti = INFINITE;
+        else
+            ti = min_delay;
+        handle = (HANDLE)_get_osfhandle(console_fd);
+        ret = WaitForSingleObject(handle, ti);
+        if (ret == WAIT_OBJECT_0) {
+            list_for_each(el, &os_rw_handlers) {
+                rh = list_entry(el, JSOSRWHandler, link);
+                if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) {
+                    call_handler(ctx, rh->rw_func[0]);
+                    /* must stop because the list may have been modified */
+                    break;
+                }
+            }
+        }
+    } else {
+        Sleep(min_delay);
+    }
+    return 0;
+}
+#else
+static int js_os_poll(JSContext *ctx)
+{
+    int ret, fd_max, min_delay;
+    int64_t cur_time, delay;
+    fd_set rfds, wfds;
+    JSOSRWHandler *rh;
+    struct list_head *el;
+    struct timeval tv, *tvp;
+
+    if (unlikely(os_pending_signals != 0)) {
+        JSOSSignalHandler *sh;
+        uint64_t mask;
+        
+        list_for_each(el, &os_signal_handlers) {
+            sh = list_entry(el, JSOSSignalHandler, link);
+            mask = (uint64_t)1 << sh->sig_num;
+            if (os_pending_signals & mask) {
+                os_pending_signals &= ~mask;
+                call_handler(ctx, sh->func);
+                return 0;
+            }
+        }
+    }
+    
+    if (list_empty(&os_rw_handlers) && list_empty(&os_timers))
+        return -1; /* no more events */
+    
+    if (!list_empty(&os_timers)) {
+        cur_time = get_time_ms();
+        min_delay = 10000;
+        list_for_each(el, &os_timers) {
+            JSOSTimer *th = list_entry(el, JSOSTimer, link);
+            delay = th->timeout - cur_time;
+            if (delay <= 0) {
+                JSValue func;
+                /* the timer expired */
+                func = th->func;
+                th->func = JS_UNDEFINED;
+                unlink_timer(JS_GetRuntime(ctx), th);
+                if (!th->has_object)
+                    free_timer(JS_GetRuntime(ctx), th);
+                call_handler(ctx, func);
+                JS_FreeValue(ctx, func);
+                return 0;
+            } else if (delay < min_delay) {
+                min_delay = delay;
+            }
+        }
+        tv.tv_sec = min_delay / 1000;
+        tv.tv_usec = (min_delay % 1000) * 1000;
+        tvp = &tv;
+    } else {
+        tvp = NULL;
+    }
+    
+    FD_ZERO(&rfds);
+    FD_ZERO(&wfds);
+    fd_max = -1;
+    list_for_each(el, &os_rw_handlers) {
+        rh = list_entry(el, JSOSRWHandler, link);
+        fd_max = max_int(fd_max, rh->fd);
+        if (!JS_IsNull(rh->rw_func[0]))
+            FD_SET(rh->fd, &rfds);
+        if (!JS_IsNull(rh->rw_func[1]))
+            FD_SET(rh->fd, &wfds);
+    }
+    
+    ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp);
+    if (ret > 0) {
+        list_for_each(el, &os_rw_handlers) {
+            rh = list_entry(el, JSOSRWHandler, link);
+            if (!JS_IsNull(rh->rw_func[0]) &&
+                FD_ISSET(rh->fd, &rfds)) {
+                call_handler(ctx, rh->rw_func[0]);
+                /* must stop because the list may have been modified */
+                break;
+            }
+            if (!JS_IsNull(rh->rw_func[1])) {
+                FD_SET(rh->fd, &wfds);
+                call_handler(ctx, rh->rw_func[1]);
+                /* must stop because the list may have been modified */
+                break;
+            }
+        }
+    }
+    return 0;
+}
+#endif /* !_WIN32 */
+
+static JSValue make_obj_error(JSContext *ctx,
+                              JSValue obj,
+                              int err)
+{
+    JSValue arr;
+    if (JS_IsException(obj))
+        return obj;
+    arr = JS_NewArray(ctx);
+    if (JS_IsException(arr))
+        return JS_EXCEPTION;
+    JS_DefinePropertyValueUint32(ctx, arr, 0, obj,
+                                 JS_PROP_C_W_E);
+    JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err),
+                                 JS_PROP_C_W_E);
+    return arr;
+}
+
+static JSValue make_string_error(JSContext *ctx,
+                                 const char *buf,
+                                 int err)
+{
+    return make_obj_error(ctx, JS_NewString(ctx, buf), err);
+}
+
+/* return [cwd, errorcode] */
+static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    char buf[PATH_MAX];
+    int err;
+    
+    if (!getcwd(buf, sizeof(buf))) {
+        buf[0] = '\0';
+        err = -errno;
+    } else {
+        err = 0;
+    }
+    return make_string_error(ctx, buf, err);
+}
+
+#if !defined(_WIN32)
+
+/* return [path, errorcode] */
+static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    const char *path;
+    char buf[PATH_MAX], *res;
+    int err;
+
+    path = JS_ToCString(ctx, argv[0]);
+    if (!path)
+        return JS_EXCEPTION;
+    res = realpath(path, buf);
+    JS_FreeCString(ctx, path);
+    if (!res) {
+        buf[0] = '\0';
+        err = -errno;
+    } else {
+        err = 0;
+    }
+    return make_string_error(ctx, buf, err);
+}
+
+static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    int mode, ret;
+    const char *path;
+    
+    if (argc >= 2) {
+        if (JS_ToInt32(ctx, &mode, argv[1]))
+            return JS_EXCEPTION;
+    } else {
+        mode = 0777;
+    }
+    path = JS_ToCString(ctx, argv[0]);
+    if (!path)
+        return JS_EXCEPTION;
+    ret = mkdir(path, mode);
+    JS_FreeCString(ctx, path);
+    return js_os_return(ctx, ret);
+}
+
+static int64_t timespec_to_ms(const struct timespec *tv)
+{
+    return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000);
+}
+
+/* return [obj, errcode] */
+static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv, int is_lstat)
+{
+    const char *path;
+    int err, res;
+    struct stat st;
+    JSValue obj;
+
+    path = JS_ToCString(ctx, argv[0]);
+    if (!path)
+        return JS_EXCEPTION;
+    if (is_lstat)
+        res = lstat(path, &st);
+    else
+        res = stat(path, &st);
+    JS_FreeCString(ctx, path);
+    if (res < 0) {
+        err = -errno;
+        obj = JS_NULL;
+    } else {
+        err = 0;
+        obj = JS_NewObject(ctx);
+        if (JS_IsException(obj))
+            return JS_EXCEPTION;
+        JS_DefinePropertyValueStr(ctx, obj, "dev",
+                                  JS_NewInt64(ctx, st.st_dev),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "ino",
+                                  JS_NewInt64(ctx, st.st_ino),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "mode",
+                                  JS_NewInt32(ctx, st.st_mode),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "nlink",
+                                  JS_NewInt64(ctx, st.st_nlink),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "uid",
+                                  JS_NewInt64(ctx, st.st_uid),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "gid",
+                                  JS_NewInt64(ctx, st.st_gid),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "rdev",
+                                  JS_NewInt64(ctx, st.st_rdev),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "size",
+                                  JS_NewInt64(ctx, st.st_size),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "blocks",
+                                  JS_NewInt64(ctx, st.st_blocks),
+                                  JS_PROP_C_W_E);
+#if defined(__APPLE__)
+        JS_DefinePropertyValueStr(ctx, obj, "atime",
+                                  JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "mtime",
+                                  JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "ctime",
+                                  JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)),
+                                  JS_PROP_C_W_E);
+#else
+        JS_DefinePropertyValueStr(ctx, obj, "atime",
+                                  JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "mtime",
+                                  JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)),
+                                  JS_PROP_C_W_E);
+        JS_DefinePropertyValueStr(ctx, obj, "ctime",
+                                  JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)),
+                                  JS_PROP_C_W_E);
+#endif
+    }
+    return make_obj_error(ctx, obj, err);
+}
+
+static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    const char *target, *linkpath;
+    int err;
+    
+    target = JS_ToCString(ctx, argv[0]);
+    if (!target)
+        return JS_EXCEPTION;
+    linkpath = JS_ToCString(ctx, argv[1]);
+    if (!linkpath) {
+        JS_FreeCString(ctx, target);
+        return JS_EXCEPTION;
+    }
+    err = symlink(target, linkpath);
+    JS_FreeCString(ctx, target);
+    JS_FreeCString(ctx, linkpath);
+    return js_os_return(ctx, err);
+}
+
+/* return [path, errorcode] */
+static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    const char *path;
+    char buf[PATH_MAX];
+    int err;
+    ssize_t res;
+    
+    path = JS_ToCString(ctx, argv[0]);
+    if (!path)
+        return JS_EXCEPTION;
+    res = readlink(path, buf, sizeof(buf) - 1);
+    JS_FreeCString(ctx, path);
+    if (res < 0) {
+        buf[0] = '\0';
+        err = -errno;
+    } else {
+        buf[res] = '\0';
+        err = 0;
+    }
+    return make_string_error(ctx, buf, err);
+}
+
+/* return [array, errorcode] */
+static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    const char *path;
+    DIR *f;
+    struct dirent *d;
+    JSValue obj;
+    int err;
+    uint32_t len;
+    
+    path = JS_ToCString(ctx, argv[0]);
+    if (!path)
+        return JS_EXCEPTION;
+    obj = JS_NewArray(ctx);
+    if (JS_IsException(obj)) {
+        JS_FreeCString(ctx, path);
+        return JS_EXCEPTION;
+    }
+    f = opendir(path);
+    JS_FreeCString(ctx, path);
+    err = 0;
+    if (!f) {
+        err = -errno;
+        goto done;
+    }
+    len = 0;
+    for(;;) {
+        errno = 0;
+        d = readdir(f);
+        if (!d) {
+            err = -errno;
+            break;
+        }
+        JS_DefinePropertyValueUint32(ctx, obj, len++,
+                                     JS_NewString(ctx, d->d_name),
+                                     JS_PROP_C_W_E);
+    }
+    closedir(f);
+ done:
+    return make_obj_error(ctx, obj, err);
+}
+
+static void ms_to_timeval(struct timeval *tv, uint64_t v)
+{
+    tv->tv_sec = v / 1000;
+    tv->tv_usec = (v % 1000) * 1000;
+}
+
+static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    const char *path;
+    int64_t atime, mtime;
+    int ret;
+    struct timeval times[2];
+    
+    if (JS_ToInt64(ctx, &atime, argv[1]))
+        return JS_EXCEPTION;
+    if (JS_ToInt64(ctx, &mtime, argv[2]))
+        return JS_EXCEPTION;
+    path = JS_ToCString(ctx, argv[0]);
+    if (!path)
+        return JS_EXCEPTION;
+    ms_to_timeval(&times[0], atime);
+    ms_to_timeval(&times[1], mtime);
+    ret = utimes(path, times);
+    JS_FreeCString(ctx, path);
+    return js_os_return(ctx, ret);
+}
+
+/* exec(args[, options]) -> exitcode */
+static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    JSValueConst options, args = argv[0];
+    JSValue val, ret_val;
+    const char **exec_argv, *file = NULL, *str, *cwd = NULL;
+    uint32_t exec_argc, i;
+    int ret, pid, status;
+    BOOL block_flag = TRUE, use_path = TRUE;
+    static const char *std_name[3] = { "stdin", "stdout", "stderr" };
+    int std_fds[3];
+    
+    val = JS_GetPropertyStr(ctx, args, "length");
+    if (JS_IsException(val))
+        return JS_EXCEPTION;
+    ret = JS_ToUint32(ctx, &exec_argc, val);
+    JS_FreeValue(ctx, val);
+    if (ret)
+        return JS_EXCEPTION;
+    /* arbitrary limit to avoid overflow */
+    if (exec_argc < 1 || exec_argc > 65535) {
+        return JS_ThrowTypeError(ctx, "invalid number of arguments");
+    }
+    exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1));
+    if (!exec_argv)
+        return JS_EXCEPTION;
+    for(i = 0; i < exec_argc; i++) {
+        val = JS_GetPropertyUint32(ctx, args, i);
+        if (JS_IsException(val))
+            goto exception;
+        str = JS_ToCString(ctx, val);
+        JS_FreeValue(ctx, val);
+        if (!str)
+            goto exception;
+        exec_argv[i] = str;
+    }
+    exec_argv[exec_argc] = NULL;
+
+    for(i = 0; i < 3; i++)
+        std_fds[i] = i;
+    
+    /* get the options, if any */
+    if (argc >= 2) {
+        options = argv[1];
+
+        if (get_bool_option(ctx, &block_flag, options, "block"))
+            goto exception;
+        if (get_bool_option(ctx, &use_path, options, "usePath"))
+            goto exception;
+        
+        val = JS_GetPropertyStr(ctx, options, "file");
+        if (JS_IsException(val))
+            goto exception;
+        if (!JS_IsUndefined(val)) {
+            file = JS_ToCString(ctx, val);
+            JS_FreeValue(ctx, val);
+            if (!file)
+                goto exception;
+        }
+
+        val = JS_GetPropertyStr(ctx, options, "cwd");
+        if (JS_IsException(val))
+            goto exception;
+        if (!JS_IsUndefined(val)) {
+            cwd = JS_ToCString(ctx, val);
+            JS_FreeValue(ctx, val);
+            if (!cwd)
+                goto exception;
+        }
+
+        /* stdin/stdout/stderr handles */
+        for(i = 0; i < 3; i++) {
+            val = JS_GetPropertyStr(ctx, options, std_name[i]);
+            if (JS_IsException(val))
+                goto exception;
+            if (!JS_IsUndefined(val)) {
+                int fd;
+                ret = JS_ToInt32(ctx, &fd, val);
+                JS_FreeValue(ctx, val);
+                if (ret)
+                    goto exception;
+                std_fds[i] = fd;
+            }
+        }
+    }
+
+    pid = fork();
+    if (pid < 0) {
+        JS_ThrowTypeError(ctx, "fork error");
+        goto exception;
+    }
+    if (pid == 0) {
+        /* child */
+        int fd_max = sysconf(_SC_OPEN_MAX);
+
+        /* remap the stdin/stdout/stderr handles if necessary */
+        for(i = 0; i < 3; i++) {
+            if (std_fds[i] != i) {
+                if (dup2(std_fds[i], i) < 0)
+                    _exit(127);
+            }
+        }
+
+        for(i = 3; i < fd_max; i++)
+            close(i);
+        if (cwd) {
+            if (chdir(cwd) < 0)
+                _exit(127);
+        }
+        if (!file)
+            file = exec_argv[0];
+        if (use_path)
+            ret = execvp(file, (char **)exec_argv);
+        else
+            ret = execv(file, (char **)exec_argv);
+        _exit(127);
+    }
+    /* parent */
+    if (block_flag) {
+        for(;;) {
+            ret = waitpid(pid, &status, 0);
+            if (ret == pid) {
+                if (WIFEXITED(status)) {
+                    ret = WEXITSTATUS(status);
+                    break;
+                } else if (WIFSIGNALED(status)) {
+                    ret = -WTERMSIG(status);
+                    break;
+                }
+            }
+        }
+    } else {
+        ret = pid;
+    }
+    ret_val = JS_NewInt32(ctx, ret);
+ done:
+    JS_FreeCString(ctx, file);
+    JS_FreeCString(ctx, cwd);
+    for(i = 0; i < exec_argc; i++)
+        JS_FreeCString(ctx, exec_argv[i]);
+    js_free(ctx, exec_argv);
+    return ret_val;
+ exception:
+    ret_val = JS_EXCEPTION;
+    goto done;
+}
+
+/* waitpid(pid, block) -> [pid, status] */
+static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    int pid, status, options, ret;
+    JSValue obj;
+    
+    if (JS_ToInt32(ctx, &pid, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &options, argv[1]))
+        return JS_EXCEPTION;
+
+    ret = waitpid(pid, &status, options);
+    if (ret < 0) {
+        ret = -errno;
+        status = 0;
+    }
+
+    obj = JS_NewArray(ctx);
+    if (JS_IsException(obj))
+        return obj;
+    JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret),
+                                 JS_PROP_C_W_E);
+    JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status),
+                                 JS_PROP_C_W_E);
+    return obj;
+}    
+
+/* pipe() -> [read_fd, write_fd] or null if error */
+static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    int pipe_fds[2], ret;
+    JSValue obj;
+    
+    ret = pipe(pipe_fds);
+    if (ret < 0)
+        return JS_NULL;
+    obj = JS_NewArray(ctx);
+    if (JS_IsException(obj))
+        return obj;
+    JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]),
+                                 JS_PROP_C_W_E);
+    JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]),
+                                 JS_PROP_C_W_E);
+    return obj;
+}
+
+/* kill(pid, sig) */
+static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    int pid, sig, ret;
+    
+    if (JS_ToInt32(ctx, &pid, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &sig, argv[1]))
+        return JS_EXCEPTION;
+    ret = kill(pid, sig);
+    return js_os_return(ctx, ret);
+}
+
+/* sleep(delay_ms) */
+static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv)
+{
+    int64_t delay;
+    struct timespec ts;
+    int ret;
+    
+    if (JS_ToInt64(ctx, &delay, argv[0]))
+        return JS_EXCEPTION;
+    ts.tv_sec = delay / 1000;
+    ts.tv_nsec = (delay % 1000) * 1000000;
+    ret = nanosleep(&ts, NULL);
+    return js_os_return(ctx, ret);
+}
+
+/* dup(fd) */
+static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val,
+                         int argc, JSValueConst *argv)
+{
+    int fd, ret;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    ret = dup(fd);
+    return js_os_return(ctx, ret);
+}
+
+/* dup2(fd) */
+static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val,
+                         int argc, JSValueConst *argv)
+{
+    int fd, fd2, ret;
+    
+    if (JS_ToInt32(ctx, &fd, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &fd2, argv[1]))
+        return JS_EXCEPTION;
+    ret = dup2(fd, fd2);
+    return js_os_return(ctx, ret);
+}
+
+#endif /* !_WIN32 */
+
+#if defined(_WIN32)
+#define OS_PLATFORM "win32"
+#elif defined(__APPLE__)
+#define OS_PLATFORM "darwin"
+#elif defined(EMSCRIPTEN)
+#define OS_PLATFORM "js"
+#else
+#define OS_PLATFORM "linux"
+#endif
+
+#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE )
+
+static const JSCFunctionListEntry js_os_funcs[] = {
+    JS_CFUNC_DEF("open", 2, js_os_open ),
+    OS_FLAG(O_RDONLY),
+    OS_FLAG(O_WRONLY),
+    OS_FLAG(O_RDWR),
+    OS_FLAG(O_APPEND),
+    OS_FLAG(O_CREAT),
+    OS_FLAG(O_EXCL),
+    OS_FLAG(O_TRUNC),
+#if defined(_WIN32)
+    OS_FLAG(O_BINARY),
+    OS_FLAG(O_TEXT),
+#endif
+    JS_CFUNC_DEF("close", 1, js_os_close ),
+    JS_CFUNC_DEF("seek", 3, js_os_seek ),
+    JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ),
+    JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ),
+    JS_CFUNC_DEF("isatty", 1, js_os_isatty ),
+    JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ),
+    JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ),
+    JS_CFUNC_DEF("remove", 1, js_os_remove ),
+    JS_CFUNC_DEF("rename", 2, js_os_rename ),
+    JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ),
+    JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ),
+    JS_CFUNC_DEF("signal", 2, js_os_signal ),
+    OS_FLAG(SIGINT),
+    OS_FLAG(SIGABRT),
+    OS_FLAG(SIGFPE),
+    OS_FLAG(SIGILL),
+    OS_FLAG(SIGSEGV),
+    OS_FLAG(SIGTERM),
+#if !defined(_WIN32)
+    OS_FLAG(SIGQUIT),
+    OS_FLAG(SIGPIPE),
+    OS_FLAG(SIGALRM),
+    OS_FLAG(SIGUSR1),
+    OS_FLAG(SIGUSR2),
+    OS_FLAG(SIGCHLD),
+    OS_FLAG(SIGCONT),
+    OS_FLAG(SIGSTOP),
+    OS_FLAG(SIGTSTP),
+    OS_FLAG(SIGTTIN),
+    OS_FLAG(SIGTTOU),
+#endif
+    JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
+    JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
+    JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
+    JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ),
+#if !defined(_WIN32)
+    JS_CFUNC_DEF("realpath", 1, js_os_realpath ),
+    JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ),
+    JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ),
+    JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ),
+    /* st_mode constants */
+    OS_FLAG(S_IFMT),
+    OS_FLAG(S_IFIFO),
+    OS_FLAG(S_IFCHR),
+    OS_FLAG(S_IFDIR),
+    OS_FLAG(S_IFBLK),
+    OS_FLAG(S_IFREG),
+    OS_FLAG(S_IFSOCK),
+    OS_FLAG(S_IFLNK),
+    OS_FLAG(S_ISGID),
+    OS_FLAG(S_ISUID),
+    JS_CFUNC_DEF("symlink", 2, js_os_symlink ),
+    JS_CFUNC_DEF("readlink", 1, js_os_readlink ),
+    JS_CFUNC_DEF("readdir", 1, js_os_readdir ),
+    JS_CFUNC_DEF("utimes", 3, js_os_utimes ),
+    JS_CFUNC_DEF("exec", 1, js_os_exec ),
+    JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ),
+    OS_FLAG(WNOHANG),
+    JS_CFUNC_DEF("pipe", 0, js_os_pipe ),
+    JS_CFUNC_DEF("kill", 2, js_os_kill ),
+    JS_CFUNC_DEF("sleep", 1, js_os_sleep ),
+    JS_CFUNC_DEF("dup", 1, js_os_dup ),
+    JS_CFUNC_DEF("dup2", 2, js_os_dup2 ),
+#endif
+};
+
+static int js_os_init(JSContext *ctx, JSModuleDef *m)
+{
+    os_poll_func = js_os_poll;
+    
+    /* OSTimer class */
+    JS_NewClassID(&js_os_timer_class_id);
+    JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class);
+    
+    return JS_SetModuleExportList(ctx, m, js_os_funcs,
+                                  countof(js_os_funcs));
+}
+
+JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name)
+{
+    JSModuleDef *m;
+    m = JS_NewCModule(ctx, module_name, js_os_init);
+    if (!m)
+        return NULL;
+    JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs));
+    return m;
+}
+
+/**********************************************************/
+
+static JSValue js_print(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    int i;
+    const char *str;
+
+    for(i = 0; i < argc; i++) {
+        if (i != 0)
+            putchar(' ');
+        str = JS_ToCString(ctx, argv[i]);
+        if (!str)
+            return JS_EXCEPTION;
+        fputs(str, stdout);
+        JS_FreeCString(ctx, str);
+    }
+    putchar('\n');
+    return JS_UNDEFINED;
+}
+
+void js_std_add_helpers(JSContext *ctx, int argc, char **argv)
+{
+    JSValue global_obj, console, args;
+    int i;
+
+    /* XXX: should these global definitions be enumerable? */
+    global_obj = JS_GetGlobalObject(ctx);
+
+    console = JS_NewObject(ctx);
+    JS_SetPropertyStr(ctx, console, "log",
+                      JS_NewCFunction(ctx, js_print, "log", 1));
+    JS_SetPropertyStr(ctx, global_obj, "console", console);
+
+    /* same methods as the mozilla JS shell */
+    args = JS_NewArray(ctx);
+    for(i = 0; i < argc; i++) {
+        JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i]));
+    }
+    JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args);
+
+    JS_SetPropertyStr(ctx, global_obj, "print",
+                      JS_NewCFunction(ctx, js_print, "print", 1));
+    JS_SetPropertyStr(ctx, global_obj, "__loadScript",
+                      JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1));
+    
+    JS_FreeValue(ctx, global_obj);
+
+    /* XXX: not multi-context */
+    init_list_head(&os_rw_handlers);
+    init_list_head(&os_signal_handlers);
+    init_list_head(&os_timers);
+    os_pending_signals = 0;
+}
+
+void js_std_free_handlers(JSRuntime *rt)
+{
+    struct list_head *el, *el1;
+
+    list_for_each_safe(el, el1, &os_rw_handlers) {
+        JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link);
+        free_rw_handler(rt, rh);
+    }
+
+    list_for_each_safe(el, el1, &os_signal_handlers) {
+        JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link);
+        free_sh(rt, sh);
+    }
+    
+    list_for_each_safe(el, el1, &os_timers) {
+        JSOSTimer *th = list_entry(el, JSOSTimer, link);
+        unlink_timer(rt, th);
+        if (!th->has_object)
+            free_timer(rt, th);
+    }
+}
+
+void js_std_dump_error(JSContext *ctx)
+{
+    JSValue exception_val, val;
+    const char *stack;
+    BOOL is_error;
+    
+    exception_val = JS_GetException(ctx);
+    is_error = JS_IsError(ctx, exception_val);
+    if (!is_error)
+        printf("Throw: ");
+    js_print(ctx, JS_NULL, 1, (JSValueConst *)&exception_val);
+    if (is_error) {
+        val = JS_GetPropertyStr(ctx, exception_val, "stack");
+        if (!JS_IsUndefined(val)) {
+            stack = JS_ToCString(ctx, val);
+            printf("%s\n", stack);
+            JS_FreeCString(ctx, stack);
+        }
+        JS_FreeValue(ctx, val);
+    }
+    JS_FreeValue(ctx, exception_val);
+}
+
+/* main loop which calls the user JS callbacks */
+void js_std_loop(JSContext *ctx)
+{
+    JSContext *ctx1;
+    int err;
+
+    for(;;) {
+        /* execute the pending jobs */
+        for(;;) {
+            err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
+            if (err <= 0) {
+                if (err < 0) {
+                    js_std_dump_error(ctx1);
+                }
+                break;
+            }
+        }
+
+        if (!os_poll_func || os_poll_func(ctx))
+            break;
+    }
+}
+
+void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len,
+                        int load_only)
+{
+    JSValue obj, val;
+    obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE);
+    if (JS_IsException(obj))
+        goto exception;
+    if (load_only) {
+        if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
+            js_module_set_import_meta(ctx, obj, FALSE, FALSE);
+        }
+    } else {
+        if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
+            if (JS_ResolveModule(ctx, obj) < 0) {
+                JS_FreeValue(ctx, obj);
+                goto exception;
+            }
+            js_module_set_import_meta(ctx, obj, FALSE, TRUE);
+        }
+        val = JS_EvalFunction(ctx, obj);
+        if (JS_IsException(val)) {
+        exception:
+            js_std_dump_error(ctx);
+            exit(1);
+        }
+        JS_FreeValue(ctx, val);
+    }
+}
diff --git a/quickjs-libc.h b/quickjs-libc.h
new file mode 100644
index 0000000..ac7947e
--- /dev/null
+++ b/quickjs-libc.h
@@ -0,0 +1,46 @@
+/*
+ * QuickJS C library
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef QUICKJS_LIBC_H
+#define QUICKJS_LIBC_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "quickjs.h"
+
+JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name);
+JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name);
+void js_std_add_helpers(JSContext *ctx, int argc, char **argv);
+void js_std_loop(JSContext *ctx);
+void js_std_free_handlers(JSRuntime *rt);
+void js_std_dump_error(JSContext *ctx);
+uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename);
+int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
+                              JS_BOOL use_realpath, JS_BOOL is_main);
+JSModuleDef *js_module_loader(JSContext *ctx,
+                              const char *module_name, void *opaque);
+void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len,
+                        int flags);
+
+#endif /* QUICKJS_LIBC_H */
diff --git a/quickjs-opcode.h b/quickjs-opcode.h
new file mode 100644
index 0000000..910bb75
--- /dev/null
+++ b/quickjs-opcode.h
@@ -0,0 +1,370 @@
+/*
+ * QuickJS opcode definitions
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ * Copyright (c) 2017-2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifdef FMT
+FMT(none)
+FMT(none_int)
+FMT(none_loc)
+FMT(none_arg)
+FMT(none_var_ref)
+FMT(u8)
+FMT(i8)
+FMT(loc8)
+FMT(const8)
+FMT(label8)
+FMT(u16)
+FMT(i16)
+FMT(label16)
+FMT(npop)
+FMT(npopx)
+FMT(npop_u16)
+FMT(loc)
+FMT(arg)
+FMT(var_ref)
+FMT(u32)
+FMT(i32)
+FMT(const)
+FMT(label)
+FMT(atom)
+FMT(atom_u8)
+FMT(atom_u16)
+FMT(atom_label_u8)
+FMT(atom_label_u16)
+FMT(label_u16)
+#undef FMT
+#endif /* FMT */
+
+#ifdef DEF
+
+#ifndef def
+#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f)
+#endif
+
+DEF(invalid, 1, 0, 0, none) /* never emitted */
+
+/* push values */
+DEF(       push_i32, 5, 0, 1, i32)
+DEF(     push_const, 5, 0, 1, const)
+DEF(       fclosure, 5, 0, 1, const) /* must follow push_const */
+DEF(push_atom_value, 5, 0, 1, atom)
+DEF( private_symbol, 5, 0, 1, atom)
+DEF(      undefined, 1, 0, 1, none)
+DEF(           null, 1, 0, 1, none)
+DEF(      push_this, 1, 0, 1, none) /* only used at the start of a function */
+DEF(     push_false, 1, 0, 1, none)
+DEF(      push_true, 1, 0, 1, none)
+DEF(         object, 1, 0, 1, none)
+DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */
+DEF(           rest, 3, 0, 1, u16) /* only used at the start of a function */
+
+DEF(           drop, 1, 1, 0, none) /* a -> */
+DEF(            nip, 1, 2, 1, none) /* a b -> b */
+DEF(           nip1, 1, 3, 2, none) /* a b c -> b c */
+DEF(            dup, 1, 1, 2, none) /* a -> a a */
+DEF(           dup1, 1, 2, 3, none) /* a b -> a a b */
+DEF(           dup2, 1, 2, 4, none) /* a b -> a b a b */
+DEF(           dup3, 1, 3, 6, none) /* a b c -> a b c a b c */
+DEF(        insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */
+DEF(        insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */
+DEF(        insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */
+DEF(          perm3, 1, 3, 3, none) /* obj a b -> a obj b */
+DEF(          perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */
+DEF(          perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */
+DEF(           swap, 1, 2, 2, none) /* a b -> b a */
+DEF(          swap2, 1, 4, 4, none) /* a b c d -> c d a b */
+DEF(          rot3l, 1, 3, 3, none) /* x a b -> a b x */
+DEF(          rot3r, 1, 3, 3, none) /* a b x -> x a b */
+DEF(          rot4l, 1, 4, 4, none) /* x a b c -> a b c x */
+DEF(          rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */
+
+DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */
+DEF(           call, 3, 1, 1, npop) /* arguments are not counted in n_pop */
+DEF(      tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */
+DEF(    call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */
+DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */
+DEF(     array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */
+DEF(          apply, 3, 3, 1, u16)
+DEF(         return, 1, 1, 0, none)
+DEF(   return_undef, 1, 0, 0, none)
+DEF(check_ctor_return, 1, 1, 2, none)
+DEF(     check_ctor, 1, 0, 0, none)
+DEF(    check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */
+DEF(      add_brand, 1, 2, 0, none) /* this_obj home_obj -> */
+DEF(   return_async, 1, 1, 0, none)
+DEF(          throw, 1, 1, 0, none)
+DEF(      throw_var, 6, 0, 0, atom_u8)
+DEF(           eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */
+DEF(     apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */
+DEF(         regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
+                                       bytecode string */
+DEF( get_super_ctor, 1, 1, 1, none)
+DEF(      get_super, 1, 1, 1, none)
+DEF(         import, 1, 1, 1, none) /* dynamic module import */
+
+DEF(      check_var, 5, 0, 1, atom) /* check if a variable exists */
+DEF(  get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */
+DEF(        get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */
+DEF(        put_var, 5, 1, 0, atom) /* must come after get_var */
+DEF(   put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */
+DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */
+
+DEF(  get_ref_value, 1, 2, 3, none)
+DEF(  put_ref_value, 1, 3, 0, none)
+
+DEF(     define_var, 6, 0, 0, atom_u8)
+DEF(check_define_var, 6, 0, 0, atom_u8)
+DEF(    define_func, 6, 1, 0, atom_u8)
+DEF(      get_field, 5, 1, 1, atom)
+DEF(     get_field2, 5, 1, 2, atom)
+DEF(      put_field, 5, 2, 0, atom)
+DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */
+DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */
+DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */
+DEF(   get_array_el, 1, 2, 1, none)
+DEF(  get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */
+DEF(   put_array_el, 1, 3, 0, none)
+DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */
+DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */
+DEF(   define_field, 5, 2, 1, atom)
+DEF(       set_name, 5, 1, 1, atom)
+DEF(set_name_computed, 1, 2, 2, none)
+DEF(      set_proto, 1, 2, 1, none)
+DEF(set_home_object, 1, 2, 2, none)
+DEF(define_array_el, 1, 3, 2, none)
+DEF(         append, 1, 3, 2, none) /* append enumerated object, update length */
+DEF(copy_data_properties, 2, 3, 3, u8)
+DEF(  define_method, 6, 2, 1, atom_u8)
+DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */
+DEF(   define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */
+DEF(   define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
+
+DEF(        get_loc, 3, 0, 1, loc)
+DEF(        put_loc, 3, 1, 0, loc) /* must come after get_loc */
+DEF(        set_loc, 3, 1, 1, loc) /* must come after put_loc */
+DEF(        get_arg, 3, 0, 1, arg)
+DEF(        put_arg, 3, 1, 0, arg) /* must come after get_arg */
+DEF(        set_arg, 3, 1, 1, arg) /* must come after put_arg */
+DEF(    get_var_ref, 3, 0, 1, var_ref) 
+DEF(    put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */
+DEF(    set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */
+DEF(set_loc_uninitialized, 3, 0, 0, loc)
+DEF(  get_loc_check, 3, 0, 1, loc)
+DEF(  put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
+DEF(  put_loc_check_init, 3, 1, 0, loc)
+DEF(get_var_ref_check, 3, 0, 1, var_ref) 
+DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */
+DEF(put_var_ref_check_init, 3, 1, 0, var_ref)
+DEF(      close_loc, 3, 0, 0, loc)
+DEF(       if_false, 5, 1, 0, label)
+DEF(        if_true, 5, 1, 0, label) /* must come after if_false */
+DEF(           goto, 5, 0, 0, label) /* must come after if_true */
+DEF(          catch, 5, 0, 1, label)
+DEF(          gosub, 5, 0, 0, label) /* used to execute the finally block */
+DEF(            ret, 1, 1, 0, none) /* used to return from the finally block */
+
+DEF(      to_object, 1, 1, 1, none)
+//DEF(      to_string, 1, 1, 1, none)
+DEF(     to_propkey, 1, 1, 1, none)
+DEF(    to_propkey2, 1, 2, 2, none)
+
+DEF(   with_get_var, 10, 1, 0, atom_label_u8)     /* must be in the same order as scope_xxx */
+DEF(   with_put_var, 10, 2, 1, atom_label_u8)     /* must be in the same order as scope_xxx */
+DEF(with_delete_var, 10, 1, 0, atom_label_u8)     /* must be in the same order as scope_xxx */
+DEF(  with_make_ref, 10, 1, 0, atom_label_u8)     /* must be in the same order as scope_xxx */
+DEF(   with_get_ref, 10, 1, 0, atom_label_u8)     /* must be in the same order as scope_xxx */
+DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8)
+
+DEF(   make_loc_ref, 7, 0, 2, atom_u16)
+DEF(   make_arg_ref, 7, 0, 2, atom_u16)
+DEF(make_var_ref_ref, 7, 0, 2, atom_u16)
+DEF(   make_var_ref, 5, 0, 2, atom)
+
+DEF(   for_in_start, 1, 1, 1, none)
+DEF(   for_of_start, 1, 1, 3, none)
+DEF(for_await_of_start, 1, 1, 3, none)
+DEF(    for_in_next, 1, 1, 3, none)
+DEF(    for_of_next, 2, 3, 5, u8)
+DEF(for_await_of_next, 1, 3, 4, none)
+DEF(iterator_get_value_done, 1, 1, 2, none)
+DEF( iterator_close, 1, 3, 0, none)
+DEF(iterator_close_return, 1, 4, 4, none)
+DEF(async_iterator_close, 1, 3, 2, none)
+DEF(async_iterator_next, 1, 4, 4, none)
+DEF(async_iterator_get, 2, 4, 5, u8)
+DEF(  initial_yield, 1, 0, 0, none)
+DEF(          yield, 1, 1, 2, none)
+DEF(     yield_star, 1, 2, 2, none)
+DEF(async_yield_star, 1, 1, 2, none)
+DEF(          await, 1, 1, 1, none)
+
+/* arithmetic/logic operations */
+DEF(            neg, 1, 1, 1, none)
+DEF(           plus, 1, 1, 1, none)
+DEF(            dec, 1, 1, 1, none)
+DEF(            inc, 1, 1, 1, none)
+DEF(       post_dec, 1, 1, 2, none)
+DEF(       post_inc, 1, 1, 2, none)
+DEF(        dec_loc, 2, 0, 0, loc8)
+DEF(        inc_loc, 2, 0, 0, loc8)
+DEF(        add_loc, 2, 1, 0, loc8)
+DEF(            not, 1, 1, 1, none)
+DEF(           lnot, 1, 1, 1, none)
+DEF(         typeof, 1, 1, 1, none)
+DEF(         delete, 1, 2, 1, none)
+DEF(     delete_var, 5, 0, 1, atom)
+
+DEF(            mul, 1, 2, 1, none)
+DEF(            div, 1, 2, 1, none)
+DEF(            mod, 1, 2, 1, none)
+DEF(            add, 1, 2, 1, none)
+DEF(            sub, 1, 2, 1, none)
+DEF(            pow, 1, 2, 1, none)
+DEF(            shl, 1, 2, 1, none)
+DEF(            sar, 1, 2, 1, none)
+DEF(            shr, 1, 2, 1, none)
+DEF(             lt, 1, 2, 1, none)
+DEF(            lte, 1, 2, 1, none)
+DEF(             gt, 1, 2, 1, none)
+DEF(            gte, 1, 2, 1, none)
+DEF(     instanceof, 1, 2, 1, none)
+DEF(             in, 1, 2, 1, none)
+DEF(             eq, 1, 2, 1, none)
+DEF(            neq, 1, 2, 1, none)
+DEF(      strict_eq, 1, 2, 1, none)
+DEF(     strict_neq, 1, 2, 1, none)
+DEF(            and, 1, 2, 1, none)
+DEF(            xor, 1, 2, 1, none)
+DEF(             or, 1, 2, 1, none)
+#ifdef CONFIG_BIGNUM
+DEF(      mul_pow10, 1, 2, 1, none)
+DEF(       math_div, 1, 2, 1, none)
+DEF(       math_mod, 1, 2, 1, none)
+DEF(       math_pow, 1, 2, 1, none)
+#endif
+/* must be the last non short and non temporary opcode */
+DEF(            nop, 1, 0, 0, none) 
+
+/* temporary opcodes: never emitted in the final bytecode */
+
+def(set_arg_valid_upto, 3, 0, 0, arg) /* emitted in phase 1, removed in phase 2 */
+
+def(close_var_object, 1, 0, 0, none) /* emitted in phase 1, removed in phase 2 */
+def(    enter_scope, 3, 0, 0, u16)  /* emitted in phase 1, removed in phase 2 */
+def(    leave_scope, 3, 0, 0, u16)  /* emitted in phase 1, removed in phase 2 */
+
+def(          label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */
+
+def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
+def(  scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
+def(  scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */
+def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
+def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */
+def(  scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
+def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
+def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */
+def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */
+def(scope_put_private_field, 7, 1, 1, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */
+
+def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
+    
+def(       line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */
+
+#if SHORT_OPCODES
+DEF(    push_minus1, 1, 0, 1, none_int)
+DEF(         push_0, 1, 0, 1, none_int)
+DEF(         push_1, 1, 0, 1, none_int)
+DEF(         push_2, 1, 0, 1, none_int)
+DEF(         push_3, 1, 0, 1, none_int)
+DEF(         push_4, 1, 0, 1, none_int)
+DEF(         push_5, 1, 0, 1, none_int)
+DEF(         push_6, 1, 0, 1, none_int)
+DEF(         push_7, 1, 0, 1, none_int)
+DEF(        push_i8, 2, 0, 1, i8)
+DEF(       push_i16, 3, 0, 1, i16)
+DEF(    push_const8, 2, 0, 1, const8)
+DEF(      fclosure8, 2, 0, 1, const8) /* must follow push_const8 */
+DEF(push_empty_string, 1, 0, 1, none)
+
+DEF(       get_loc8, 2, 0, 1, loc8)
+DEF(       put_loc8, 2, 1, 0, loc8)
+DEF(       set_loc8, 2, 1, 1, loc8)
+
+DEF(       get_loc0, 1, 0, 1, none_loc)
+DEF(       get_loc1, 1, 0, 1, none_loc)
+DEF(       get_loc2, 1, 0, 1, none_loc)
+DEF(       get_loc3, 1, 0, 1, none_loc)
+DEF(       put_loc0, 1, 1, 0, none_loc)
+DEF(       put_loc1, 1, 1, 0, none_loc)
+DEF(       put_loc2, 1, 1, 0, none_loc)
+DEF(       put_loc3, 1, 1, 0, none_loc)
+DEF(       set_loc0, 1, 1, 1, none_loc)
+DEF(       set_loc1, 1, 1, 1, none_loc)
+DEF(       set_loc2, 1, 1, 1, none_loc)
+DEF(       set_loc3, 1, 1, 1, none_loc)
+DEF(       get_arg0, 1, 0, 1, none_arg)
+DEF(       get_arg1, 1, 0, 1, none_arg)
+DEF(       get_arg2, 1, 0, 1, none_arg)
+DEF(       get_arg3, 1, 0, 1, none_arg)
+DEF(       put_arg0, 1, 1, 0, none_arg)
+DEF(       put_arg1, 1, 1, 0, none_arg)
+DEF(       put_arg2, 1, 1, 0, none_arg)
+DEF(       put_arg3, 1, 1, 0, none_arg)
+DEF(       set_arg0, 1, 1, 1, none_arg)
+DEF(       set_arg1, 1, 1, 1, none_arg)
+DEF(       set_arg2, 1, 1, 1, none_arg)
+DEF(       set_arg3, 1, 1, 1, none_arg)
+DEF(   get_var_ref0, 1, 0, 1, none_var_ref)
+DEF(   get_var_ref1, 1, 0, 1, none_var_ref)
+DEF(   get_var_ref2, 1, 0, 1, none_var_ref)
+DEF(   get_var_ref3, 1, 0, 1, none_var_ref)
+DEF(   put_var_ref0, 1, 1, 0, none_var_ref)
+DEF(   put_var_ref1, 1, 1, 0, none_var_ref)
+DEF(   put_var_ref2, 1, 1, 0, none_var_ref)
+DEF(   put_var_ref3, 1, 1, 0, none_var_ref)
+DEF(   set_var_ref0, 1, 1, 1, none_var_ref)
+DEF(   set_var_ref1, 1, 1, 1, none_var_ref)
+DEF(   set_var_ref2, 1, 1, 1, none_var_ref)
+DEF(   set_var_ref3, 1, 1, 1, none_var_ref)
+
+DEF(     get_length, 1, 1, 1, none)
+
+DEF(      if_false8, 2, 1, 0, label8)
+DEF(       if_true8, 2, 1, 0, label8) /* must come after if_false8 */
+DEF(          goto8, 2, 0, 0, label8) /* must come after if_true8 */
+DEF(         goto16, 3, 0, 0, label16)
+
+DEF(          call0, 1, 1, 1, npopx)
+DEF(          call1, 1, 1, 1, npopx)
+DEF(          call2, 1, 1, 1, npopx)
+DEF(          call3, 1, 1, 1, npopx)
+
+DEF(   is_undefined, 1, 1, 1, none)
+DEF(        is_null, 1, 1, 1, none)
+DEF(    is_function, 1, 1, 1, none)
+#endif
+
+#undef DEF
+#undef def
+#endif  /* DEF */
diff --git a/quickjs.c b/quickjs.c
new file mode 100644
index 0000000..5d833b0
--- /dev/null
+++ b/quickjs.c
@@ -0,0 +1,50241 @@
+/*
+ * QuickJS Javascript Engine
+ * 
+ * Copyright (c) 2017-2019 Fabrice Bellard
+ * Copyright (c) 2017-2019 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <time.h>
+#include <fenv.h>
+#include <math.h>
+#if defined(__APPLE__)
+#include <malloc/malloc.h>
+#elif defined(__linux__)
+#include <malloc.h>
+#endif
+
+#include "cutils.h"
+#include "list.h"
+#include "quickjs.h"
+#include "libregexp.h"
+#ifdef CONFIG_BIGNUM
+#include "libbf.h"
+#endif
+
+#define OPTIMIZE         1
+#define SHORT_OPCODES    1
+#if defined(EMSCRIPTEN)
+#define DIRECT_DISPATCH  0
+#else
+#define DIRECT_DISPATCH  1
+#endif
+
+#if defined(__APPLE__)
+#define MALLOC_OVERHEAD  0
+#else
+#define MALLOC_OVERHEAD  8
+#endif
+
+#if !defined(_WIN32)
+/* define it if printf uses the RNDN rounding mode instead of RNDNA */
+#define CONFIG_PRINTF_RNDN
+#endif
+
+/* define to include Atomics.* operations which depend on the OS
+   threads */
+#if !defined(EMSCRIPTEN)
+#define CONFIG_ATOMICS
+#endif
+
+/* dump object free */
+//#define DUMP_FREE
+//#define DUMP_CLOSURE
+/* dump the bytecode of the compiled functions: combination of bits
+   1: dump pass 3 final byte code
+   2: dump pass 2 code
+   4: dump pass 1 code
+   8: dump stdlib functions
+  16: dump bytecode in hex
+  32: dump line number table
+ */
+//#define DUMP_BYTECODE  (1)
+/* dump the occurence of the automatic GC */
+//#define DUMP_GC
+/* dump objects freed by the garbage collector */
+//#define DUMP_GC_FREE
+/* dump objects leaking when freeing the runtime */
+//#define DUMP_LEAKS  1
+/* dump memory usage before running the garbage collector */
+//#define DUMP_MEM
+//#define DUMP_OBJECTS    /* dump objects in JS_FreeContext */
+//#define DUMP_ATOMS      /* dump atoms in JS_FreeContext */
+//#define DUMP_SHAPES     /* dump shapes in JS_FreeContext */
+//#define DUMP_MODULE_RESOLVE
+//#define DUMP_PROMISE
+//#define DUMP_READ_OBJECT
+
+/* test the GC by forcing it before each object allocation */
+//#define FORCE_GC_AT_MALLOC
+
+#ifdef CONFIG_ATOMICS
+#include <pthread.h>
+#include <stdatomic.h>
+#include <errno.h>
+#endif
+
+enum {
+    /* classid tag        */    /* union usage   | properties */
+    JS_CLASS_OBJECT = 1,        /* must be first */
+    JS_CLASS_ARRAY,             /* u.array       | length */
+    JS_CLASS_ERROR,
+    JS_CLASS_NUMBER,            /* u.object_data */
+    JS_CLASS_STRING,            /* u.object_data */
+    JS_CLASS_BOOLEAN,           /* u.object_data */
+    JS_CLASS_SYMBOL,            /* u.object_data */
+    JS_CLASS_ARGUMENTS,         /* u.array       | length */
+    JS_CLASS_MAPPED_ARGUMENTS,  /*               | length */
+    JS_CLASS_DATE,              /* u.object_data */
+    JS_CLASS_MODULE_NS,
+    JS_CLASS_C_FUNCTION,        /* u.cfunc */
+    JS_CLASS_BYTECODE_FUNCTION, /* u.func */
+    JS_CLASS_BOUND_FUNCTION,    /* u.bound_function */
+    JS_CLASS_C_FUNCTION_DATA,   /* u.c_function_data_record */
+    JS_CLASS_GENERATOR_FUNCTION, /* u.func */
+    JS_CLASS_FOR_IN_ITERATOR,   /* u.for_in_iterator */
+    JS_CLASS_REGEXP,            /* u.regexp */
+    JS_CLASS_ARRAY_BUFFER,      /* u.array_buffer */
+    JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */
+    JS_CLASS_UINT8C_ARRAY,      /* u.array (typed_array) */
+    JS_CLASS_INT8_ARRAY,        /* u.array (typed_array) */
+    JS_CLASS_UINT8_ARRAY,       /* u.array (typed_array) */
+    JS_CLASS_INT16_ARRAY,       /* u.array (typed_array) */
+    JS_CLASS_UINT16_ARRAY,      /* u.array (typed_array) */
+    JS_CLASS_INT32_ARRAY,       /* u.array (typed_array) */
+    JS_CLASS_UINT32_ARRAY,      /* u.array (typed_array) */
+#ifdef CONFIG_BIGNUM
+    JS_CLASS_BIG_INT64_ARRAY,   /* u.array (typed_array) */
+    JS_CLASS_BIG_UINT64_ARRAY,  /* u.array (typed_array) */
+#endif
+    JS_CLASS_FLOAT32_ARRAY,     /* u.array (typed_array) */
+    JS_CLASS_FLOAT64_ARRAY,     /* u.array (typed_array) */
+    JS_CLASS_DATAVIEW,          /* u.typed_array */
+#ifdef CONFIG_BIGNUM
+    JS_CLASS_BIG_INT,           /* u.object_data */
+    JS_CLASS_BIG_FLOAT,         /* u.object_data */
+    JS_CLASS_FLOAT_ENV,         /* u.float_env */
+#endif
+    JS_CLASS_MAP,               /* u.map_state */
+    JS_CLASS_SET,               /* u.map_state */
+    JS_CLASS_WEAKMAP,           /* u.map_state */
+    JS_CLASS_WEAKSET,           /* u.map_state */
+    JS_CLASS_MAP_ITERATOR,      /* u.map_iterator_data */
+    JS_CLASS_SET_ITERATOR,      /* u.map_iterator_data */
+    JS_CLASS_ARRAY_ITERATOR,    /* u.array_iterator_data */
+    JS_CLASS_STRING_ITERATOR,   /* u.array_iterator_data */
+    JS_CLASS_REGEXP_STRING_ITERATOR,   /* u.regexp_string_iterator_data */
+    JS_CLASS_GENERATOR,         /* u.generator_data */
+    JS_CLASS_PROXY,             /* u.proxy_data */
+    JS_CLASS_PROMISE,           /* u.promise_data */
+    JS_CLASS_PROMISE_RESOLVE_FUNCTION,  /* u.promise_function_data */
+    JS_CLASS_PROMISE_REJECT_FUNCTION,   /* u.promise_function_data */
+    JS_CLASS_ASYNC_FUNCTION,            /* u.func */
+    JS_CLASS_ASYNC_FUNCTION_RESOLVE,    /* u.async_function_data */
+    JS_CLASS_ASYNC_FUNCTION_REJECT,     /* u.async_function_data */
+    JS_CLASS_ASYNC_FROM_SYNC_ITERATOR,  /* u.async_from_sync_iterator_data */
+    JS_CLASS_ASYNC_GENERATOR_FUNCTION,  /* u.func */
+    JS_CLASS_ASYNC_GENERATOR,   /* u.async_generator_data */
+
+    JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
+};
+
+/* number of typed array types */
+#define JS_TYPED_ARRAY_COUNT  (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1)
+static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT];
+#define typed_array_size_log2(classid)  (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY])
+
+typedef enum JSErrorEnum {
+    JS_EVAL_ERROR,
+    JS_RANGE_ERROR,
+    JS_REFERENCE_ERROR,
+    JS_SYNTAX_ERROR,
+    JS_TYPE_ERROR,
+    JS_URI_ERROR,
+    JS_INTERNAL_ERROR,
+
+    JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */
+} JSErrorEnum;
+
+#define JS_MAX_LOCAL_VARS 65536
+#define JS_STACK_SIZE_MAX 65536
+#define JS_STRING_LEN_MAX ((1 << 30) - 1)
+
+#define __exception __attribute__((warn_unused_result))
+
+typedef struct JSShape JSShape;
+typedef struct JSString JSString;
+typedef struct JSString JSAtomStruct;
+
+struct JSRuntime {
+    JSMallocFunctions mf;
+    JSMallocState malloc_state;
+    const char *rt_info;
+
+    int atom_hash_size; /* power of two */
+    int atom_count;
+    int atom_size;
+    int atom_count_resize; /* resize hash table at this count */
+    uint32_t *atom_hash;
+    JSAtomStruct **atom_array;
+    int atom_free_index; /* 0 = none */
+
+    int class_count;    /* size of class_array */
+    JSClass *class_array;
+
+    struct list_head context_list; /* list of JSContext.link */
+    /* list of allocated objects (used by the garbage collector) */
+    struct list_head obj_list; /* list of JSObject.link */
+    size_t malloc_gc_threshold;
+#ifdef DUMP_LEAKS
+    struct list_head string_list; /* list of JSString.link */
+#endif
+    struct list_head tmp_obj_list; /* used during gc */
+    struct list_head free_obj_list; /* used during gc */
+    struct list_head *el_next; /* used during gc */
+
+    JSInterruptHandler *interrupt_handler;
+    void *interrupt_opaque;
+
+    struct list_head job_list; /* list of JSJobEntry.link */
+
+    JSModuleNormalizeFunc *module_normalize_func;
+    JSModuleLoaderFunc *module_loader_func;
+    void *module_loader_opaque;
+
+    BOOL in_gc_sweep : 8;
+    BOOL can_block : 8; /* TRUE if Atomics.wait can block */
+
+    /* Shape hash table */
+    int shape_hash_bits;
+    int shape_hash_size;
+    int shape_hash_count; /* number of hashed shapes */
+    JSShape **shape_hash;
+#ifdef CONFIG_BIGNUM
+    bf_context_t bf_ctx;
+#endif
+};
+
+struct JSClass {
+    uint32_t class_id; /* 0 means free entry */
+    JSAtom class_name;
+    JSClassFinalizer *finalizer;
+    JSClassGCMark *gc_mark;
+    JSClassCall *call;
+    /* pointers for exotic behavior, can be NULL if none are present */
+    const JSClassExoticMethods *exotic;
+};
+
+#define JS_MODE_STRICT (1 << 0)
+#define JS_MODE_STRIP  (1 << 1)
+#define JS_MODE_BIGINT (1 << 2)
+#define JS_MODE_MATH   (1 << 3)
+
+typedef struct JSStackFrame {
+    struct JSStackFrame *prev_frame; /* NULL if first stack frame */
+    JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */
+    JSValue *arg_buf; /* arguments */
+    JSValue *var_buf; /* variables */
+    struct list_head var_ref_list; /* list of JSVarRef.link */
+    const uint8_t *cur_pc; /* only used in bytecode functions : PC of the
+                        instruction after the call */
+    int arg_count;
+    int js_mode; /* for C functions: 0 */
+    /* only used in generators. Current stack pointer value. NULL if
+       the function is running. */ 
+    JSValue *cur_sp;
+} JSStackFrame;
+
+typedef struct JSGCHeader {
+    uint8_t mark;
+} JSGCHeader;
+
+typedef struct JSVarRef {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    JSGCHeader gc_header; /* must come after JSRefCountHeader, 8-bit */
+    uint8_t is_arg : 1;
+    int var_idx; /* index of the corresponding function variable on
+                    the stack */
+    struct list_head link; /* prev = NULL if no longer on the stack */
+    JSValue *pvalue; /* pointer to the value, either on the stack or
+                        to 'value' */
+    JSValue value; /* used when the variable is no longer on the stack */
+} JSVarRef;
+
+#ifdef CONFIG_BIGNUM
+typedef struct JSFloatEnv {
+    limb_t prec;
+    bf_flags_t flags;
+    unsigned int status;
+} JSFloatEnv;
+
+/* the same structure is used for big integers and big floats. Big
+   integers are never infinite or NaNs */
+typedef struct JSBigFloat {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    bf_t num;
+} JSBigFloat;
+#endif
+
+/* must be large enough to have a negligible runtime cost and small
+   enough to call the interrupt callback often. */
+#define JS_INTERRUPT_COUNTER_INIT 10000
+
+struct JSContext {
+    JSRuntime *rt;
+    struct list_head link;
+    const uint8_t *stack_top;
+    size_t stack_size; /* in bytes */
+
+    JSValue current_exception;
+    /* true if a backtrace needs to be added to the current exception
+       (the backtrace generation cannot be done immediately in a bytecode
+       function) */
+    BOOL exception_needs_backtrace : 8;
+    /* true if inside an out of memory error, to avoid recursing */
+    BOOL in_out_of_memory : 8;
+    uint16_t binary_object_count;
+    int binary_object_size;
+
+    JSShape *array_shape;   /* initial shape for Array objects */
+
+    JSStackFrame *current_stack_frame;
+
+    JSValue *class_proto;
+    JSValue function_proto;
+    JSValue function_ctor;
+    JSValue regexp_ctor;
+    JSValue promise_ctor;
+    JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
+    JSValue iterator_proto;
+    JSValue async_iterator_proto;
+    JSValue array_proto_values;
+    JSValue throw_type_error;
+    JSValue eval_obj;
+
+    JSValue global_obj; /* global object */
+    JSValue global_var_obj; /* contains the global let/const definitions */
+
+    uint64_t random_state;
+#ifdef CONFIG_BIGNUM
+    bf_context_t *bf_ctx;   /* points to rt->bf_ctx, shared by all contexts */
+    JSFloatEnv fp_env; /* global FP environment */
+#endif
+    /* when the counter reaches zero, JSRutime.interrupt_handler is called */
+    int interrupt_counter;
+    BOOL is_error_property_enabled;
+
+    struct list_head loaded_modules; /* list of JSModuleDef.link */
+
+    /* if NULL, RegExp compilation is not supported */
+    JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern,
+                              JSValueConst flags);
+    /* if NULL, eval is not supported */
+    JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj,
+                             const char *input, size_t input_len,
+                             const char *filename, int flags, int scope_idx);
+    void *user_opaque;
+};
+
+typedef union JSFloat64Union {
+    double d;
+    uint64_t u64;
+    uint32_t u32[2];
+} JSFloat64Union;
+
+enum {
+    JS_ATOM_TYPE_STRING = 1,
+    JS_ATOM_TYPE_GLOBAL_SYMBOL,
+    JS_ATOM_TYPE_SYMBOL,
+    JS_ATOM_TYPE_PRIVATE,
+};
+
+enum {
+    JS_ATOM_HASH_SYMBOL,
+    JS_ATOM_HASH_PRIVATE,
+};
+
+typedef enum {
+    JS_ATOM_KIND_STRING,
+    JS_ATOM_KIND_SYMBOL,
+    JS_ATOM_KIND_PRIVATE,
+} JSAtomKindEnum;
+
+#define JS_ATOM_HASH_MASK  ((1 << 30) - 1)
+
+struct JSString {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    uint32_t len : 31;
+    uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
+    /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3,
+       for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3
+       XXX: could change encoding to have one more bit in hash */
+    uint32_t hash : 30;
+    uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
+    uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */
+#ifdef DUMP_LEAKS
+    struct list_head link; /* string list */
+#endif
+    union {
+        uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */
+        uint16_t str16[0];
+    } u;
+};
+
+typedef struct JSClosureVar {
+    uint8_t is_local : 1;
+    uint8_t is_arg : 1;
+    uint8_t is_const : 1;
+    uint8_t is_lexical : 1;
+    uint8_t var_kind : 3; /* see JSVarKindEnum */
+    /* 9 bits available */
+    uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the
+                    parent function. otherwise: index to a closure
+                    variable of the parent function */
+    JSAtom var_name;
+} JSClosureVar;
+
+typedef struct JSVarScope {
+    int parent;  /* index into fd->scopes of the enclosing scope */
+    int first;   /* index into fd->vars of the last variable in this scope */
+} JSVarScope;
+
+typedef enum {
+    /* XXX: add more variable kinds here instead of using bit fields */
+    JS_VAR_NORMAL,
+    JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */
+    JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator
+                                 function declaration */
+    JS_VAR_CATCH,
+    JS_VAR_PRIVATE_FIELD,
+    JS_VAR_PRIVATE_METHOD,
+    JS_VAR_PRIVATE_GETTER,
+    JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */
+    JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */
+} JSVarKindEnum;
+
+typedef struct JSVarDef {
+    JSAtom var_name;
+    int scope_level;   /* index into fd->scopes of this variable lexical scope */
+    int scope_next;    /* index into fd->vars of the next variable in the
+                        * same or enclosing lexical scope */
+    uint8_t is_func_var : 1; /* used for the function self reference */
+    uint8_t is_const : 1;
+    uint8_t is_lexical : 1;
+    uint8_t is_captured : 1;
+    uint8_t var_kind : 4; /* see JSVarKindEnum */
+    /* only used during compilation: function pool index for lexical
+       variables with var_kind =
+       JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of
+       the definition of the 'var' variables (they have scope_level =
+       0) */
+    int func_pool_or_scope_idx : 24; /* only used during compilation */
+} JSVarDef;
+
+/* for the encoding of the pc2line table */
+#define PC2LINE_BASE     (-1)
+#define PC2LINE_RANGE    5
+#define PC2LINE_OP_FIRST 1
+#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE)
+
+typedef enum JSFunctionKindEnum {
+    JS_FUNC_NORMAL = 0,
+    JS_FUNC_GENERATOR = (1 << 0),
+    JS_FUNC_ASYNC = (1 << 1),
+    JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC),
+} JSFunctionKindEnum;
+
+typedef struct JSFunctionBytecode {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    JSGCHeader gc_header; /* must come after header, 8-bit */
+    uint8_t js_mode;
+    uint8_t has_prototype : 1; /* true if a prototype field is necessary */
+    uint8_t has_simple_parameter_list : 1;
+    uint8_t is_derived_class_constructor : 1;
+    /* true if home_object needs to be initialized */
+    uint8_t need_home_object : 1;
+    uint8_t func_kind : 2;
+    uint8_t new_target_allowed : 1;
+    uint8_t super_call_allowed : 1;
+    uint8_t super_allowed : 1;
+    uint8_t arguments_allowed : 1;
+    uint8_t has_debug : 1;
+    uint8_t read_only_bytecode : 1;
+    /* XXX: 4 bits available */
+    uint8_t *byte_code_buf; /* (self pointer) */
+    int byte_code_len;
+    JSAtom func_name;
+    JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */
+    JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */
+    uint16_t arg_count;
+    uint16_t var_count;
+    uint16_t defined_arg_count; /* for length function property */
+    uint16_t stack_size; /* maximum stack size */
+    JSValue *cpool; /* constant pool (self pointer) */
+    int cpool_count;
+    int closure_var_count;
+    struct {
+        /* debug info, move to separate structure to save memory? */
+        JSAtom filename;
+        int line_num;
+        int source_len;
+        int pc2line_len;
+        uint8_t *pc2line_buf;
+        char *source;
+    } debug;
+} JSFunctionBytecode;
+
+typedef struct JSBoundFunction {
+    JSValue func_obj;
+    JSValue this_val;
+    int argc;
+    JSValue argv[0];
+} JSBoundFunction;
+
+typedef enum JSIteratorKindEnum {
+    JS_ITERATOR_KIND_KEY,
+    JS_ITERATOR_KIND_VALUE,
+    JS_ITERATOR_KIND_KEY_AND_VALUE,
+} JSIteratorKindEnum;
+
+typedef struct JSForInIterator {
+    JSValue obj;
+    BOOL is_array;
+    uint32_t array_length;
+    uint32_t idx;
+} JSForInIterator;
+
+typedef struct JSRegExp {
+    JSString *pattern;
+    JSString *bytecode; /* also contains the flags */
+} JSRegExp;
+
+typedef struct JSProxyData {
+    JSValue target;
+    JSValue handler;
+    JSValue proto;
+    uint8_t is_func;
+    uint8_t is_revoked;
+} JSProxyData;
+
+typedef struct JSArrayBuffer {
+    int byte_length; /* 0 if detached */
+    uint8_t detached;
+    uint8_t shared; /* if shared, the array buffer cannot be detached */
+    uint8_t *data; /* NULL if detached */
+    struct list_head array_list;
+    void *opaque;
+    JSFreeArrayBufferDataFunc *free_func;
+} JSArrayBuffer;
+
+typedef struct JSTypedArray {
+    struct list_head link; /* link to arraybuffer */
+    JSObject *obj; /* back pointer to the TypedArray/DataView object */
+    JSObject *buffer; /* based array buffer */
+    uint32_t offset; /* offset in the array buffer */
+    uint32_t length; /* length in the array buffer */
+} JSTypedArray;
+
+typedef struct JSAsyncFunctionState {
+    JSValue this_val; /* 'this' generator argument */
+    int argc; /* number of function arguments */
+    BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */
+    JSStackFrame frame;
+} JSAsyncFunctionState;
+
+/* XXX: could use an object instead to avoid the
+   JS_TAG_ASYNC_FUNCTION tag for the GC */
+typedef struct JSAsyncFunctionData {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    JSGCHeader gc_header; /* must come after JSRefCountHeader, 8-bit */
+    JSValue resolving_funcs[2];
+    BOOL is_active; /* true if the async function state is valid */
+    JSAsyncFunctionState func_state;
+} JSAsyncFunctionData;
+
+typedef struct JSReqModuleEntry {
+    JSAtom module_name;
+    JSModuleDef *module; /* used using resolution */
+} JSReqModuleEntry;
+
+typedef enum JSExportTypeEnum {
+    JS_EXPORT_TYPE_LOCAL,
+    JS_EXPORT_TYPE_INDIRECT,
+} JSExportTypeEnum;
+
+typedef struct JSExportEntry {
+    union {
+        struct {
+            int var_idx; /* closure variable index */
+            JSVarRef *var_ref; /* if != NULL, reference to the variable */
+        } local; /* for local export */
+        int req_module_idx; /* module for indirect export */
+    } u;
+    JSExportTypeEnum export_type;
+    JSAtom local_name; /* '*' if export ns from. not used for local
+                          export after compilation */
+    JSAtom export_name; /* exported variable name */
+} JSExportEntry;
+
+typedef struct JSStarExportEntry {
+    int req_module_idx; /* in req_module_entries */
+} JSStarExportEntry;
+
+typedef struct JSImportEntry {
+    int var_idx; /* closure variable index */
+    JSAtom import_name;
+    int req_module_idx; /* in req_module_entries */
+} JSImportEntry;
+
+struct JSModuleDef {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    JSAtom module_name;
+    struct list_head link;
+
+    JSReqModuleEntry *req_module_entries;
+    int req_module_entries_count;
+    int req_module_entries_size;
+
+    JSExportEntry *export_entries;
+    int export_entries_count;
+    int export_entries_size;
+
+    JSStarExportEntry *star_export_entries;
+    int star_export_entries_count;
+    int star_export_entries_size;
+
+    JSImportEntry *import_entries;
+    int import_entries_count;
+    int import_entries_size;
+
+    JSValue module_ns;
+    JSValue func_obj; /* only used for JS modules */
+    JSModuleInitFunc *init_func; /* only used for C modules */
+    BOOL resolved : 8;
+    BOOL instantiated : 8;
+    BOOL evaluated : 8;
+    BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */
+    /* true if evaluation yielded an exception. It is saved in
+       eval_exception */
+    BOOL eval_has_exception : 8; 
+    JSValue eval_exception;
+    JSValue meta_obj; /* for import.meta */
+};
+
+typedef struct JSJobEntry {
+    struct list_head link;
+    JSContext *ctx;
+    JSJobFunc *job_func;
+    int argc;
+    JSValue argv[0];
+} JSJobEntry;
+
+typedef struct JSProperty {
+    union {
+        JSValue value;      /* JS_PROP_NORMAL */
+        struct {            /* JS_PROP_GETSET */
+            JSObject *getter; /* NULL if undefined */
+            JSObject *setter; /* NULL if undefined */
+        } getset;
+        JSVarRef *var_ref;  /* JS_PROP_VARREF */
+        struct {            /* JS_PROP_AUTOINIT */
+            int (*init_func)(JSContext *ctx, JSObject *obj,
+                             JSAtom prop, void *opaque);
+            void *opaque;
+        } init;
+    } u;
+} JSProperty;
+
+#define JS_PROP_INITIAL_SIZE 2
+#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */
+#define JS_ARRAY_INITIAL_SIZE 2
+
+typedef struct JSShapeProperty {
+    uint32_t hash_next : 26; /* 0 if last in list */
+    uint32_t flags : 6;   /* JS_PROP_XXX */
+    JSAtom atom; /* JS_ATOM_NULL = free property entry */
+} JSShapeProperty;
+
+struct JSShape {
+    uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1
+                                  before the start of the structure. */
+    JSRefCountHeader header; /* must come first, 32-bit */
+    JSGCHeader gc_header; /* must come after JSRefCountHeader, 8-bit */
+    /* true if the shape is inserted in the shape hash table. If not,
+       JSShape.hash is not valid */
+    uint8_t is_hashed;
+    /* If true, the shape may have small array index properties 'n' with 0
+       <= n <= 2^31-1. If false, the shape is guaranteed not to have
+       small array index properties */
+    uint8_t has_small_array_index;
+    uint32_t hash; /* current hash value */
+    uint32_t prop_hash_mask;
+    int prop_size; /* allocated properties */
+    int prop_count;
+    JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */
+    JSObject *proto;
+    JSShapeProperty prop[0]; /* prop_size elements */
+};
+struct JSObject {
+    JSRefCountHeader header; /* must come first, 32-bit */
+    JSGCHeader gc_header; /* must come after JSRefCountHeader, 8-bit */
+    uint8_t extensible : 1;
+    uint8_t free_mark : 1; /* only used when freeing objects with cycles */
+    uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */
+    uint8_t fast_array : 1; /* TRUE if u.array is used for get/put */
+    uint8_t is_constructor : 1; /* TRUE if object is a constructor function */
+    uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */
+    uint8_t is_class : 1; /* TRUE if object is a class constructor */
+    uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */
+    uint16_t class_id; /* see JS_CLASS_x */
+    /* byte offsets: 8/8 */
+    struct list_head link; /* object list */
+    /* byte offsets: 16/24 */
+    JSShape *shape; /* prototype and property names + flag */
+    JSProperty *prop; /* array of properties */
+    /* byte offsets: 24/40 */
+    struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */
+    /* byte offsets: 28/48 */
+    union {
+        void *opaque;
+        struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */
+        struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */
+        struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */
+        struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */
+        struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */
+#ifdef CONFIG_BIGNUM
+        struct JSFloatEnv *float_env; /* JS_CLASS_FLOAT_ENV */
+#endif
+        struct JSMapState *map_state;   /* JS_CLASS_MAP..JS_CLASS_WEAKSET */
+        struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */
+        struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */
+        struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */
+        struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */
+        struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */
+        struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */
+        struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */
+        struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
+        struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
+        struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */
+        struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */
+            /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */
+            struct JSFunctionBytecode *function_bytecode;
+            JSVarRef **var_refs;
+            JSObject *home_object; /* for 'super' access */
+        } func;
+        struct { /* JS_CLASS_C_FUNCTION: 8/12 bytes */
+            JSCFunctionType c_function;
+            uint8_t length;
+            uint8_t cproto;
+            int16_t magic;
+        } cfunc;
+        /* array part for fast arrays and typed arrays */
+        struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */
+            union {
+                uint32_t size;          /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */
+                struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */
+            } u1;
+            union {
+                JSValue *values;        /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ 
+                void *ptr;              /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */
+                int8_t *int8_ptr;       /* JS_CLASS_INT8_ARRAY */
+                uint8_t *uint8_ptr;     /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */
+                int16_t *int16_ptr;     /* JS_CLASS_INT16_ARRAY */
+                uint16_t *uint16_ptr;   /* JS_CLASS_UINT16_ARRAY */
+                int32_t *int32_ptr;     /* JS_CLASS_INT32_ARRAY */
+                uint32_t *uint32_ptr;   /* JS_CLASS_UINT32_ARRAY */
+                int64_t *int64_ptr;     /* JS_CLASS_INT64_ARRAY */
+                uint64_t *uint64_ptr;   /* JS_CLASS_UINT64_ARRAY */
+                float *float_ptr;       /* JS_CLASS_FLOAT32_ARRAY */
+                double *double_ptr;     /* JS_CLASS_FLOAT64_ARRAY */
+            } u;
+            uint32_t count; /* <= 2^31-1. 0 for a detached typed array */
+        } array;    /* 12/20 bytes */
+        JSRegExp regexp;    /* JS_CLASS_REGEXP: 8/16 bytes */
+        JSValue object_data;    /* for JS_SetObjectData(): 8/16/16 bytes */
+    } u;
+    /* byte sizes: 40/48/72 */
+};
+enum {
+    JS_ATOM_NULL,
+#define DEF(name, str) JS_ATOM_ ## name,
+#include "quickjs-atom.h"
+#undef DEF
+    JS_ATOM_END,
+};
+#define JS_ATOM_LAST_KEYWORD JS_ATOM_super
+#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield
+
+static const char js_atom_init[] =
+#define DEF(name, str) str "\0"
+#include "quickjs-atom.h"
+#undef DEF
+;
+
+typedef enum OPCodeFormat {
+#define FMT(f) OP_FMT_ ## f,
+#define DEF(id, size, n_pop, n_push, f)
+#include "quickjs-opcode.h"
+#undef DEF
+#undef FMT
+} OPCodeFormat;
+
+typedef enum OPCodeEnum {
+#define FMT(f)
+#define DEF(id, size, n_pop, n_push, f) OP_ ## id,
+#define def(id, size, n_pop, n_push, f)
+#include "quickjs-opcode.h"
+#undef def
+#undef DEF
+#undef FMT
+    OP_COUNT, /* excluding temporary opcodes */
+    /* temporary opcodes : overlap with the short opcodes */
+    OP_TEMP_START = OP_nop + 1,
+    OP___dummy = OP_TEMP_START - 1,
+#define FMT(f)
+#define DEF(id, size, n_pop, n_push, f)
+#define def(id, size, n_pop, n_push, f) OP_ ## id,
+#include "quickjs-opcode.h"
+#undef def
+#undef DEF
+#undef FMT
+    OP_TEMP_END,
+} OPCodeEnum;
+
+static int JS_InitAtoms(JSRuntime *rt);
+static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
+                               int atom_type);
+static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p);
+static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b);
+static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj,
+                               JSValueConst this_obj, JSValueConst new_target,
+                               int argc, JSValue *argv, int flags);
+static JSValue JS_CallConstructorInternal(JSContext *ctx,
+                                          JSValueConst func_obj,
+                                          JSValueConst new_target,
+                                          int argc, JSValue *argv, int flags);
+static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj,
+                           int argc, JSValueConst *argv);
+static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
+                             int argc, JSValueConst *argv);
+static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen,
+                                            JSValue val);
+static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj,
+                             JSValueConst val, int flags, int scope_idx);
+JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...);
+static __maybe_unused void JS_DumpAtoms(JSRuntime *rt);
+static __maybe_unused void JS_DumpString(JSRuntime *rt,
+                                                  const JSString *p);
+static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt);
+static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p);
+static __maybe_unused void JS_DumpValueShort(JSRuntime *rt,
+                                                      JSValueConst val);
+static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val);
+static __maybe_unused void JS_PrintValue(JSContext *ctx,
+                                                  const char *str,
+                                                  JSValueConst val);
+static __maybe_unused void JS_DumpShapes(JSRuntime *rt);
+static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv, int magic);
+static void js_array_finalizer(JSRuntime *rt, JSValue val);
+static void js_array_mark(JSRuntime *rt, JSValueConst val,
+                          JS_MarkFunc *mark_func);
+static void js_object_data_finalizer(JSRuntime *rt, JSValue val);
+static void js_object_data_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val);
+static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_bound_function_finalizer(JSRuntime *rt, JSValue val);
+static void js_bound_function_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val);
+static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_regexp_finalizer(JSRuntime *rt, JSValue val);
+static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val);
+static void js_typed_array_finalizer(JSRuntime *rt, JSValue val);
+static void js_typed_array_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_proxy_finalizer(JSRuntime *rt, JSValue val);
+static void js_proxy_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_map_finalizer(JSRuntime *rt, JSValue val);
+static void js_map_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val);
+static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val);
+static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val);
+static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_generator_finalizer(JSRuntime *rt, JSValue obj);
+static void js_generator_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_promise_finalizer(JSRuntime *rt, JSValue val);
+static void js_promise_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val);
+static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func);
+static JSValue js_promise_resolve_function_call(JSContext *ctx,
+                                                JSValueConst func_obj,
+                                                JSValueConst this_val,
+                                                int argc, JSValueConst *argv);
+static JSValue JS_ToStringFree(JSContext *ctx, JSValue val);
+static int JS_ToBoolFree(JSContext *ctx, JSValue val);
+static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val);
+static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val);
+static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val);
+static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
+                                 JSValueConst flags);
+static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor,
+                                              JSValue pattern, JSValue bc);
+static void gc_decref(JSRuntime *rt);
+static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
+                        const JSClassDef *class_def, JSAtom name);
+
+typedef enum JSStrictEqModeEnum {
+    JS_EQ_STRICT,
+    JS_EQ_SAME_VALUE,
+    JS_EQ_SAME_VALUE_ZERO,
+} JSStrictEqModeEnum;
+
+static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
+                          JSStrictEqModeEnum eq_mode);
+static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2);
+static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2);
+static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2);
+static JSValue JS_ToObject(JSContext *ctx, JSValueConst val);
+static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val);
+static JSProperty *add_property(JSContext *ctx,
+                                JSObject *p, JSAtom prop, int prop_flags);
+#ifdef CONFIG_BIGNUM
+static void js_float_env_finalizer(JSRuntime *rt, JSValue val);
+static JSValue JS_NewBigFloat(JSContext *ctx, bf_t *a);
+static JSValue JS_NewBigInt2(JSContext *ctx, bf_t *a, BOOL force_bigint);
+static JSValue JS_NewBigInt(JSContext *ctx, bf_t *a);
+static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val);
+static bf_t *JS_ToBigFloat(JSContext *ctx, BOOL *pis_float, bf_t *buf,
+                           JSValueConst val);
+#endif
+JSValue JS_ThrowOutOfMemory(JSContext *ctx);
+static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx);
+static JSValue js_proxy_call(JSContext *ctx, JSValueConst func_obj,
+                             JSValueConst this_obj,
+                             int argc, JSValueConst *argv);
+static JSValue js_proxy_call_constructor(JSContext *ctx, JSValueConst func_obj,
+                                         JSValueConst new_target,
+                                         int argc, JSValueConst *argv);
+static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj);
+static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj,
+                                   JSValueConst proto_val, BOOL throw_flag);
+static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj);
+static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj);
+static int js_proxy_isArray(JSContext *ctx, JSValueConst obj);
+static int JS_CreateProperty(JSContext *ctx, JSObject *p,
+                             JSAtom prop, JSValueConst val,
+                             JSValueConst getter, JSValueConst setter,
+                             int flags);
+static int js_string_memcmp(const JSString *p1, const JSString *p2, int len);
+static void reset_weak_ref(JSRuntime *rt, JSObject *p);
+static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p);
+static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p);
+static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx);
+static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx,
+                             BOOL is_arg);
+static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
+                                          JSValueConst this_obj,
+                                          int argc, JSValueConst *argv);
+static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val);
+static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val,
+                                           JS_MarkFunc *mark_func);
+static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
+                               const char *input, size_t input_len,
+                               const char *filename, int flags, int scope_idx);
+static void js_free_module_def(JSContext *ctx, JSModuleDef *m);
+static JSValue js_import_meta(JSContext *ctx);
+static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier);
+static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref);
+static JSValue js_new_promise_capability(JSContext *ctx,
+                                         JSValue *resolving_funcs,
+                                         JSValueConst ctor);
+static __exception int perform_promise_then(JSContext *ctx,
+                                            JSValueConst promise,
+                                            JSValueConst *resolve_reject,
+                                            JSValueConst *cap_resolving_funcs);
+static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv, int magic);
+static int js_string_compare(JSContext *ctx,
+                             const JSString *p1, const JSString *p2);
+static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val);
+static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj,
+                               JSValue prop, JSValue val, int flags);
+static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val);
+static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val);
+static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val);
+static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc,
+                                     JSObject *p, JSAtom prop);
+static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc);
+static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s,
+                            JS_MarkFunc *mark_func);
+static void JS_AddIntrinsicBasicObjects(JSContext *ctx);
+static void js_free_shape(JSRuntime *rt, JSShape *sh);
+static void js_free_shape_null(JSRuntime *rt, JSShape *sh);
+static int js_shape_prepare_update(JSContext *ctx, JSObject *p,
+                                   JSShapeProperty **pprs);
+static int init_shape_hash(JSRuntime *rt);
+static __exception int js_get_length32(JSContext *ctx, uint32_t *pres,
+                                       JSValueConst obj);
+static __exception int js_get_length64(JSContext *ctx, int64_t *pres,
+                                       JSValueConst obj);
+static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len);
+static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen,
+                               JSValueConst array_arg);
+static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj,
+                              JSValue **arrpp, uint32_t *countp);
+static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx,
+                                              JSValueConst sync_iter);
+static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val);
+static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val,
+                                    JS_MarkFunc *mark_func);
+static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj,
+                                       JSValueConst this_val,
+                                       int argc, JSValueConst *argv);
+static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val);
+
+static const JSClassExoticMethods js_arguments_exotic_methods;
+static const JSClassExoticMethods js_string_exotic_methods;
+static const JSClassExoticMethods js_proxy_exotic_methods;
+static const JSClassExoticMethods js_module_ns_exotic_methods;
+static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
+
+JS_BOOL JS_IsNumber(JSValueConst v)
+{
+#ifdef CONFIG_BIGNUM
+    int tag = JS_VALUE_GET_TAG(v);
+    return tag == JS_TAG_INT || tag == JS_TAG_BIG_INT ||
+        tag == JS_TAG_BIG_FLOAT || JS_TAG_IS_FLOAT64(tag);
+#else
+    int tag = JS_VALUE_GET_TAG(v);
+    return tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag);
+#endif
+}
+
+static void js_trigger_gc(JSRuntime *rt, size_t size)
+{
+    BOOL force_gc;
+#ifdef FORCE_GC_AT_MALLOC
+    force_gc = TRUE;
+#else
+    force_gc = ((rt->malloc_state.malloc_size + size) >
+                rt->malloc_gc_threshold);
+#endif
+    if (force_gc) {
+#ifdef DUMP_GC
+        printf("GC: size=%" PRIu64 "\n",
+               (uint64_t)rt->malloc_state.malloc_size);
+#endif
+        JS_RunGC(rt);
+        rt->malloc_gc_threshold = rt->malloc_state.malloc_size +
+            (rt->malloc_state.malloc_size >> 1);
+    }
+}
+
+static size_t js_malloc_usable_size_unknown(const void *ptr)
+{
+    return 0;
+}
+
+void *js_malloc_rt(JSRuntime *rt, size_t size)
+{
+    return rt->mf.js_malloc(&rt->malloc_state, size);
+}
+
+void js_free_rt(JSRuntime *rt, void *ptr)
+{
+    rt->mf.js_free(&rt->malloc_state, ptr);
+}
+
+void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size)
+{
+    return rt->mf.js_realloc(&rt->malloc_state, ptr, size);
+}
+
+size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr)
+{
+    return rt->mf.js_malloc_usable_size(ptr);
+}
+
+void *js_mallocz_rt(JSRuntime *rt, size_t size)
+{
+    void *ptr;
+    ptr = js_malloc_rt(rt, size);
+    if (!ptr)
+        return NULL;
+    return memset(ptr, 0, size);
+}
+
+#ifdef CONFIG_BIGNUM
+/* called by libbf */
+static void *js_bf_realloc(void *opaque, void *ptr, size_t size)
+{
+    JSRuntime *rt = opaque;
+    return js_realloc_rt(rt, ptr, size);
+}
+#endif /* CONFIG_BIGNUM */
+
+/* Throw out of memory in case of error */
+void *js_malloc(JSContext *ctx, size_t size)
+{
+    void *ptr;
+    ptr = js_malloc_rt(ctx->rt, size);
+    if (unlikely(!ptr)) {
+        JS_ThrowOutOfMemory(ctx);
+        return NULL;
+    }
+    return ptr;
+}
+
+/* Throw out of memory in case of error */
+void *js_mallocz(JSContext *ctx, size_t size)
+{
+    void *ptr;
+    ptr = js_mallocz_rt(ctx->rt, size);
+    if (unlikely(!ptr)) {
+        JS_ThrowOutOfMemory(ctx);
+        return NULL;
+    }
+    return ptr;
+}
+
+void js_free(JSContext *ctx, void *ptr)
+{
+    js_free_rt(ctx->rt, ptr);
+}
+
+/* Throw out of memory in case of error */
+void *js_realloc(JSContext *ctx, void *ptr, size_t size)
+{
+    void *ret;
+    ret = js_realloc_rt(ctx->rt, ptr, size);
+    if (unlikely(!ret && size != 0)) {
+        JS_ThrowOutOfMemory(ctx);
+        return NULL;
+    }
+    return ret;
+}
+
+/* store extra allocated size in *pslack if successful */
+void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack)
+{
+    void *ret;
+    ret = js_realloc_rt(ctx->rt, ptr, size);
+    if (unlikely(!ret && size != 0)) {
+        JS_ThrowOutOfMemory(ctx);
+        return NULL;
+    }
+    if (pslack) {
+        size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret);
+        *pslack = (new_size > size) ? new_size - size : 0;
+    }
+    return ret;
+}
+
+size_t js_malloc_usable_size(JSContext *ctx, const void *ptr)
+{
+    return js_malloc_usable_size_rt(ctx->rt, ptr);
+}
+
+/* Throw out of memory exception in case of error */
+char *js_strndup(JSContext *ctx, const char *s, size_t n)
+{
+    char *ptr;
+    ptr = js_malloc(ctx, n + 1);
+    if (ptr) {
+        memcpy(ptr, s, n);
+        ptr[n] = '\0';
+    }
+    return ptr;
+}
+
+char *js_strdup(JSContext *ctx, const char *str)
+{
+    return js_strndup(ctx, str, strlen(str));
+}
+
+static inline void js_dbuf_init(JSContext *ctx, DynBuf *s)
+{
+    dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt);
+}
+
+static inline int is_digit(int c) {
+    return c >= '0' && c <= '9';
+}
+
+typedef struct JSClassShortDef {
+    JSAtom class_name;
+    JSClassFinalizer *finalizer;
+    JSClassGCMark *gc_mark;
+} JSClassShortDef;
+
+static JSClassShortDef const js_std_class_def[] = {
+    { JS_ATOM_Object, NULL, NULL },                             /* JS_CLASS_OBJECT */
+    { JS_ATOM_Array, js_array_finalizer, js_array_mark },       /* JS_CLASS_ARRAY */
+    { JS_ATOM_Error, NULL, NULL },                              /* JS_CLASS_ERROR */
+    { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */
+    { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */
+    { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */
+    { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */
+    { JS_ATOM_Arguments, js_array_finalizer, js_array_mark },   /* JS_CLASS_ARGUMENTS */
+    { JS_ATOM_Arguments, NULL, NULL },                          /* JS_CLASS_MAPPED_ARGUMENTS */
+    { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */
+    { JS_ATOM_Object, NULL, NULL },                             /* JS_CLASS_MODULE_NS */
+    { JS_ATOM_Function, NULL, NULL },                           /* JS_CLASS_C_FUNCTION */
+    { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */
+    { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */
+    { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */
+    { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark },  /* JS_CLASS_GENERATOR_FUNCTION */
+    { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark },      /* JS_CLASS_FOR_IN_ITERATOR */
+    { JS_ATOM_RegExp, js_regexp_finalizer, NULL },                              /* JS_CLASS_REGEXP */
+    { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL },                   /* JS_CLASS_ARRAY_BUFFER */
+    { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL },             /* JS_CLASS_SHARED_ARRAY_BUFFER */
+    { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */
+    { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark },       /* JS_CLASS_INT8_ARRAY */
+    { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark },      /* JS_CLASS_UINT8_ARRAY */
+    { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark },      /* JS_CLASS_INT16_ARRAY */
+    { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark },     /* JS_CLASS_UINT16_ARRAY */
+    { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark },      /* JS_CLASS_INT32_ARRAY */
+    { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark },     /* JS_CLASS_UINT32_ARRAY */
+#ifdef CONFIG_BIGNUM
+    { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark },   /* JS_CLASS_BIG_INT64_ARRAY */
+    { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark },  /* JS_CLASS_BIG_UINT64_ARRAY */
+#endif
+    { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark },    /* JS_CLASS_FLOAT32_ARRAY */
+    { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark },    /* JS_CLASS_FLOAT64_ARRAY */
+    { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark },        /* JS_CLASS_DATAVIEW */
+#ifdef CONFIG_BIGNUM
+    { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark },      /* JS_CLASS_BIG_INT */
+    { JS_ATOM_BigFloat, js_object_data_finalizer, js_object_data_mark },    /* JS_CLASS_BIG_FLOAT */
+    { JS_ATOM_BigFloatEnv, js_float_env_finalizer, NULL },      /* JS_CLASS_FLOAT_ENV */
+#endif
+    { JS_ATOM_Map, js_map_finalizer, js_map_mark },             /* JS_CLASS_MAP */
+    { JS_ATOM_Set, js_map_finalizer, js_map_mark },             /* JS_CLASS_SET */
+    { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark },         /* JS_CLASS_WEAKMAP */
+    { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark },         /* JS_CLASS_WEAKSET */
+    { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */
+    { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */
+    { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */
+    { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */
+    { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */
+    { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */
+};
+
+static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab,
+                            int start, int count)
+{
+    JSClassDef cm_s, *cm = &cm_s;
+    int i, class_id;
+
+    for(i = 0; i < count; i++) {
+        class_id = i + start;
+        memset(cm, 0, sizeof(*cm));
+        cm->finalizer = tab[i].finalizer;
+        cm->gc_mark = tab[i].gc_mark;
+        if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
+{
+    JSRuntime *rt;
+    JSMallocState ms;
+
+    memset(&ms, 0, sizeof(ms));
+    ms.opaque = opaque;
+    ms.malloc_limit = -1;
+
+    rt = mf->js_malloc(&ms, sizeof(JSRuntime));
+    if (!rt)
+        return NULL;
+    memset(rt, 0, sizeof(*rt));
+    rt->mf = *mf;
+    if (!rt->mf.js_malloc_usable_size) {
+        /* use dummy function if none provided */
+        rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown;
+    }
+    rt->malloc_state = ms;
+    rt->malloc_gc_threshold = 256 * 1024;
+
+#ifdef CONFIG_BIGNUM
+    bf_context_init(&rt->bf_ctx, js_bf_realloc, rt);
+#endif
+
+    init_list_head(&rt->context_list);
+    init_list_head(&rt->obj_list);
+#ifdef DUMP_LEAKS
+    init_list_head(&rt->string_list);
+#endif
+    init_list_head(&rt->job_list);
+
+    if (JS_InitAtoms(rt))
+        goto fail;
+
+    /* create the object, array and function classes */
+    if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT,
+                         countof(js_std_class_def)) < 0)
+        goto fail;
+    rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods;
+    rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods;
+    rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods;
+
+    rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call;
+    rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call;
+    if (init_shape_hash(rt))
+        goto fail;
+    return rt;
+ fail:
+    JS_FreeRuntime(rt);
+    return NULL;
+}
+
+/* default memory allocation functions with memory limitation */
+static inline size_t js_def_malloc_usable_size(void *ptr)
+{
+#if defined(__APPLE__)
+    return malloc_size(ptr);
+#elif defined(_WIN32)
+    return _msize(ptr);
+#elif defined(EMSCRIPTEN)
+    return 0;
+#elif defined(__linux__)
+    return malloc_usable_size(ptr);
+#else
+    /* change this to `return 0;` if compilation fails */
+    return malloc_usable_size(ptr);
+#endif
+}
+
+static void *js_def_malloc(JSMallocState *s, size_t size)
+{
+    void *ptr;
+
+    /* Do not allocate zero bytes: behavior is platform dependent */
+    assert(size != 0);
+
+    if (unlikely(s->malloc_size + size > s->malloc_limit))
+        return NULL;
+
+    ptr = malloc(size);
+    if (!ptr)
+        return NULL;
+
+    s->malloc_count++;
+    s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
+    return ptr;
+}
+
+static void js_def_free(JSMallocState *s, void *ptr)
+{
+    if (!ptr)
+        return;
+
+    s->malloc_count--;
+    s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
+    free(ptr);
+}
+
+static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size)
+{
+    size_t old_size;
+
+    if (!ptr) {
+        if (size == 0)
+            return NULL;
+        return js_def_malloc(s, size);
+    }
+    old_size = js_def_malloc_usable_size(ptr);
+    if (size == 0) {
+        s->malloc_count--;
+        s->malloc_size -= old_size + MALLOC_OVERHEAD;
+        free(ptr);
+        return NULL;
+    }
+    if (s->malloc_size + size - old_size > s->malloc_limit)
+        return NULL;
+
+    ptr = realloc(ptr, size);
+    if (!ptr)
+        return NULL;
+
+    s->malloc_size += js_def_malloc_usable_size(ptr) - old_size;
+    return ptr;
+}
+
+static const JSMallocFunctions def_malloc_funcs = {
+    js_def_malloc,
+    js_def_free,
+    js_def_realloc,
+#if defined(__APPLE__)
+    malloc_size,
+#elif defined(_WIN32)
+    (size_t (*)(const void *))_msize,
+#elif defined(EMSCRIPTEN)
+    NULL,
+#elif defined(__linux__)
+    (size_t (*)(const void *))malloc_usable_size,
+#else
+    /* change this to `NULL,` if compilation fails */
+    malloc_usable_size,
+#endif
+};
+
+JSRuntime *JS_NewRuntime(void)
+{
+    return JS_NewRuntime2(&def_malloc_funcs, NULL);
+}
+
+void JS_SetMemoryLimit(JSRuntime *rt, size_t limit)
+{
+    rt->malloc_state.malloc_limit = limit;
+}
+
+/* use -1 to disable automatic GC */
+void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold)
+{
+    rt->malloc_gc_threshold = gc_threshold;
+}
+
+#define malloc(s) malloc_is_forbidden(s)
+#define free(p) free_is_forbidden(p)
+#define realloc(p,s) realloc_is_forbidden(p,s)
+
+void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque)
+{
+    rt->interrupt_handler = cb;
+    rt->interrupt_opaque = opaque;
+}
+
+void JS_SetCanBlock(JSRuntime *rt, BOOL can_block)
+{
+    rt->can_block = can_block;
+}
+
+/* return 0 if OK, < 0 if exception */
+int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func,
+                  int argc, JSValueConst *argv)
+{
+    JSRuntime *rt = ctx->rt;
+    JSJobEntry *e;
+    int i;
+
+    e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue));
+    if (!e)
+        return -1;
+    e->ctx = ctx;
+    e->job_func = job_func;
+    e->argc = argc;
+    for(i = 0; i < argc; i++) {
+        e->argv[i] = JS_DupValue(ctx, argv[i]);
+    }
+    list_add_tail(&e->link, &rt->job_list);
+    return 0;
+}
+
+BOOL JS_IsJobPending(JSRuntime *rt)
+{
+    return !list_empty(&rt->job_list);
+}
+
+/* return < 0 if exception, 0 if no job pending, 1 if a job was
+   executed successfully. the context of the job is stored in '*pctx' */
+int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx)
+{
+    JSContext *ctx;
+    JSJobEntry *e;
+    JSValue res;
+    int i, ret;
+
+    if (list_empty(&rt->job_list)) {
+        *pctx = NULL;
+        return 0;
+    }
+
+    /* get the first pending job and execute it */
+    e = list_entry(rt->job_list.next, JSJobEntry, link);
+    list_del(&e->link);
+    ctx = e->ctx;
+    res = e->job_func(e->ctx, e->argc, (JSValueConst *)e->argv);
+    for(i = 0; i < e->argc; i++)
+        JS_FreeValue(ctx, e->argv[i]);
+    if (JS_IsException(res))
+        ret = -1;
+    else
+        ret = 1;
+    JS_FreeValue(ctx, res);
+    js_free(ctx, e);
+    *pctx = ctx;
+    return ret;
+}
+
+static inline uint32_t atom_get_free(const JSAtomStruct *p)
+{
+    return (uintptr_t)p >> 1;
+}
+
+static inline BOOL atom_is_free(const JSAtomStruct *p)
+{
+    return (uintptr_t)p & 1;
+}
+
+static inline JSAtomStruct *atom_set_free(uint32_t v)
+{
+    return (JSAtomStruct *)(((uintptr_t)v << 1) | 1);
+}
+
+/* Note: the string contents are uninitialized */
+static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char)
+{
+    JSString *str;
+    str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char);
+    if (unlikely(!str))
+        return NULL;
+    str->header.ref_count = 1;
+    str->is_wide_char = is_wide_char;
+    str->len = max_len;
+    str->atom_type = 0;
+    str->hash = 0;          /* optional but costless */
+    str->hash_next = 0;     /* optional */
+#ifdef DUMP_LEAKS
+    list_add_tail(&str->link, &rt->string_list);
+#endif
+    return str;
+}
+
+static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char)
+{
+    JSString *p;
+    p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char);
+    if (unlikely(!p)) {
+        JS_ThrowOutOfMemory(ctx);
+        return NULL;
+    }
+    return p;
+}
+
+/* same as JS_FreeValueRT() but faster */
+static inline void js_free_string(JSRuntime *rt, JSString *str)
+{
+    if (--str->header.ref_count <= 0) {
+        if (str->atom_type) {
+            JS_FreeAtomStruct(rt, str);
+        } else {
+#ifdef DUMP_LEAKS
+            list_del(&str->link);
+#endif
+            js_free_rt(rt, str);
+        }
+    }
+}
+
+void JS_SetRuntimeInfo(JSRuntime *rt, const char *s)
+{
+    if (rt)
+        rt->rt_info = s;
+}
+
+void JS_FreeRuntime(JSRuntime *rt)
+{
+    struct list_head *el, *el1;
+    int i;
+
+    list_for_each_safe(el, el1, &rt->context_list) {
+        JSContext *ctx = list_entry(el, JSContext, link);
+        JS_FreeContext(ctx);
+    }
+
+    list_for_each_safe(el, el1, &rt->job_list) {
+        JSJobEntry *e = list_entry(el, JSJobEntry, link);
+        for(i = 0; i < e->argc; i++)
+            JS_FreeValueRT(rt, e->argv[i]);
+        js_free_rt(rt, e);
+    }
+    init_list_head(&rt->job_list);
+
+    JS_RunGC(rt);
+
+#ifdef DUMP_LEAKS
+    /* leaking objects */
+    {
+        BOOL header_done;
+        JSObject *p;
+        int count;
+
+        /* remove the internal refcounts to display only the object
+           referenced externally */
+        list_for_each(el, &rt->obj_list) {
+            p = list_entry(el, JSObject, link);
+            p->gc_header.mark = 0;
+        }
+        gc_decref(rt);
+
+        header_done = FALSE;
+        list_for_each(el, &rt->obj_list) {
+            p = list_entry(el, JSObject, link);
+            if (p->header.ref_count != 0) {
+                if (!header_done) {
+                    printf("Object leaks:\n");
+                    JS_DumpObjectHeader(rt);
+                    header_done = TRUE;
+                }
+                JS_DumpObject(rt, p);
+            }
+        }
+
+        count = 0;
+        list_for_each(el, &rt->obj_list) {
+            p = list_entry(el, JSObject, link);
+            if (p->header.ref_count == 0) {
+                count++;
+            }
+        }
+        if (count != 0)
+            printf("Secondary object leaks: %d\n", count);
+    }
+#endif
+    assert(list_empty(&rt->obj_list));
+
+    /* free the classes */
+    for(i = 0; i < rt->class_count; i++) {
+        JSClass *cl = &rt->class_array[i];
+        if (cl->class_id != 0) {
+            JS_FreeAtomRT(rt, cl->class_name);
+        }
+    }
+    js_free_rt(rt, rt->class_array);
+
+#ifdef CONFIG_BIGNUM
+    bf_context_end(&rt->bf_ctx);
+#endif
+
+#ifdef DUMP_LEAKS
+    /* only the atoms defined in JS_InitAtoms() should be left */
+    {
+        BOOL header_done = FALSE;
+
+        for(i = 0; i < rt->atom_size; i++) {
+            JSAtomStruct *p = rt->atom_array[i];
+            if (!atom_is_free(p) /* && p->str*/) {
+                if (i >= JS_ATOM_END || p->header.ref_count != 1) {
+                    if (!header_done) {
+                        header_done = TRUE;
+                        if (rt->rt_info) {
+                            printf("%s:1: atom leakage:", rt->rt_info);
+                        } else {
+                            printf("Atom leaks:\n"
+                                   "    %6s %6s %s\n",
+                                   "ID", "REFCNT", "NAME");
+                        }
+                    }
+                    if (rt->rt_info) {
+                        printf(" ");
+                    } else {
+                        printf("    %6u %6u ", i, p->header.ref_count);
+                    }
+                    switch (p->atom_type) {
+                    case JS_ATOM_TYPE_STRING:
+                        JS_DumpString(rt, p);
+                        break;
+                    case JS_ATOM_TYPE_GLOBAL_SYMBOL:
+                        printf("Symbol.for(");
+                        JS_DumpString(rt, p);
+                        printf(")");
+                        break;
+                    case JS_ATOM_TYPE_SYMBOL:
+                        if (p->hash == JS_ATOM_HASH_SYMBOL) {
+                            printf("Symbol(");
+                            JS_DumpString(rt, p);
+                            printf(")");
+                        } else {
+                            printf("Private(");
+                            JS_DumpString(rt, p);
+                            printf(")");
+                        }
+                        break;
+                    }
+                    if (rt->rt_info) {
+                        printf(":%u", p->header.ref_count);
+                    } else {
+                        printf("\n");
+                    }
+                }
+            }
+        }
+        if (rt->rt_info && header_done)
+            printf("\n");
+    }
+#endif
+
+    /* free the atoms */
+    for(i = 0; i < rt->atom_size; i++) {
+        JSAtomStruct *p = rt->atom_array[i];
+        if (!atom_is_free(p)) {
+#ifdef DUMP_LEAKS
+            list_del(&p->link);
+#endif
+            js_free_rt(rt, p);
+        }
+    }
+    js_free_rt(rt, rt->atom_array);
+    js_free_rt(rt, rt->atom_hash);
+    js_free_rt(rt, rt->shape_hash);
+#ifdef DUMP_LEAKS
+    if (!list_empty(&rt->string_list)) {
+        if (rt->rt_info) {
+            printf("%s:1: string leakage:", rt->rt_info);
+        } else {
+            printf("String leaks:\n"
+                   "    %6s %s\n",
+                   "REFCNT", "VALUE");
+        }
+        list_for_each_safe(el, el1, &rt->string_list) {
+            JSString *str = list_entry(el, JSString, link);
+            if (rt->rt_info) {
+                printf(" ");
+            } else {
+                printf("    %6u ", str->header.ref_count);
+            }
+            JS_DumpString(rt, str);
+            if (rt->rt_info) {
+                printf(":%u", str->header.ref_count);
+            } else {
+                printf("\n");
+            }
+            list_del(&str->link);
+            js_free_rt(rt, str);
+        }
+        if (rt->rt_info)
+            printf("\n");
+    }
+    {
+        JSMallocState *s = &rt->malloc_state;
+        if (s->malloc_count > 1) {
+            if (rt->rt_info)
+                printf("%s:1: ", rt->rt_info);
+            printf("Memory leak: %"PRIu64" bytes lost in %"PRIu64" block%s\n",
+                   (uint64_t)(s->malloc_size - sizeof(JSRuntime)),
+                   (uint64_t)(s->malloc_count - 1), &"s"[s->malloc_count == 2]);
+        }
+    }
+#endif
+
+    {
+        JSMallocState ms = rt->malloc_state;
+        rt->mf.js_free(&ms, rt);
+    }
+}
+
+#if defined(EMSCRIPTEN)
+/* currently no stack limitation */
+static inline uint8_t *js_get_stack_pointer(void)
+{
+    return NULL;
+}
+
+static inline BOOL js_check_stack_overflow(JSContext *ctx, size_t alloca_size)
+{
+    return FALSE;
+}
+#else
+/* Note: OS and CPU dependent */
+static inline uint8_t *js_get_stack_pointer(void)
+{
+    return __builtin_frame_address(0);
+}
+
+static inline BOOL js_check_stack_overflow(JSContext *ctx, size_t alloca_size)
+{
+    size_t size;
+    size = ctx->stack_top - js_get_stack_pointer();
+    return unlikely((size + alloca_size) > ctx->stack_size);
+}
+#endif
+
+JSContext *JS_NewContextRaw(JSRuntime *rt)
+{
+    JSContext *ctx;
+    int i;
+
+    ctx = js_mallocz_rt(rt, sizeof(JSContext));
+    if (!ctx)
+        return NULL;
+    ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) *
+                                    rt->class_count);
+    if (!ctx->class_proto) {
+        js_free_rt(rt, ctx);
+        return NULL;
+    }
+    ctx->rt = rt;
+    list_add_tail(&ctx->link, &rt->context_list);
+    ctx->stack_top = js_get_stack_pointer();
+    ctx->stack_size = JS_DEFAULT_STACK_SIZE;
+    ctx->current_exception = JS_NULL;
+#ifdef CONFIG_BIGNUM
+    ctx->bf_ctx = &rt->bf_ctx;
+    ctx->fp_env.prec = 53;
+    ctx->fp_env.flags = bf_set_exp_bits(11) | BF_RNDN | BF_FLAG_SUBNORMAL;
+#endif
+    for(i = 0; i < rt->class_count; i++)
+        ctx->class_proto[i] = JS_NULL;
+    ctx->regexp_ctor = JS_NULL;
+    ctx->promise_ctor = JS_NULL;
+    init_list_head(&ctx->loaded_modules);
+
+    JS_AddIntrinsicBasicObjects(ctx);
+    return ctx;
+}
+
+JSContext *JS_NewContext(JSRuntime *rt)
+{
+    JSContext *ctx;
+
+    ctx = JS_NewContextRaw(rt);
+    if (!ctx)
+        return NULL;
+
+    JS_AddIntrinsicBaseObjects(ctx);
+    JS_AddIntrinsicDate(ctx);
+    JS_AddIntrinsicEval(ctx);
+    JS_AddIntrinsicStringNormalize(ctx);
+    JS_AddIntrinsicRegExp(ctx);
+    JS_AddIntrinsicJSON(ctx);
+    JS_AddIntrinsicProxy(ctx);
+    JS_AddIntrinsicMapSet(ctx);
+    JS_AddIntrinsicTypedArrays(ctx);
+    JS_AddIntrinsicPromise(ctx);
+
+    return ctx;
+}
+
+void *JS_GetContextOpaque(JSContext *ctx)
+{
+    return ctx->user_opaque;
+}
+
+void JS_SetContextOpaque(JSContext *ctx, void *opaque)
+{
+    ctx->user_opaque = opaque;
+}
+
+/* set the new value and free the old value after (freeing the value
+   can reallocate the object data) */
+static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val)
+{
+    JSValue old_val;
+    old_val = *pval;
+    *pval = new_val;
+    JS_FreeValue(ctx, old_val);
+}
+
+void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj)
+{
+    JSRuntime *rt = ctx->rt;
+    assert(class_id < rt->class_count);
+    set_value(ctx, &ctx->class_proto[class_id], obj);
+}
+
+JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id)
+{
+    JSRuntime *rt = ctx->rt;
+    assert(class_id < rt->class_count);
+    return JS_DupValue(ctx, ctx->class_proto[class_id]);
+}
+
+typedef enum JSFreeModuleEnum {
+    JS_FREE_MODULE_ALL,
+    JS_FREE_MODULE_NOT_RESOLVED,
+    JS_FREE_MODULE_NOT_EVALUATED,
+} JSFreeModuleEnum;
+
+/* XXX: would be more efficient with separate module lists */
+static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag)
+{
+    struct list_head *el, *el1;
+    list_for_each_safe(el, el1, &ctx->loaded_modules) {
+        JSModuleDef *m = list_entry(el, JSModuleDef, link);
+        if (flag == JS_FREE_MODULE_ALL ||
+            (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved) ||
+            (flag == JS_FREE_MODULE_NOT_EVALUATED && !m->evaluated)) {
+            js_free_module_def(ctx, m);
+        }
+    }
+}
+
+void JS_FreeContext(JSContext *ctx)
+{
+    JSRuntime *rt = ctx->rt;
+    int i;
+
+#ifdef DUMP_ATOMS
+    JS_DumpAtoms(ctx->rt);
+#endif
+#ifdef DUMP_SHAPES
+    JS_DumpShapes(ctx->rt);
+#endif
+#ifdef DUMP_OBJECTS
+    {
+        struct list_head *el;
+        JSObject *p;
+        printf("JSObjects: {\n");
+        JS_DumpObjectHeader(ctx->rt);
+        list_for_each(el, &rt->obj_list) {
+            p = list_entry(el, JSObject, link);
+            JS_DumpObject(rt, p);
+        }
+        printf("}\n");
+    }
+#endif
+#ifdef DUMP_MEM
+    {
+        JSMemoryUsage stats;
+        JS_ComputeMemoryUsage(rt, &stats);
+        JS_DumpMemoryUsage(stdout, &stats, rt);
+    }
+#endif
+
+    js_free_modules(ctx, JS_FREE_MODULE_ALL);
+
+    JS_FreeValue(ctx, ctx->current_exception);
+
+    JS_FreeValue(ctx, ctx->global_obj);
+    JS_FreeValue(ctx, ctx->global_var_obj);
+
+    JS_FreeValue(ctx, ctx->throw_type_error);
+    JS_FreeValue(ctx, ctx->eval_obj);
+
+    JS_FreeValue(ctx, ctx->array_proto_values);
+    for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
+        JS_FreeValue(ctx, ctx->native_error_proto[i]);
+    }
+    for(i = 0; i < rt->class_count; i++) {
+        JS_FreeValue(ctx, ctx->class_proto[i]);
+    }
+    js_free_rt(rt, ctx->class_proto);
+    JS_FreeValue(ctx, ctx->iterator_proto);
+    JS_FreeValue(ctx, ctx->async_iterator_proto);
+    JS_FreeValue(ctx, ctx->promise_ctor);
+    JS_FreeValue(ctx, ctx->regexp_ctor);
+    JS_FreeValue(ctx, ctx->function_ctor);
+    JS_FreeValue(ctx, ctx->function_proto);
+
+    js_free_shape_null(ctx->rt, ctx->array_shape);
+
+    list_del(&ctx->link);
+    js_free_rt(ctx->rt, ctx);
+}
+
+JSRuntime *JS_GetRuntime(JSContext *ctx)
+{
+    return ctx->rt;
+}
+
+void JS_SetMaxStackSize(JSContext *ctx, size_t stack_size)
+{
+    ctx->stack_size = stack_size;
+}
+
+static inline BOOL is_strict_mode(JSContext *ctx)
+{
+    JSStackFrame *sf = ctx->current_stack_frame;
+    return (sf && (sf->js_mode & JS_MODE_STRICT));
+}
+
+#ifdef CONFIG_BIGNUM
+static inline BOOL is_bignum_mode(JSContext *ctx)
+{
+    JSStackFrame *sf = ctx->current_stack_frame;
+    return (sf && (sf->js_mode & JS_MODE_BIGINT));
+}
+#endif
+
+JSValue JS_NewInt64(JSContext *ctx, int64_t v)
+{
+    if (v == (int32_t)v) {
+        return JS_NewInt32(ctx, v);
+    } else {
+#ifdef CONFIG_BIGNUM
+        if (is_bignum_mode(ctx)) {
+            bf_t a_s, *a = &a_s;
+            bf_init(ctx->bf_ctx, a);
+            bf_set_si(a, v);
+            return JS_NewBigInt(ctx, a);
+        } else
+#endif
+        {
+            return __JS_NewFloat64(ctx, (double)v);
+        }
+    }
+}
+
+static force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val)
+{
+#ifdef CONFIG_BIGNUM
+    return JS_NewInt64(ctx, val);
+#else
+    JSValue v;
+    if (val <= 0x7fffffff) {
+        v = JS_MKVAL(JS_TAG_INT, val);
+    } else {
+        v = __JS_NewFloat64(ctx, val);
+    }
+    return v;
+#endif
+}
+
+/* JSAtom support */
+
+#define JS_ATOM_TAG_INT (1U << 31)
+#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1)
+#define JS_ATOM_MAX     ((1U << 30) - 1)
+
+/* return the max count from the hash size */
+#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2)
+
+static inline BOOL __JS_AtomIsConst(JSAtom v)
+{
+#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1
+        return (int32_t)v <= 0;
+#else
+        return (int32_t)v < JS_ATOM_END;
+#endif
+}
+
+static inline BOOL __JS_AtomIsTaggedInt(JSAtom v)
+{
+    return (v & JS_ATOM_TAG_INT) != 0;
+}
+
+static inline JSAtom __JS_AtomFromUInt32(uint32_t v)
+{
+    return v | JS_ATOM_TAG_INT;
+}
+
+static inline uint32_t __JS_AtomToUInt32(JSAtom atom)
+{
+    return atom & ~JS_ATOM_TAG_INT;
+}
+
+static inline int is_num(int c)
+{
+    return c >= '0' && c <= '9';
+}
+
+/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */
+static inline BOOL is_num_string(uint32_t *pval, const JSString *p)
+{
+    uint32_t n;
+    uint64_t n64;
+    int c, i, len;
+
+    len = p->len;
+    if (len == 0 || len > 10)
+        return FALSE;
+    if (p->is_wide_char)
+        c = p->u.str16[0];
+    else
+        c = p->u.str8[0];
+    if (is_num(c)) {
+        if (c == '0') {
+            if (len != 1)
+                return FALSE;
+            n = 0;
+        } else {
+            n = c - '0';
+            for(i = 1; i < len; i++) {
+                if (p->is_wide_char)
+                    c = p->u.str16[i];
+                else
+                    c = p->u.str8[i];
+                if (!is_num(c))
+                    return FALSE;
+                n64 = (uint64_t)n * 10 + (c - '0');
+                if ((n64 >> 32) != 0)
+                    return FALSE;
+                n = n64;
+            }
+        }
+        *pval = n;
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+/* XXX: could use faster version ? */
+static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h)
+{
+    size_t i;
+
+    for(i = 0; i < len; i++)
+        h = h * 263 + str[i];
+    return h;
+}
+
+static inline uint32_t hash_string16(const uint16_t *str,
+                                     size_t len, uint32_t h)
+{
+    size_t i;
+
+    for(i = 0; i < len; i++)
+        h = h * 263 + str[i];
+    return h;
+}
+
+static uint32_t hash_string(const JSString *str, uint32_t h)
+{
+    if (str->is_wide_char)
+        h = hash_string16(str->u.str16, str->len, h);
+    else
+        h = hash_string8(str->u.str8, str->len, h);
+    return h;
+}
+
+static __maybe_unused void JS_DumpString(JSRuntime *rt,
+                                                  const JSString *p)
+{
+    int i, c, sep;
+
+    if (p == NULL) {
+        printf("<null>");
+        return;
+    }
+    printf("%d", p->header.ref_count);
+    sep = (p->header.ref_count == 1) ? '\"' : '\'';
+    putchar(sep);
+    for(i = 0; i < p->len; i++) {
+        if (p->is_wide_char)
+            c = p->u.str16[i];
+        else
+            c = p->u.str8[i];
+        if (c == sep || c == '\\') {
+            putchar('\\');
+            putchar(c);
+        } else if (c >= ' ' && c <= 126) {
+            putchar(c);
+        } else if (c == '\n') {
+            putchar('\\');
+            putchar('n');
+        } else {
+            printf("\\u%04x", c);
+        }
+    }
+    putchar(sep);
+}
+
+static __maybe_unused void JS_DumpAtoms(JSRuntime *rt)
+{
+    JSAtomStruct *p;
+    int h, i;
+    /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */
+    printf("JSAtom count=%d size=%d hash_size=%d:\n",
+           rt->atom_count, rt->atom_size, rt->atom_hash_size);
+    printf("JSAtom hash table: {\n");
+    for(i = 0; i < rt->atom_hash_size; i++) {
+        h = rt->atom_hash[i];
+        if (h) {
+            printf("  %d:", i);
+            while (h) {
+                p = rt->atom_array[h];
+                printf(" ");
+                JS_DumpString(rt, p);
+                h = p->hash_next;
+            }
+            printf("\n");
+        }
+    }
+    printf("}\n");
+    printf("JSAtom table: {\n");
+    for(i = 0; i < rt->atom_size; i++) {
+        p = rt->atom_array[i];
+        if (!atom_is_free(p)) {
+            printf("  %d: { %d %08x ", i, p->atom_type, p->hash);
+            if (!(p->len == 0 && p->is_wide_char != 0))
+                JS_DumpString(rt, p);
+            printf(" %d }\n", p->hash_next);
+        }
+    }
+    printf("}\n");
+}
+
+static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size)
+{
+    JSAtomStruct *p;
+    uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash;
+
+    assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */
+    new_hash_mask = new_hash_size - 1;
+    new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size);
+    if (!new_hash)
+        return -1;
+    for(i = 0; i < rt->atom_hash_size; i++) {
+        h = rt->atom_hash[i];
+        while (h != 0) {
+            p = rt->atom_array[h];
+            hash_next1 = p->hash_next;
+            /* add in new hash table */
+            j = p->hash & new_hash_mask;
+            p->hash_next = new_hash[j];
+            new_hash[j] = h;
+            h = hash_next1;
+        }
+    }
+    js_free_rt(rt, rt->atom_hash);
+    rt->atom_hash = new_hash;
+    rt->atom_hash_size = new_hash_size;
+    rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size);
+    //    JS_DumpAtoms(rt);
+    return 0;
+}
+
+static int JS_InitAtoms(JSRuntime *rt)
+{
+    int i, len, atom_type;
+    const char *p;
+
+    rt->atom_hash_size = 0;
+    rt->atom_hash = NULL;
+    rt->atom_count = 0;
+    rt->atom_size = 0;
+    rt->atom_free_index = 0;
+    if (JS_ResizeAtomHash(rt, 256))     /* there are at least 195 predefined atoms */
+        return -1;
+
+    p = js_atom_init;
+    for(i = 1; i < JS_ATOM_END; i++) {
+        if (i == JS_ATOM_Private_brand)
+            atom_type = JS_ATOM_TYPE_PRIVATE;
+        else if (i >= JS_ATOM_Symbol_toPrimitive)
+            atom_type = JS_ATOM_TYPE_SYMBOL;
+        else
+            atom_type = JS_ATOM_TYPE_STRING;
+        len = strlen(p);
+        if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL)
+            return -1;
+        p = p + len + 1;
+    }
+    return 0;
+}
+
+static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v)
+{
+    JSAtomStruct *p;
+
+    if (!__JS_AtomIsConst(v)) {
+        p = rt->atom_array[v];
+        p->header.ref_count++;
+    }
+    return v;
+}
+
+JSAtom JS_DupAtom(JSContext *ctx, JSAtom v)
+{
+    JSRuntime *rt;
+    JSAtomStruct *p;
+
+    if (!__JS_AtomIsConst(v)) {
+        rt = ctx->rt;
+        p = rt->atom_array[v];
+        p->header.ref_count++;
+    }
+    return v;
+}
+
+static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v)
+{
+    JSRuntime *rt;
+    JSAtomStruct *p;
+
+    rt = ctx->rt;
+    if (__JS_AtomIsTaggedInt(v))
+        return JS_ATOM_KIND_STRING;
+    p = rt->atom_array[v];
+    switch(p->atom_type) {
+    case JS_ATOM_TYPE_STRING:
+        return JS_ATOM_KIND_STRING;
+    case JS_ATOM_TYPE_GLOBAL_SYMBOL:
+        return JS_ATOM_KIND_SYMBOL;
+    case JS_ATOM_TYPE_SYMBOL:
+        switch(p->hash) {
+        case JS_ATOM_HASH_SYMBOL:
+            return JS_ATOM_KIND_SYMBOL;
+        case JS_ATOM_HASH_PRIVATE:
+            return JS_ATOM_KIND_PRIVATE;
+        default:
+            abort();
+        }
+    default:
+        abort();
+    }
+}
+
+static BOOL JS_AtomIsString(JSContext *ctx, JSAtom v)
+{
+    return JS_AtomGetKind(ctx, v) == JS_ATOM_KIND_STRING;
+}
+
+static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p)
+{
+    uint32_t i = p->hash_next;  /* atom_index */
+    if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
+        JSAtomStruct *p1;
+
+        i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)];
+        p1 = rt->atom_array[i];
+        while (p1 != p) {
+            assert(i != 0);
+            i = p1->hash_next;
+            p1 = rt->atom_array[i];
+        }
+    }
+    return i;
+}
+
+/* string case (internal). Return JS_ATOM_NULL if error. 'str' is
+   freed. */
+static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
+{
+    uint32_t h, h1, i;
+    JSAtomStruct *p;
+    int len;
+
+#if 0
+    printf("__JS_NewAtom: ");  JS_DumpString(rt, str); printf("\n");
+#endif
+    if (atom_type < JS_ATOM_TYPE_SYMBOL) {
+        /* str is not NULL */
+        if (str->atom_type == atom_type) {
+            /* str is the atom, return its index */
+            i = js_get_atom_index(rt, str);
+            /* reduce string refcount and increase atom's unless constant */
+            if (__JS_AtomIsConst(i))
+                str->header.ref_count--;
+            return i;
+        }
+        /* try and locate an already registered atom */
+        len = str->len;
+        h = hash_string(str, atom_type);
+        h &= JS_ATOM_HASH_MASK;
+        h1 = h & (rt->atom_hash_size - 1);
+        i = rt->atom_hash[h1];
+        while (i != 0) {
+            p = rt->atom_array[i];
+            if (p->hash == h &&
+                p->atom_type == atom_type &&
+                p->len == len &&
+                js_string_memcmp(p, str, len) == 0) {
+                if (!__JS_AtomIsConst(i))
+                    p->header.ref_count++;
+                goto done;
+            }
+            i = p->hash_next;
+        }
+    } else {
+        h1 = 0; /* avoid warning */
+        if (atom_type == JS_ATOM_TYPE_SYMBOL) {
+            h = JS_ATOM_HASH_SYMBOL;
+        } else {
+            h = JS_ATOM_HASH_PRIVATE;
+            atom_type = JS_ATOM_TYPE_SYMBOL;
+        }
+    }
+
+    if (rt->atom_free_index == 0) {
+        /* allow new atom entries */
+        uint32_t new_size, start;
+        JSAtomStruct **new_array;
+
+        /* alloc new with size progression 3/2:
+           4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092
+           preallocating space for predefined atoms (at least 195).
+         */
+        new_size = max_int(211, rt->atom_size * 3 / 2);
+        if (new_size > JS_ATOM_MAX)
+            goto fail;
+        /* XXX: should use realloc2 to use slack space */
+        new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size);
+        if (!new_array)
+            goto fail;
+        /* Note: the atom 0 is not used */
+        start = rt->atom_size;
+        if (start == 0) {
+            /* JS_ATOM_NULL entry */
+            p = js_mallocz_rt(rt, sizeof(JSAtomStruct));
+            if (!p) {
+                js_free_rt(rt, new_array);
+                goto fail;
+            }
+            p->header.ref_count = 1;  /* not refcounted */
+            p->atom_type = JS_ATOM_TYPE_SYMBOL;
+#ifdef DUMP_LEAKS
+            list_add_tail(&p->link, &rt->string_list);
+#endif
+            new_array[0] = p;
+            rt->atom_count++;
+            start = 1;
+        }
+        rt->atom_size = new_size;
+        rt->atom_array = new_array;
+        rt->atom_free_index = start;
+        for(i = start; i < new_size; i++) {
+            uint32_t next;
+            if (i == (new_size - 1))
+                next = 0;
+            else
+                next = i + 1;
+            rt->atom_array[i] = atom_set_free(next);
+        }
+    }
+
+    if (str) {
+        if (str->atom_type == 0) {
+            p = str;
+            p->atom_type = atom_type;
+        } else {
+            p = js_malloc_rt(rt, sizeof(JSString) +
+                             (str->len << str->is_wide_char) +
+                             1 - str->is_wide_char);
+            if (unlikely(!p))
+                goto fail;
+            p->header.ref_count = 1;
+            p->is_wide_char = str->is_wide_char;
+            p->len = str->len;
+#ifdef DUMP_LEAKS
+            list_add_tail(&p->link, &rt->string_list);
+#endif
+            memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) +
+                   1 - str->is_wide_char);
+            js_free_string(rt, str);
+        }
+    } else {
+        p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */
+        if (!p)
+            return JS_ATOM_NULL;
+        p->header.ref_count = 1;
+        p->is_wide_char = 1;    /* Hack to represent NULL as a JSString */
+        p->len = 0;
+#ifdef DUMP_LEAKS
+        list_add_tail(&p->link, &rt->string_list);
+#endif
+    }
+
+    /* use an already free entry */
+    i = rt->atom_free_index;
+    rt->atom_free_index = atom_get_free(rt->atom_array[i]);
+    rt->atom_array[i] = p;
+
+    p->hash = h;
+    p->hash_next = i;   /* atom_index */
+    p->atom_type = atom_type;
+
+    rt->atom_count++;
+
+    if (atom_type != JS_ATOM_TYPE_SYMBOL) {
+        p->hash_next = rt->atom_hash[h1];
+        rt->atom_hash[h1] = i;
+        if (unlikely(rt->atom_count >= rt->atom_count_resize))
+            JS_ResizeAtomHash(rt, rt->atom_hash_size * 2);
+    }
+
+    //    JS_DumpAtoms(rt);
+    return i;
+
+ fail:
+    i = JS_ATOM_NULL;
+ done:
+    if (str)
+        js_free_string(rt, str);
+    return i;
+}
+
+/* only works with zero terminated 8 bit strings */
+static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
+                               int atom_type)
+{
+    JSString *p;
+    p = js_alloc_string_rt(rt, len, 0);
+    if (!p)
+        return JS_ATOM_NULL;
+    memcpy(p->u.str8, str, len);
+    p->u.str8[len] = '\0';
+    return __JS_NewAtom(rt, p, atom_type);
+}
+
+static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
+                            int atom_type)
+{
+    uint32_t h, h1, i;
+    JSAtomStruct *p;
+
+    h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING);
+    h &= JS_ATOM_HASH_MASK;
+    h1 = h & (rt->atom_hash_size - 1);
+    i = rt->atom_hash[h1];
+    while (i != 0) {
+        p = rt->atom_array[i];
+        if (p->hash == h &&
+            p->atom_type == JS_ATOM_TYPE_STRING &&
+            p->len == len &&
+            p->is_wide_char == 0 &&
+            memcmp(p->u.str8, str, len) == 0) {
+            if (!__JS_AtomIsConst(i))
+                p->header.ref_count++;
+            return i;
+        }
+        i = p->hash_next;
+    }
+    return JS_ATOM_NULL;
+}
+
+static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p)
+{
+#if 0   /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */
+    if (unlikely(i == JS_ATOM_NULL)) {
+        p->header.ref_count = INT32_MAX / 2;
+        return;
+    }
+#endif
+    uint32_t i = p->hash_next;  /* atom_index */
+    if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
+        JSAtomStruct *p0, *p1;
+        uint32_t h0;
+
+        h0 = p->hash & (rt->atom_hash_size - 1);
+        i = rt->atom_hash[h0];
+        p1 = rt->atom_array[i];
+        if (p1 == p) {
+            rt->atom_hash[h0] = p1->hash_next;
+        } else {
+            for(;;) {
+                assert(i != 0);
+                p0 = p1;
+                i = p1->hash_next;
+                p1 = rt->atom_array[i];
+                if (p1 == p) {
+                    p0->hash_next = p1->hash_next;
+                    break;
+                }
+            }
+        }
+    }
+    /* insert in free atom list */
+    rt->atom_array[i] = atom_set_free(rt->atom_free_index);
+    rt->atom_free_index = i;
+    /* free the string structure */
+#ifdef DUMP_LEAKS
+    list_del(&p->link);
+#endif
+    js_free_rt(rt, p);
+    rt->atom_count--;
+    assert(rt->atom_count >= 0);
+}
+
+static void __JS_FreeAtom(JSRuntime *rt, uint32_t i)
+{
+    JSAtomStruct *p;
+
+    p = rt->atom_array[i];
+    if (--p->header.ref_count > 0)
+        return;
+    JS_FreeAtomStruct(rt, p);
+}
+
+/* Warning: 'p' is freed */
+static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p)
+{
+    JSRuntime *rt = ctx->rt;
+    uint32_t n;
+    if (is_num_string(&n, p)) {
+        if (n <= JS_ATOM_MAX_INT) {
+            js_free_string(rt, p);
+            return __JS_AtomFromUInt32(n);
+        }
+    }
+    /* XXX: should generate an exception */
+    return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING);
+}
+
+JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len)
+{
+    JSValue val;
+
+    if (len == 0 || !is_digit(*str)) {
+        JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING);
+        if (atom)
+            return atom;
+    }
+    val = JS_NewStringLen(ctx, str, len);
+    if (JS_IsException(val))
+        return JS_ATOM_NULL;
+    return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val));
+}
+
+JSAtom JS_NewAtom(JSContext *ctx, const char *str)
+{
+    return JS_NewAtomLen(ctx, str, strlen(str));
+}
+
+JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n)
+{
+    if (n <= JS_ATOM_MAX_INT) {
+        return __JS_AtomFromUInt32(n);
+    } else {
+        char buf[11];
+        JSValue val;
+        snprintf(buf, sizeof(buf), "%u", n);
+        val = JS_NewString(ctx, buf);
+        if (JS_IsException(val))
+            return JS_ATOM_NULL;
+        return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
+                            JS_ATOM_TYPE_STRING);
+    }
+}
+
+static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n)
+{
+    if ((uint64_t)n <= JS_ATOM_MAX_INT) {
+        return __JS_AtomFromUInt32((uint32_t)n);
+    } else {
+        char buf[24];
+        JSValue val;
+        snprintf(buf, sizeof(buf), "%" PRId64 , n);
+        val = JS_NewString(ctx, buf);
+        if (JS_IsException(val))
+            return JS_ATOM_NULL;
+        return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
+                            JS_ATOM_TYPE_STRING);
+    }
+}
+
+/* 'p' is freed */
+static JSValue JS_NewSymbol(JSContext *ctx, JSString *p, int atom_type)
+{
+    JSRuntime *rt = ctx->rt;
+    JSAtom atom;
+    atom = __JS_NewAtom(rt, p, atom_type);
+    if (atom == JS_ATOM_NULL)
+        return JS_ThrowOutOfMemory(ctx);
+    return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]);
+}
+
+/* descr must be a non-numeric string atom */
+static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr,
+                                    int atom_type)
+{
+    JSRuntime *rt = ctx->rt;
+    JSString *p;
+
+    assert(!__JS_AtomIsTaggedInt(descr));
+    assert(descr < rt->atom_size);
+    p = rt->atom_array[descr];
+    JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
+    return JS_NewSymbol(ctx, p, atom_type);
+}
+
+#define ATOM_GET_STR_BUF_SIZE 64
+
+/* Should only be used for debug. */
+static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size,
+                                   JSAtom atom)
+{
+    if (__JS_AtomIsTaggedInt(atom)) {
+        snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom));
+    } else {
+        JSAtomStruct *p;
+        assert(atom < rt->atom_size);
+        if (atom == JS_ATOM_NULL) {
+            snprintf(buf, buf_size, "<null>");
+        } else {
+            int i, c;
+            char *q;
+            JSString *str;
+
+            q = buf;
+            p = rt->atom_array[atom];
+            assert(!atom_is_free(p));
+            str = p;
+            if (str) {
+                if (!str->is_wide_char) {
+                    /* special case ASCII strings */
+                    c = 0;
+                    for(i = 0; i < str->len; i++) {
+                        c |= str->u.str8[i];
+                    }
+                    if (c < 0x80)
+                        return (const char *)str->u.str8;
+                }
+                for(i = 0; i < str->len; i++) {
+                    if (str->is_wide_char)
+                        c = str->u.str16[i];
+                    else
+                        c = str->u.str8[i];
+                    if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX)
+                        break;
+                    if (c < 128) {
+                        *q++ = c;
+                    } else {
+                        q += unicode_to_utf8((uint8_t *)q, c);
+                    }
+                }
+            }
+            *q = '\0';
+        }
+    }
+    return buf;
+}
+
+static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom)
+{
+    return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom);
+}
+
+static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+
+    if (__JS_AtomIsTaggedInt(atom)) {
+        snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom));
+        return JS_NewString(ctx, buf);
+    } else {
+        JSRuntime *rt = ctx->rt;
+        JSAtomStruct *p;
+        assert(atom < rt->atom_size);
+        p = rt->atom_array[atom];
+        if (p->atom_type == JS_ATOM_TYPE_STRING) {
+            goto ret_string;
+        } else if (force_string) {
+            if (p->len == 0 && p->is_wide_char != 0) {
+                /* no description string */
+                p = rt->atom_array[JS_ATOM_empty_string];
+            }
+        ret_string:
+            return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
+        } else {
+            return JS_DupValue(ctx, JS_MKPTR(JS_TAG_SYMBOL, p));
+        }
+    }
+}
+
+JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom)
+{
+    return __JS_AtomToValue(ctx, atom, FALSE);
+}
+
+JSValue JS_AtomToString(JSContext *ctx, JSAtom atom)
+{
+    return __JS_AtomToValue(ctx, atom, TRUE);
+}
+
+/* return TRUE if the atom is an array index (i.e. 0 <= index <=
+   2^32-2 and return its value */
+static BOOL JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom)
+{
+    if (__JS_AtomIsTaggedInt(atom)) {
+        *pval = __JS_AtomToUInt32(atom);
+        return TRUE;
+    } else {
+        JSRuntime *rt = ctx->rt;
+        JSAtomStruct *p;
+        uint32_t val;
+
+        assert(atom < rt->atom_size);
+        p = rt->atom_array[atom];
+        if (p->atom_type == JS_ATOM_TYPE_STRING &&
+            is_num_string(&val, p) && val != -1) {
+            *pval = val;
+            return TRUE;
+        } else {
+            *pval = 0;
+            return FALSE;
+        }
+    }
+}
+
+/* This test must be fast if atom is not a numeric index (e.g. a
+   method name). Return JS_UNDEFINED if not a numeric
+   index. JS_EXCEPTION can also be returned. */
+static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom)
+{
+    JSRuntime *rt = ctx->rt;
+    JSAtomStruct *p1;
+    JSString *p;
+    int c, len, ret;
+    JSValue num, str;
+
+    if (__JS_AtomIsTaggedInt(atom))
+        return JS_NewInt32(ctx, __JS_AtomToUInt32(atom));
+    assert(atom < rt->atom_size);
+    p1 = rt->atom_array[atom];
+    if (p1->atom_type != JS_ATOM_TYPE_STRING)
+        return JS_UNDEFINED;
+    p = p1;
+    len = p->len;
+    if (p->is_wide_char) {
+        const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len;
+        if (r >= r_end)
+            return JS_UNDEFINED;
+        c = *r;
+        if (c == '-') {
+            if (r >= r_end)
+                return JS_UNDEFINED;
+            r++;
+            c = *r;
+            /* -0 case is specific */
+            if (c == '0' && len == 2)
+                goto minus_zero;
+        }
+        /* XXX: should test NaN, but the tests do not check it */
+        if (!is_num(c)) {
+            /* XXX: String should be normalized, therefore 8-bit only */
+            const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' };
+            if (!(c =='I' && (r_end - r) == 8 &&
+                  !memcmp(r + 1, nfinity16, sizeof(nfinity16))))
+                return JS_UNDEFINED;
+        }
+    } else {
+        const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len;
+        if (r >= r_end)
+            return JS_UNDEFINED;
+        c = *r;
+        if (c == '-') {
+            if (r >= r_end)
+                return JS_UNDEFINED;
+            r++;
+            c = *r;
+            /* -0 case is specific */
+            if (c == '0' && len == 2) {
+            minus_zero:
+                return __JS_NewFloat64(ctx, -0.0);
+            }
+        }
+        if (!is_num(c)) {
+            if (!(c =='I' && (r_end - r) == 8 &&
+                  !memcmp(r + 1, "nfinity", 7)))
+                return JS_UNDEFINED;
+        }
+    }
+    /* XXX: bignum: would be better to only accept integer to avoid
+       relying on current floating point precision */
+    /* this is ECMA CanonicalNumericIndexString primitive */
+    num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p));
+    if (JS_IsException(num))
+        return num;
+    str = JS_ToString(ctx, num);
+    if (JS_IsException(str)) {
+        JS_FreeValue(ctx, num);
+        return str;
+    }
+    ret = js_string_compare(ctx, p, JS_VALUE_GET_STRING(str));
+    JS_FreeValue(ctx, str);
+    if (ret == 0) {
+        return num;
+    } else {
+        JS_FreeValue(ctx, num);
+        return JS_UNDEFINED;
+    }
+}
+
+/* return -1 if exception or TRUE/FALSE */
+static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom)
+{
+    JSValue num;
+    num = JS_AtomIsNumericIndex1(ctx, atom);
+    if (likely(JS_IsUndefined(num)))
+        return FALSE;
+    if (JS_IsException(num))
+        return -1;
+    JS_FreeValue(ctx, num);
+    return TRUE;
+}
+
+void JS_FreeAtom(JSContext *ctx, JSAtom v)
+{
+    if (!__JS_AtomIsConst(v))
+        __JS_FreeAtom(ctx->rt, v);
+}
+
+void JS_FreeAtomRT(JSRuntime *rt, JSAtom v)
+{
+    if (!__JS_AtomIsConst(v))
+        __JS_FreeAtom(rt, v);
+}
+
+/* return TRUE if 'v' is a symbol with a string description */
+static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v)
+{
+    JSRuntime *rt;
+    JSAtomStruct *p;
+
+    rt = ctx->rt;
+    if (__JS_AtomIsTaggedInt(v))
+        return FALSE;
+    p = rt->atom_array[v];
+    return (((p->atom_type == JS_ATOM_TYPE_SYMBOL &&
+              p->hash == JS_ATOM_HASH_SYMBOL) ||
+             p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) &&
+            !(p->len == 0 && p->is_wide_char != 0));
+}
+
+static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    const char *p;
+    int i;
+
+    /* XXX: should handle embedded null characters */
+    /* XXX: should move encoding code to JS_AtomGetStr */
+    p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom);
+    for (i = 0; p[i]; i++) {
+        int c = (unsigned char)p[i];
+        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+              (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0)))
+            break;
+    }
+    if (i > 0 && p[i] == '\0') {
+        printf("%s", p);
+    } else {
+        putchar('"');
+        printf("%.*s", i, p);
+        for (; p[i]; i++) {
+            int c = (unsigned char)p[i];
+            if (c == '\"' || c == '\\') {
+                putchar('\\');
+                putchar(c);
+            } else if (c >= ' ' && c <= 126) {
+                putchar(c);
+            } else if (c == '\n') {
+                putchar('\\');
+                putchar('n');
+            } else {
+                printf("\\u%04x", c);
+            }
+        }
+        putchar('\"');
+    }
+}
+
+/* free with JS_FreeCString() */
+const char *JS_AtomToCString(JSContext *ctx, JSAtom atom)
+{
+    JSValue str;
+    const char *cstr;
+
+    str = JS_AtomToString(ctx, atom);
+    if (JS_IsException(str))
+        return NULL;
+    cstr = JS_ToCString(ctx, str);
+    JS_FreeValue(ctx, str);
+    return cstr;
+}
+
+/* return a string atom containing name concatenated with str1 */
+static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1)
+{
+    JSValue str;
+    JSAtom atom;
+    const char *cstr;
+    char *cstr2;
+    size_t len, len1;
+    
+    str = JS_AtomToString(ctx, name);
+    if (JS_IsException(str))
+        return JS_ATOM_NULL;
+    cstr = JS_ToCStringLen(ctx, &len, str);
+    if (!cstr)
+        goto fail;
+    len1 = strlen(str1);
+    cstr2 = js_malloc(ctx, len + len1 + 1);
+    if (!cstr2)
+        goto fail;
+    memcpy(cstr2, cstr, len);
+    memcpy(cstr2 + len, str1, len1);
+    cstr2[len + len1] = '\0';
+    atom = JS_NewAtomLen(ctx, cstr2, len + len1);
+    js_free(ctx, cstr2);
+    JS_FreeCString(ctx, cstr);
+    JS_FreeValue(ctx, str);
+    return atom;
+ fail:
+    JS_FreeCString(ctx, cstr);
+    JS_FreeValue(ctx, str);
+    return JS_ATOM_NULL;
+}
+
+static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n)
+{
+    char buf[16];
+    snprintf(buf, sizeof(buf), "%u", n);
+    return js_atom_concat_str(ctx, name, buf);
+}
+
+static inline BOOL JS_IsEmptyString(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0;
+}
+
+/* JSClass support */
+
+/* a new class ID is allocated if *pclass_id != 0 */
+JSClassID JS_NewClassID(JSClassID *pclass_id)
+{
+    JSClassID class_id;
+    /* XXX: make it thread safe */
+    class_id = *pclass_id;
+    if (class_id == 0) {
+        class_id = js_class_id_alloc++;
+        *pclass_id = class_id;
+    }
+    return class_id;
+}
+
+BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id)
+{
+    return (class_id < rt->class_count &&
+            rt->class_array[class_id].class_id != 0);
+}
+
+/* create a new object internal class. Return -1 if error, 0 if
+   OK. The finalizer can be NULL if none is needed. */
+static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
+                        const JSClassDef *class_def, JSAtom name)
+{
+    int new_size, i;
+    JSClass *cl, *new_class_array;
+    struct list_head *el;
+
+    if (class_id < rt->class_count &&
+        rt->class_array[class_id].class_id != 0)
+        return -1;
+
+    if (class_id >= rt->class_count) {
+        new_size = max_int(JS_CLASS_INIT_COUNT,
+                           max_int(class_id + 1, rt->class_count * 3 / 2));
+
+        /* reallocate the context class prototype array, if any */
+        list_for_each(el, &rt->context_list) {
+            JSContext *ctx = list_entry(el, JSContext, link);
+            JSValue *new_tab;
+            new_tab = js_realloc_rt(rt, ctx->class_proto,
+                                    sizeof(ctx->class_proto[0]) * new_size);
+            if (!new_tab)
+                return -1;
+            for(i = rt->class_count; i < new_size; i++)
+                new_tab[i] = JS_NULL;
+            ctx->class_proto = new_tab;
+        }
+        /* reallocate the class array */
+        new_class_array = js_realloc_rt(rt, rt->class_array,
+                                        sizeof(JSClass) * new_size);
+        if (!new_class_array)
+            return -1;
+        memset(new_class_array + rt->class_count, 0,
+               (new_size - rt->class_count) * sizeof(JSClass));
+        rt->class_array = new_class_array;
+        rt->class_count = new_size;
+    }
+    cl = &rt->class_array[class_id];
+    cl->class_id = class_id;
+    cl->class_name = JS_DupAtomRT(rt, name);
+    cl->finalizer = class_def->finalizer;
+    cl->gc_mark = class_def->gc_mark;
+    cl->call = class_def->call;
+    cl->exotic = class_def->exotic;
+    return 0;
+}
+
+int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def)
+{
+    int ret, len;
+    JSAtom name;
+
+    len = strlen(class_def->class_name);
+    name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING);
+    if (name == JS_ATOM_NULL) {
+        name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING);
+        if (name == JS_ATOM_NULL)
+            return -1;
+    }
+    ret = JS_NewClass1(rt, class_id, class_def, name);
+    JS_FreeAtomRT(rt, name);
+    return ret;
+}
+
+static JSValue js_new_string8(JSContext *ctx, const uint8_t *buf, int len)
+{
+    JSString *str;
+
+    if (len <= 0) {
+        return JS_AtomToString(ctx, JS_ATOM_empty_string);
+    }
+    str = js_alloc_string(ctx, len, 0);
+    if (!str)
+        return JS_EXCEPTION;
+    memcpy(str->u.str8, buf, len);
+    str->u.str8[len] = '\0';
+    return JS_MKPTR(JS_TAG_STRING, str);
+}
+
+static JSValue js_new_string16(JSContext *ctx, const uint16_t *buf, int len)
+{
+    JSString *str;
+    str = js_alloc_string(ctx, len, 1);
+    if (!str)
+        return JS_EXCEPTION;
+    memcpy(str->u.str16, buf, len * 2);
+    return JS_MKPTR(JS_TAG_STRING, str);
+}
+
+static JSValue js_new_string_char(JSContext *ctx, uint16_t c)
+{
+    if (c < 0x100) {
+        uint8_t ch8 = c;
+        return js_new_string8(ctx, &ch8, 1);
+    } else {
+        uint16_t ch16 = c;
+        return js_new_string16(ctx, &ch16, 1);
+    }
+}
+
+static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end)
+{
+    int len = end - start;
+    if (start == 0 && end == p->len) {
+        return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
+    }
+    if (p->is_wide_char && len > 0) {
+        JSString *str;
+        int i;
+        uint16_t c = 0;
+        for (i = start; i < end; i++) {
+            c |= p->u.str16[i];
+        }
+        if (c > 0xFF)
+            return js_new_string16(ctx, p->u.str16 + start, len);
+
+        str = js_alloc_string(ctx, len, 0);
+        if (!str)
+            return JS_EXCEPTION;
+        for (i = 0; i < len; i++) {
+            str->u.str8[i] = p->u.str16[start + i];
+        }
+        str->u.str8[len] = '\0';
+        return JS_MKPTR(JS_TAG_STRING, str);
+    } else {
+        return js_new_string8(ctx, p->u.str8 + start, len);
+    }
+}
+
+typedef struct StringBuffer {
+    JSContext *ctx;
+    JSString *str;
+    int len;
+    int size;
+    int is_wide_char;
+    int error_status;
+} StringBuffer;
+
+/* It is valid to call string_buffer_end() and all string_buffer functions even
+   if string_buffer_init() or another string_buffer function returns an error.
+   If the error_status is set, string_buffer_end() returns JS_EXCEPTION.
+ */
+static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size,
+                               int is_wide)
+{
+    s->ctx = ctx;
+    s->size = size;
+    s->len = 0;
+    s->is_wide_char = is_wide;
+    s->error_status = 0;
+    s->str = js_alloc_string(ctx, size, is_wide);
+    if (unlikely(!s->str)) {
+        s->size = 0;
+        return s->error_status = -1;
+    }
+#ifdef DUMP_LEAKS
+    /* the StringBuffer may reallocate the JSString, only link it at the end */
+    list_del(&s->str->link);
+#endif
+    return 0;
+}
+
+static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size)
+{
+    return string_buffer_init2(ctx, s, size, 0);
+}
+
+static void string_buffer_free(StringBuffer *s)
+{
+    js_free(s->ctx, s->str);
+    s->str = NULL;
+}
+
+static int string_buffer_set_error(StringBuffer *s)
+{
+    js_free(s->ctx, s->str);
+    s->str = NULL;
+    s->size = 0;
+    s->len = 0;
+    return s->error_status = -1;
+}
+
+static no_inline int string_buffer_widen(StringBuffer *s, int size)
+{
+    JSString *str;
+    size_t slack;
+    int i;
+
+    if (s->error_status)
+        return -1;
+
+    str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack);
+    if (!str)
+        return string_buffer_set_error(s);
+    size += slack >> 1;
+    for(i = s->len; i-- > 0;) {
+        str->u.str16[i] = str->u.str8[i];
+    }
+    s->is_wide_char = 1;
+    s->size = size;
+    s->str = str;
+    return 0;
+}
+
+static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c)
+{
+    JSString *new_str;
+    int new_size;
+    size_t new_size_bytes, slack;
+
+    if (s->error_status)
+        return -1;
+
+    if (new_len > JS_STRING_LEN_MAX) {
+        JS_ThrowInternalError(s->ctx, "string too long");
+        return string_buffer_set_error(s);
+    }
+    new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX);
+    if (!s->is_wide_char && c >= 0x100) {
+        return string_buffer_widen(s, new_size);
+    }
+    new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char;
+    new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack);
+    if (!new_str)
+        return string_buffer_set_error(s);
+    new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX);
+    s->size = new_size;
+    s->str = new_str;
+    return 0;
+}
+
+static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c)
+{
+    if (unlikely(s->len >= s->size)) {
+        if (string_buffer_realloc(s, s->len + 1, c))
+            return -1;
+    }
+    if (s->is_wide_char) {
+        s->str->u.str16[s->len++] = c;
+    } else if (c < 0x100) {
+        s->str->u.str8[s->len++] = c;
+    } else {
+        if (string_buffer_widen(s, s->size))
+            return -1;
+        s->str->u.str16[s->len++] = c;
+    }
+    return 0;
+}
+
+/* 0 <= c <= 0xff */
+static int string_buffer_putc8(StringBuffer *s, uint32_t c)
+{
+    if (unlikely(s->len >= s->size)) {
+        if (string_buffer_realloc(s, s->len + 1, c))
+            return -1;
+    }
+    if (s->is_wide_char) {
+        s->str->u.str16[s->len++] = c;
+    } else {
+        s->str->u.str8[s->len++] = c;
+    }
+    return 0;
+}
+
+/* 0 <= c <= 0xffff */
+static int string_buffer_putc16(StringBuffer *s, uint32_t c)
+{
+    if (likely(s->len < s->size)) {
+        if (s->is_wide_char) {
+            s->str->u.str16[s->len++] = c;
+            return 0;
+        } else if (c < 0x100) {
+            s->str->u.str8[s->len++] = c;
+            return 0;
+        }
+    }
+    return string_buffer_putc_slow(s, c);
+}
+
+/* 0 <= c <= 0x10ffff */
+static int string_buffer_putc(StringBuffer *s, uint32_t c)
+{
+    if (unlikely(c >= 0x10000)) {
+        /* surrogate pair */
+        c -= 0x10000;
+        if (string_buffer_putc16(s, (c >> 10) + 0xd800))
+            return -1;
+        c = (c & 0x3ff) + 0xdc00;
+    }
+    return string_buffer_putc16(s, c);
+}
+
+static int string_get(const JSString *p, int idx) {
+    return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
+}
+
+static int string_getc(const JSString *p, int *pidx)
+{
+    int idx, c, c1;
+    idx = *pidx;
+    if (p->is_wide_char) {
+        c = p->u.str16[idx++];
+        if (c >= 0xd800 && c < 0xdc00 && idx < p->len) {
+            c1 = p->u.str16[idx];
+            if (c1 >= 0xdc00 && c1 < 0xe000) {
+                c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
+                idx++;
+            }
+        }
+    } else {
+        c = p->u.str8[idx++];
+    }
+    *pidx = idx;
+    return c;
+}
+
+static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len)
+{
+    int i;
+
+    if (s->len + len > s->size) {
+        if (string_buffer_realloc(s, s->len + len, 0))
+            return -1;
+    }
+    if (s->is_wide_char) {
+        for (i = 0; i < len; i++) {
+            s->str->u.str16[s->len + i] = p[i];
+        }
+        s->len += len;
+    } else {
+        memcpy(&s->str->u.str8[s->len], p, len);
+        s->len += len;
+    }
+    return 0;
+}
+
+static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len)
+{
+    int c = 0, i;
+
+    for (i = 0; i < len; i++) {
+        c |= p[i];
+    }
+    if (s->len + len > s->size) {
+        if (string_buffer_realloc(s, s->len + len, c))
+            return -1;
+    } else if (!s->is_wide_char && c >= 0x100) {
+        if (string_buffer_widen(s, s->size))
+            return -1;
+    }
+    if (s->is_wide_char) {
+        memcpy(&s->str->u.str16[s->len], p, len << 1);
+        s->len += len;
+    } else {
+        for (i = 0; i < len; i++) {
+            s->str->u.str8[s->len + i] = p[i];
+        }
+        s->len += len;
+    }
+    return 0;
+}
+
+/* appending an ASCII string */
+static int string_buffer_puts8(StringBuffer *s, const char *str)
+{
+    return string_buffer_write8(s, (const uint8_t *)str, strlen(str));
+}
+
+static int string_buffer_concat(StringBuffer *s, const JSString *p,
+                                uint32_t from, uint32_t to)
+{
+    if (to <= from)
+        return 0;
+    if (p->is_wide_char)
+        return string_buffer_write16(s, p->u.str16 + from, to - from);
+    else
+        return string_buffer_write8(s, p->u.str8 + from, to - from);
+}
+
+static int string_buffer_concat_value(StringBuffer *s, JSValueConst v)
+{
+    JSString *p;
+    JSValue v1;
+    int res;
+
+    if (s->error_status) {
+        /* prevent exception overload */
+        return -1;
+    }
+    if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) {
+        v1 = JS_ToString(s->ctx, v);
+        if (JS_IsException(v1))
+            return string_buffer_set_error(s);
+        p = JS_VALUE_GET_STRING(v1);
+        res = string_buffer_concat(s, p, 0, p->len);
+        JS_FreeValue(s->ctx, v1);
+        return res;
+    }
+    p = JS_VALUE_GET_STRING(v);
+    return string_buffer_concat(s, p, 0, p->len);
+}
+
+static int string_buffer_concat_value_free(StringBuffer *s, JSValue v)
+{
+    JSString *p;
+    int res;
+
+    if (s->error_status) {
+        /* prevent exception overload */
+        JS_FreeValue(s->ctx, v);
+        return -1;
+    }
+    if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) {
+        v = JS_ToStringFree(s->ctx, v);
+        if (JS_IsException(v))
+            return string_buffer_set_error(s);
+    }
+    p = JS_VALUE_GET_STRING(v);
+    res = string_buffer_concat(s, p, 0, p->len);
+    JS_FreeValue(s->ctx, v);
+    return res;
+}
+
+static int string_buffer_fill(StringBuffer *s, int c, int count)
+{
+    /* XXX: optimize */
+    if (s->len + count > s->size) {
+        if (string_buffer_realloc(s, s->len + count, c))
+            return -1;
+    }
+    while (count-- > 0) {
+        if (string_buffer_putc16(s, c))
+            return -1;
+    }
+    return 0;
+}
+
+static JSValue string_buffer_end(StringBuffer *s)
+{
+    JSString *str;
+    str = s->str;
+    if (s->error_status)
+        return JS_EXCEPTION;
+    if (s->len == 0) {
+        js_free(s->ctx, str);
+        s->str = NULL;
+        return JS_AtomToString(s->ctx, JS_ATOM_empty_string);
+    }
+    if (s->len < s->size) {
+        /* smaller size so js_realloc should not fail, but OK if it does */
+        /* XXX: should add some slack to avoid unnecessary calls */
+        /* XXX: might need to use malloc+free to ensure smaller size */
+        str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) +
+                            (s->len << s->is_wide_char) + 1 - s->is_wide_char);
+        if (str == NULL)
+            str = s->str;
+        s->str = str;
+    }
+    if (!s->is_wide_char)
+        str->u.str8[s->len] = 0;
+#ifdef DUMP_LEAKS
+    list_add_tail(&str->link, &s->ctx->rt->string_list);
+#endif
+    str->is_wide_char = s->is_wide_char;
+    str->len = s->len;
+    s->str = NULL;
+    return JS_MKPTR(JS_TAG_STRING, str);
+}
+
+/* create a string from a UTF-8 buffer */
+JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len)
+{
+    const uint8_t *p, *p_end, *p_start, *p_next;
+    uint32_t c;
+    StringBuffer b_s, *b = &b_s;
+    size_t len1;
+    
+    p_start = (const uint8_t *)buf;
+    p_end = p_start + buf_len;
+    p = p_start;
+    while (p < p_end && *p < 128)
+        p++;
+    len1 = p - p_start;
+    if (len1 > JS_STRING_LEN_MAX)
+        return JS_ThrowInternalError(ctx, "string too long");
+    if (p == p_end) {
+        /* ASCII string */
+        return js_new_string8(ctx, (const uint8_t *)buf, buf_len);
+    } else {
+        if (string_buffer_init(ctx, b, buf_len))
+            goto fail;
+        string_buffer_write8(b, p_start, len1);
+        while (p < p_end) {
+            if (*p < 128) {
+                string_buffer_putc8(b, *p++);
+            } else {
+                /* parse utf-8 sequence, return 0xFFFFFFFF for error */
+                c = unicode_from_utf8(p, p_end - p, &p_next);
+                if (c < 0x10000) {
+                    p = p_next;
+                } else if (c <= 0x10FFFF) {
+                    p = p_next;
+                    /* surrogate pair */
+                    c -= 0x10000;
+                    string_buffer_putc16(b, (c >> 10) + 0xd800);
+                    c = (c & 0x3ff) + 0xdc00;
+                } else {
+                    /* invalid char */
+                    c = 0xfffd;
+                    /* skip the invalid chars */
+                    /* XXX: seems incorrect. Why not just use c = *p++; ? */
+                    while (p < p_end && (*p >= 0x80 && *p < 0xc0))
+                        p++;
+                    if (p < p_end) {
+                        p++;
+                        while (p < p_end && (*p >= 0x80 && *p < 0xc0))
+                            p++;
+                    }
+                }
+                string_buffer_putc16(b, c);
+            }
+        }
+    }
+    return string_buffer_end(b);
+
+ fail:
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static JSValue JS_ConcatString3(JSContext *ctx, const char *str1,
+                                JSValue str2, const char *str3)
+{
+    StringBuffer b_s, *b = &b_s;
+    int len1, len3;
+    JSString *p;
+
+    if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) {
+        str2 = JS_ToStringFree(ctx, str2);
+        if (JS_IsException(str2))
+            goto fail;
+    }
+    p = JS_VALUE_GET_STRING(str2);
+    len1 = strlen(str1);
+    len3 = strlen(str3);
+
+    if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char))
+        goto fail;
+
+    string_buffer_write8(b, (const uint8_t *)str1, len1);
+    string_buffer_concat(b, p, 0, p->len);
+    string_buffer_write8(b, (const uint8_t *)str3, len3);
+
+    JS_FreeValue(ctx, str2);
+    return string_buffer_end(b);
+
+ fail:
+    JS_FreeValue(ctx, str2);
+    return JS_EXCEPTION;
+}
+
+JSValue JS_NewString(JSContext *ctx, const char *str)
+{
+    return JS_NewStringLen(ctx, str, strlen(str));
+}
+
+JSValue JS_NewAtomString(JSContext *ctx, const char *str)
+{
+    JSAtom atom = JS_NewAtom(ctx, str);
+    if (atom == JS_ATOM_NULL)
+        return JS_EXCEPTION;
+    JSValue val = JS_AtomToString(ctx, atom);
+    JS_FreeAtom(ctx, atom);
+    return val;
+}
+
+/* return (NULL, 0) if exception. */
+/* return pointer into a JSString with a live ref_count */
+/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */
+const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, BOOL cesu8)
+{
+    JSValue val;
+    JSString *str, *str_new;
+    int pos, len, c, c1;
+    uint8_t *q;
+
+    if (JS_VALUE_GET_TAG(val1) != JS_TAG_STRING) {
+        val = JS_ToString(ctx, val1);
+        if (JS_IsException(val))
+            goto fail;
+    } else {
+        val = JS_DupValue(ctx, val1);
+    }
+
+    str = JS_VALUE_GET_STRING(val);
+    len = str->len;
+    if (!str->is_wide_char) {
+        const uint8_t *src = str->u.str8;
+        int count;
+
+        /* count the number of non-ASCII characters */
+        /* Scanning the whole string is required for ASCII strings,
+           and computing the number of non-ASCII bytes is less expensive
+           than testing each byte, hence this method is faster for ASCII
+           strings, which is the most common case.
+         */
+        count = 0;
+        for (pos = 0; pos < len; pos++) {
+            count += src[pos] >> 7;
+        }
+        if (count == 0) {
+            if (plen)
+                *plen = len;
+            return (const char *)src;
+        }
+        str_new = js_alloc_string(ctx, len + count, 0);
+        if (!str_new)
+            goto fail;
+        q = str_new->u.str8;
+        for (pos = 0; pos < len; pos++) {
+            c = src[pos];
+            if (c < 0x80) {
+                *q++ = c;
+            } else {
+                *q++ = (c >> 6) | 0xc0;
+                *q++ = (c & 0x3f) | 0x80;
+            }
+        }
+    } else {
+        const uint16_t *src = str->u.str16;
+        /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may
+           produce 4 bytes but use 2 code points.
+         */
+        str_new = js_alloc_string(ctx, len * 3, 0);
+        if (!str_new)
+            goto fail;
+        q = str_new->u.str8;
+        pos = 0;
+        while (pos < len) {
+            c = src[pos++];
+            if (c < 0x80) {
+                *q++ = c;
+            } else {
+                if (c >= 0xd800 && c < 0xdc00) {
+                    if (pos < len && !cesu8) {
+                        c1 = src[pos];
+                        if (c1 >= 0xdc00 && c1 < 0xe000) {
+                            pos++;
+                            /* surrogate pair */
+                            c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
+                        } else {
+                            /* Keep unmatched surrogate code points */
+                            /* c = 0xfffd; */ /* error */
+                        }
+                    } else {
+                        /* Keep unmatched surrogate code points */
+                        /* c = 0xfffd; */ /* error */
+                    }
+                }
+                q += unicode_to_utf8(q, c);
+            }
+        }
+    }
+
+    *q = '\0';
+    str_new->len = q - str_new->u.str8;
+    JS_FreeValue(ctx, val);
+    if (plen)
+        *plen = str_new->len;
+    return (const char *)str_new->u.str8;
+ fail:
+    if (plen)
+        *plen = 0;
+    return NULL;
+}
+
+void JS_FreeCString(JSContext *ctx, const char *ptr)
+{
+    JSString *p;
+    if (!ptr)
+        return;
+    /* purposely removing constness */
+    p = (JSString *)(void *)(ptr - offsetof(JSString, u));
+    JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
+}
+
+static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len)
+{
+    int c, i;
+    for(i = 0; i < len; i++) {
+        c = src1[i] - src2[i];
+        if (c != 0)
+            return c;
+    }
+    return 0;
+}
+
+static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len)
+{
+    int c, i;
+    for(i = 0; i < len; i++) {
+        c = src1[i] - src2[i];
+        if (c != 0)
+            return c;
+    }
+    return 0;
+}
+
+static int js_string_memcmp(const JSString *p1, const JSString *p2, int len)
+{
+    int res;
+
+    if (likely(!p1->is_wide_char)) {
+        if (likely(!p2->is_wide_char))
+            res = memcmp(p1->u.str8, p2->u.str8, len);
+        else
+            res = -memcmp16_8(p2->u.str16, p1->u.str8, len);
+    } else {
+        if (!p2->is_wide_char)
+            res = memcmp16_8(p1->u.str16, p2->u.str8, len);
+        else
+            res = memcmp16(p1->u.str16, p2->u.str16, len);
+    }
+    return res;
+}
+
+/* return < 0, 0 or > 0 */
+static int js_string_compare(JSContext *ctx,
+                             const JSString *p1, const JSString *p2)
+{
+    int res, len;
+    len = min_int(p1->len, p2->len);
+    res = js_string_memcmp(p1, p2, len);
+    if (res == 0) {
+        if (p1->len == p2->len)
+            res = 0;
+        else if (p1->len < p2->len)
+            res = -1;
+        else
+            res = 1;
+    }
+    return res;
+}
+
+static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len)
+{
+    if (p->is_wide_char) {
+        memcpy(dst, p->u.str16 + offset, len * 2);
+    } else {
+        const uint8_t *src1 = p->u.str8 + offset;
+        int i;
+
+        for(i = 0; i < len; i++)
+            dst[i] = src1[i];
+    }
+}
+
+static JSValue JS_ConcatString1(JSContext *ctx,
+                                const JSString *p1, const JSString *p2)
+{
+    JSString *p;
+    uint32_t len;
+    int is_wide_char;
+
+    len = p1->len + p2->len;
+    if (len > JS_STRING_LEN_MAX)
+        return JS_ThrowInternalError(ctx, "string too long");
+    is_wide_char = p1->is_wide_char | p2->is_wide_char;
+    p = js_alloc_string(ctx, len, is_wide_char);
+    if (!p)
+        return JS_EXCEPTION;
+    if (!is_wide_char) {
+        memcpy(p->u.str8, p1->u.str8, p1->len);
+        memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len);
+        p->u.str8[len] = '\0';
+    } else {
+        copy_str16(p->u.str16, p1, 0, p1->len);
+        copy_str16(p->u.str16 + p1->len, p2, 0, p2->len);
+    }
+    return JS_MKPTR(JS_TAG_STRING, p);
+}
+
+/* op1 and op2 are converted to strings. For convience, op1 or op2 =
+   JS_EXCEPTION are accepted and return JS_EXCEPTION.  */
+static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2)
+{
+    JSValue ret;
+    JSString *p1, *p2;
+
+    if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) {
+        op1 = JS_ToStringFree(ctx, op1);
+        if (JS_IsException(op1)) {
+            JS_FreeValue(ctx, op2);
+            return JS_EXCEPTION;
+        }
+    }
+    if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) {
+        op2 = JS_ToStringFree(ctx, op2);
+        if (JS_IsException(op2)) {
+            JS_FreeValue(ctx, op1);
+            return JS_EXCEPTION;
+        }
+    }
+    p1 = JS_VALUE_GET_STRING(op1);
+    p2 = JS_VALUE_GET_STRING(op2);
+
+    /* XXX: could also check if p1 is empty */
+    if (p2->len == 0) {
+        goto ret_op1;
+    }
+    if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char
+    &&  js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) {
+        /* Concatenate in place in available space at the end of p1 */
+        if (p1->is_wide_char) {
+            memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1);
+            p1->len += p2->len;
+        } else {
+            memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len);
+            p1->len += p2->len;
+            p1->u.str8[p1->len] = '\0';
+        }
+    ret_op1:
+        JS_FreeValue(ctx, op2);
+        return op1;
+    }
+    ret = JS_ConcatString1(ctx, p1, p2);
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+    return ret;
+}
+
+/* Shape support */
+
+static inline size_t get_shape_size(size_t hash_size, size_t prop_size)
+{
+    return hash_size * sizeof(uint32_t) + sizeof(JSShape) +
+        prop_size * sizeof(JSShapeProperty);
+}
+
+static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size)
+{
+    return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size);
+}
+
+static inline void *get_alloc_from_shape(JSShape *sh)
+{
+    return sh->prop_hash_end - ((intptr_t)sh->prop_hash_mask + 1);
+}
+
+static inline JSShapeProperty *get_shape_prop(JSShape *sh)
+{
+    return sh->prop;
+}
+
+static int init_shape_hash(JSRuntime *rt)
+{
+    rt->shape_hash_bits = 4;   /* 16 shapes */
+    rt->shape_hash_size = 1 << rt->shape_hash_bits;
+    rt->shape_hash_count = 0;
+    rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) *
+                                   rt->shape_hash_size);
+    if (!rt->shape_hash)
+        return -1;
+    return 0;
+}
+
+/* same magic hash multiplier as the Linux kernel */
+static uint32_t shape_hash(uint32_t h, uint32_t val)
+{
+    return (h + val) * 0x9e370001;
+}
+
+/* truncate the shape hash to 'hash_bits' bits */
+static uint32_t get_shape_hash(uint32_t h, int hash_bits)
+{
+    return h >> (32 - hash_bits);
+}
+
+static uint32_t shape_initial_hash(JSObject *proto)
+{
+    uint32_t h;
+    h = shape_hash(1, (uintptr_t)proto);
+    if (sizeof(proto) > 4)
+        h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32);
+    return h;
+}
+
+static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits)
+{
+    int new_shape_hash_size, i;
+    uint32_t h;
+    JSShape **new_shape_hash, *sh, *sh_next;
+
+    new_shape_hash_size = 1 << new_shape_hash_bits;
+    new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) *
+                                   new_shape_hash_size);
+    if (!new_shape_hash)
+        return -1;
+    for(i = 0; i < rt->shape_hash_size; i++) {
+        for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) {
+            sh_next = sh->shape_hash_next;
+            h = get_shape_hash(sh->hash, new_shape_hash_bits);
+            sh->shape_hash_next = new_shape_hash[h];
+            new_shape_hash[h] = sh;
+        }
+    }
+    js_free_rt(rt, rt->shape_hash);
+    rt->shape_hash_bits = new_shape_hash_bits;
+    rt->shape_hash_size = new_shape_hash_size;
+    rt->shape_hash = new_shape_hash;
+    return 0;
+}
+
+static void js_shape_hash_link(JSRuntime *rt, JSShape *sh)
+{
+    uint32_t h;
+    h = get_shape_hash(sh->hash, rt->shape_hash_bits);
+    sh->shape_hash_next = rt->shape_hash[h];
+    rt->shape_hash[h] = sh;
+    rt->shape_hash_count++;
+}
+
+static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh)
+{
+    uint32_t h;
+    JSShape **psh;
+
+    h = get_shape_hash(sh->hash, rt->shape_hash_bits);
+    psh = &rt->shape_hash[h];
+    while (*psh != sh)
+        psh = &(*psh)->shape_hash_next;
+    *psh = sh->shape_hash_next;
+    rt->shape_hash_count--;
+}
+
+/* create a new empty shape with prototype 'proto' */
+static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto,
+                                        int hash_size, int prop_size)
+{
+    JSRuntime *rt = ctx->rt;
+    void *sh_alloc;
+    JSShape *sh;
+
+    /* resize the shape hash table if necessary */
+    if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) {
+        resize_shape_hash(rt, rt->shape_hash_bits + 1);
+    }
+
+    sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size));
+    if (!sh_alloc)
+        return NULL;
+    sh = get_shape_from_alloc(sh_alloc, hash_size);
+    sh->header.ref_count = 1;
+    sh->gc_header.mark = 0;
+    if (proto)
+        JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto));
+    sh->proto = proto;
+    memset(sh->prop_hash_end - hash_size, 0, sizeof(sh->prop_hash_end[0]) *
+           hash_size);
+    sh->prop_hash_mask = hash_size - 1;
+    sh->prop_count = 0;
+    sh->prop_size = prop_size;
+
+    /* insert in the hash table */
+    sh->hash = shape_initial_hash(proto);
+    sh->is_hashed = TRUE;
+    sh->has_small_array_index = FALSE;
+    js_shape_hash_link(ctx->rt, sh);
+    return sh;
+}
+
+static JSShape *js_new_shape(JSContext *ctx, JSObject *proto)
+{
+    return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE,
+                         JS_PROP_INITIAL_SIZE);
+}
+
+/* The shape is cloned. The new shape is not inserted in the shape
+   hash table */
+static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1)
+{
+    JSShape *sh;
+    void *sh_alloc, *sh_alloc1;
+    size_t size;
+    JSShapeProperty *pr;
+    uint32_t i, hash_size;
+
+    hash_size = sh1->prop_hash_mask + 1;
+    size = get_shape_size(hash_size, sh1->prop_size);
+    sh_alloc = js_malloc(ctx, size);
+    if (!sh_alloc)
+        return NULL;
+    sh_alloc1 = get_alloc_from_shape(sh1);
+    memcpy(sh_alloc, sh_alloc1, size);
+    sh = get_shape_from_alloc(sh_alloc, hash_size);
+    sh->header.ref_count = 1;
+    sh->gc_header.mark = 0;
+    sh->is_hashed = FALSE;
+    if (sh->proto) {
+        JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
+    }
+    for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
+        JS_DupAtom(ctx, pr->atom);
+    }
+    return sh;
+}
+
+static JSShape *js_dup_shape(JSShape *sh)
+{
+    sh->header.ref_count++;
+    return sh;
+}
+
+static void js_free_shape(JSRuntime *rt, JSShape *sh)
+{
+    uint32_t i;
+    JSShapeProperty *pr;
+
+    if (unlikely(--sh->header.ref_count <= 0)) {
+        assert(sh->header.ref_count == 0);
+        if (sh->is_hashed)
+            js_shape_hash_unlink(rt, sh);
+        if (sh->proto != NULL) {
+            JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
+        }
+        pr = get_shape_prop(sh);
+        for(i = 0; i < sh->prop_count; i++) {
+            JS_FreeAtomRT(rt, pr->atom);
+            pr++;
+        }
+        js_free_rt(rt, get_alloc_from_shape(sh));
+    }
+}
+
+static void js_free_shape_null(JSRuntime *rt, JSShape *sh)
+{
+    if (sh)
+        js_free_shape(rt, sh);
+}
+
+/* make space to hold at least 'count' properties */
+static no_inline int resize_properties(JSContext *ctx, JSShape **psh,
+                                       JSObject *p, uint32_t count)
+{
+    JSShape *sh;
+    uint32_t new_size, new_hash_size, new_hash_mask, i;
+    JSShapeProperty *pr;
+    void *sh_alloc;
+    intptr_t h;
+
+    sh = *psh;
+    new_size = max_int(count, sh->prop_size * 3 / 2);
+    /* Reallocate prop array first to avoid crash or size inconsistency
+       in case of memory allocation failure */
+    if (p) {
+        JSProperty *new_prop;
+        new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
+        if (unlikely(!new_prop))
+            return -1;
+        p->prop = new_prop;
+    }
+    new_hash_size = sh->prop_hash_mask + 1;
+    while (new_hash_size < new_size)
+        new_hash_size = 2 * new_hash_size;
+    if (new_hash_size != (sh->prop_hash_mask + 1)) {
+        JSShape *old_sh;
+        /* resize the hash table and the properties */
+        old_sh = sh;
+        sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
+        if (!sh_alloc)
+            return -1;
+        sh = get_shape_from_alloc(sh_alloc, new_hash_size);
+        /* copy all the fields and the properties */
+        memcpy(sh, old_sh,
+               sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count);
+        new_hash_mask = new_hash_size - 1;
+        sh->prop_hash_mask = new_hash_mask;
+        memset(sh->prop_hash_end - new_hash_size, 0,
+               sizeof(sh->prop_hash_end[0]) * new_hash_size);
+        for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) {
+            if (pr->atom != JS_ATOM_NULL) {
+                h = ((uintptr_t)pr->atom & new_hash_mask);
+                pr->hash_next = sh->prop_hash_end[-h - 1];
+                sh->prop_hash_end[-h - 1] = i + 1;
+            }
+        }
+        js_free(ctx, get_alloc_from_shape(old_sh));
+    } else {
+        /* only resize the properties */
+        sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh),
+                              get_shape_size(new_hash_size, new_size));
+        if (unlikely(!sh_alloc))
+            return -1;
+        sh = get_shape_from_alloc(sh_alloc, new_hash_size);
+    }
+    *psh = sh;
+    sh->prop_size = new_size;
+    return 0;
+}
+
+static int add_shape_property(JSContext *ctx, JSShape **psh,
+                              JSObject *p, JSAtom atom, int prop_flags)
+{
+    JSRuntime *rt = ctx->rt;
+    JSShape *sh = *psh;
+    JSShapeProperty *pr, *prop;
+    uint32_t hash_mask, new_shape_hash = 0;
+    intptr_t h;
+
+    /* update the shape hash */
+    if (sh->is_hashed) {
+        js_shape_hash_unlink(rt, sh);
+        new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags);
+    }
+
+    if (unlikely(sh->prop_count >= sh->prop_size)) {
+        if (resize_properties(ctx, psh, p, sh->prop_count + 1)) {
+            /* in case of error, reinsert in the hash table.
+               sh is still valid if resize_properties() failed */
+            if (sh->is_hashed)
+                js_shape_hash_link(rt, sh);
+            return -1;
+        }
+        sh = *psh;
+    }
+    if (sh->is_hashed) {
+        sh->hash = new_shape_hash;
+        js_shape_hash_link(rt, sh);
+    }
+    /* Initialize the new shape property.
+       The object property at p->prop[sh->prop_count] is uninitialized */
+    prop = get_shape_prop(sh);
+    pr = &prop[sh->prop_count++];
+    pr->atom = JS_DupAtom(ctx, atom);
+    pr->flags = prop_flags;
+    sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom);
+    /* add in hash table */
+    hash_mask = sh->prop_hash_mask;
+    h = atom & hash_mask;
+    pr->hash_next = sh->prop_hash_end[-h - 1];
+    sh->prop_hash_end[-h - 1] = sh->prop_count;
+    return 0;
+}
+
+/* find a hashed empty shape matching the prototype. Return NULL if
+   not found */
+static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto)
+{
+    JSShape *sh1;
+    uint32_t h, h1;
+
+    h = shape_initial_hash(proto);
+    h1 = get_shape_hash(h, rt->shape_hash_bits);
+    for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
+        if (sh1->hash == h &&
+            sh1->proto == proto &&
+            sh1->prop_count == 0) {
+            return sh1;
+        }
+    }
+    return NULL;
+}
+
+/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if
+   not found */
+static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh,
+                                       JSAtom atom, int prop_flags)
+{
+    JSShape *sh1;
+    uint32_t h, h1, i, n;
+
+    h = sh->hash;
+    h = shape_hash(h, atom);
+    h = shape_hash(h, prop_flags);
+    h1 = get_shape_hash(h, rt->shape_hash_bits);
+    for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
+        /* we test the hash first so that the rest is done only if the
+           shapes really match */
+        if (sh1->hash == h &&
+            sh1->proto == sh->proto &&
+            sh1->prop_count == ((n = sh->prop_count) + 1)) {
+            for(i = 0; i < n; i++) {
+                if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) ||
+                    unlikely(sh1->prop[i].flags != sh->prop[i].flags))
+                    goto next;
+            }
+            if (unlikely(sh1->prop[n].atom != atom) ||
+                unlikely(sh1->prop[n].flags != prop_flags))
+                goto next;
+            return sh1;
+        }
+    next: ;
+    }
+    return NULL;
+}
+
+static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh)
+{
+    char atom_buf[ATOM_GET_STR_BUF_SIZE];
+    int j;
+
+    /* XXX: should output readable class prototype */
+    printf("%5d %3d%c %14p %5d %5d", i,
+           sh->header.ref_count, " *"[sh->is_hashed],
+           (void *)sh->proto, sh->prop_size, sh->prop_count);
+    for(j = 0; j < sh->prop_count; j++) {
+        printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf),
+                                      sh->prop[j].atom));
+    }
+    printf("\n");
+}
+
+static __maybe_unused void JS_DumpShapes(JSRuntime *rt)
+{
+    int i;
+    JSShape *sh;
+    struct list_head *el;
+    JSObject *p;
+
+    printf("JSShapes: {\n");
+    printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS");
+    for(i = 0; i < rt->shape_hash_size; i++) {
+        for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) {
+            JS_DumpShape(rt, i, sh);
+            assert(sh->is_hashed);
+        }
+    }
+    /* dump non-hashed shapes */
+    list_for_each(el, &rt->obj_list) {
+        p = list_entry(el, JSObject, link);
+        if (!p->shape->is_hashed) {
+            JS_DumpShape(rt, -1, p->shape);
+        }
+    }
+    printf("}\n");
+}
+
+static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id)
+{
+    JSObject *p;
+
+    js_trigger_gc(ctx->rt, sizeof(JSObject));
+    p = js_malloc(ctx, sizeof(JSObject));
+    if (unlikely(!p))
+        goto fail;
+    p->header.ref_count = 1;
+    p->gc_header.mark = 0;
+    p->class_id = class_id;
+    p->extensible = TRUE;
+    p->free_mark = 0;
+    p->is_exotic = 0;
+    p->fast_array = 0;
+    p->is_constructor = 0;
+    p->is_uncatchable_error = 0;
+    p->is_class = 0;
+    p->tmp_mark = 0;
+    p->first_weak_ref = NULL;
+    p->u.opaque = NULL;
+    p->shape = sh;
+    p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
+    if (unlikely(!p->prop)) {
+        js_free(ctx, p);
+    fail:
+        js_free_shape(ctx->rt, sh);
+        return JS_EXCEPTION;
+    }
+
+    switch(class_id) {
+    case JS_CLASS_OBJECT:
+        break;
+    case JS_CLASS_ARRAY:
+        {
+            JSProperty *pr;
+            p->is_exotic = 1;
+            p->fast_array = 1;
+            p->u.array.u.values = NULL;
+            p->u.array.count = 0;
+            p->u.array.u1.size = 0;
+            /* the length property is always the first one */
+            if (likely(sh == ctx->array_shape)) {
+                pr = &p->prop[0];
+            } else {
+                /* only used for the first array */
+                /* cannot fail */
+                pr = add_property(ctx, p, JS_ATOM_length,
+                                  JS_PROP_WRITABLE | JS_PROP_LENGTH);
+            }
+            pr->u.value = JS_NewInt32(ctx, 0);
+        }
+        break;
+    case JS_CLASS_C_FUNCTION:
+        p->prop[0].u.value = JS_UNDEFINED;
+        break;
+    case JS_CLASS_ARGUMENTS:
+    case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_FLOAT64_ARRAY:
+        p->is_exotic = 1;
+        p->fast_array = 1;
+        p->u.array.u.ptr = NULL;
+        p->u.array.count = 0;
+        break;
+    case JS_CLASS_DATAVIEW:
+        p->u.array.u.ptr = NULL;
+        p->u.array.count = 0;
+        break;
+    case JS_CLASS_NUMBER:
+    case JS_CLASS_STRING:
+    case JS_CLASS_BOOLEAN:
+    case JS_CLASS_SYMBOL:
+    case JS_CLASS_DATE:
+#ifdef CONFIG_BIGNUM
+    case JS_CLASS_BIG_INT:
+    case JS_CLASS_BIG_FLOAT:
+#endif
+        p->u.object_data = JS_UNDEFINED;
+        goto set_exotic;
+    case JS_CLASS_REGEXP:
+        p->u.regexp.pattern = NULL;
+        p->u.regexp.bytecode = NULL;
+        goto set_exotic;
+    default:
+    set_exotic:
+        if (ctx->rt->class_array[class_id].exotic) {
+            p->is_exotic = 1;
+        }
+        break;
+    }
+    list_add_tail(&p->link, &ctx->rt->obj_list);
+    return JS_MKPTR(JS_TAG_OBJECT, p);
+}
+
+static JSObject *get_proto_obj(JSValueConst proto_val)
+{
+    if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT)
+        return NULL;
+    else
+        return JS_VALUE_GET_OBJ(proto_val);
+}
+
+/* WARNING: proto must be an object or JS_NULL */
+JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val,
+                               JSClassID class_id)
+{
+    JSShape *sh;
+    JSObject *proto;
+
+    proto = get_proto_obj(proto_val);
+    sh = find_hashed_shape_proto(ctx->rt, proto);
+    if (likely(sh)) {
+        sh = js_dup_shape(sh);
+    } else {
+        sh = js_new_shape(ctx, proto);
+        if (!sh)
+            return JS_EXCEPTION;
+    }
+    return JS_NewObjectFromShape(ctx, sh, class_id);
+}
+
+#if 0
+static JSValue JS_GetObjectData(JSContext *ctx, JSValueConst obj)
+{
+    JSObject *p;
+
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        p = JS_VALUE_GET_OBJ(obj);
+        switch(p->class_id) {
+        case JS_CLASS_NUMBER:
+        case JS_CLASS_STRING:
+        case JS_CLASS_BOOLEAN:
+        case JS_CLASS_SYMBOL:
+        case JS_CLASS_DATE:
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT:
+        case JS_CLASS_BIG_FLOAT:
+#endif
+            return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_UNDEFINED;
+}
+#endif
+
+static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val)
+{
+    JSObject *p;
+
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        p = JS_VALUE_GET_OBJ(obj);
+        switch(p->class_id) {
+        case JS_CLASS_NUMBER:
+        case JS_CLASS_STRING:
+        case JS_CLASS_BOOLEAN:
+        case JS_CLASS_SYMBOL:
+        case JS_CLASS_DATE:
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT:
+        case JS_CLASS_BIG_FLOAT:
+#endif
+            JS_FreeValue(ctx, p->u.object_data);
+            p->u.object_data = val;
+            return 0;
+        }
+    }
+    JS_FreeValue(ctx, val);
+    if (!JS_IsException(obj))
+        JS_ThrowTypeError(ctx, "invalid object type");
+    return -1;
+}
+
+JSValue JS_NewObjectClass(JSContext *ctx, int class_id)
+{
+    return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id);
+}
+
+JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto)
+{
+    return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT);
+}
+
+JSValue JS_NewArray(JSContext *ctx)
+{
+    return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape),
+                                 JS_CLASS_ARRAY);
+}
+
+JSValue JS_NewObject(JSContext *ctx)
+{
+    /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */
+    return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT);
+}
+
+static void js_function_set_properties(JSContext *ctx, JSValueConst func_obj,
+                                       JSAtom name, int len)
+{
+    /* ES6 feature non compatible with ES5.1: length is configurable */
+    JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, JS_NewInt32(ctx, len),
+                           JS_PROP_CONFIGURABLE);
+    JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name,
+                           JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE);
+}
+
+static BOOL js_class_has_bytecode(JSClassID class_id)
+{
+    return (class_id == JS_CLASS_BYTECODE_FUNCTION ||
+            class_id == JS_CLASS_GENERATOR_FUNCTION ||
+            class_id == JS_CLASS_ASYNC_FUNCTION ||
+            class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION);
+}
+
+/* return NULL without exception if not a function or no bytecode */
+static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return NULL;
+    p = JS_VALUE_GET_OBJ(val);
+    if (!js_class_has_bytecode(p->class_id))
+        return NULL;
+    return p->u.func.function_bytecode;
+}
+
+static void js_method_set_home_object(JSContext *ctx, JSValueConst func_obj,
+                                      JSValueConst home_obj)
+{
+    JSObject *p, *p1;
+    JSFunctionBytecode *b;
+
+    if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)
+        return;
+    p = JS_VALUE_GET_OBJ(func_obj);
+    if (!js_class_has_bytecode(p->class_id))
+        return;
+    b = p->u.func.function_bytecode;
+    if (b->need_home_object) {
+        p1 = p->u.func.home_object;
+        if (p1) {
+            JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1));
+        }
+        if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT)
+            p1 = JS_VALUE_GET_OBJ(JS_DupValue(ctx, home_obj));
+        else
+            p1 = NULL;
+        p->u.func.home_object = p1;
+    }
+}
+
+static JSValue js_get_function_name(JSContext *ctx, JSAtom name)
+{
+    JSValue name_str;
+
+    name_str = JS_AtomToString(ctx, name);
+    if (JS_AtomSymbolHasDescription(ctx, name)) {
+        name_str = JS_ConcatString3(ctx, "[", name_str, "]");
+    }
+    return name_str;
+}
+
+/* Modify the name of a method according to the atom and
+   'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and
+   JS_PROP_HAS_SET. Also set the home object of the method.
+   Return < 0 if exception. */
+static int js_method_set_properties(JSContext *ctx, JSValueConst func_obj,
+                                    JSAtom name, int flags, JSValueConst home_obj)
+{
+    JSValue name_str;
+
+    name_str = js_get_function_name(ctx, name);
+    if (flags & JS_PROP_HAS_GET) {
+        name_str = JS_ConcatString3(ctx, "get ", name_str, "");
+    } else if (flags & JS_PROP_HAS_SET) {
+        name_str = JS_ConcatString3(ctx, "set ", name_str, "");
+    }
+    if (JS_IsException(name_str))
+        return -1;
+    if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str,
+                               JS_PROP_CONFIGURABLE) < 0)
+        return -1;
+    js_method_set_home_object(ctx, func_obj, home_obj);
+    return 0;
+}
+
+/* Note: at least 'length' arguments will be readable in 'argv' */
+static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func,
+                                const char *name,
+                                int length, JSCFunctionEnum cproto, int magic,
+                                JSValueConst proto_val)
+{
+    JSValue func_obj;
+    JSObject *p;
+    JSAtom name_atom;
+    
+    func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION);
+    if (JS_IsException(func_obj))
+        return func_obj;
+    p = JS_VALUE_GET_OBJ(func_obj);
+    p->u.cfunc.c_function.generic = func;
+    p->u.cfunc.length = length;
+    p->u.cfunc.cproto = cproto;
+    p->u.cfunc.magic = magic;
+    p->is_constructor = (cproto == JS_CFUNC_constructor ||
+                         cproto == JS_CFUNC_constructor_magic ||
+                         cproto == JS_CFUNC_constructor_or_func ||
+                         cproto == JS_CFUNC_constructor_or_func_magic);
+    if (!name)
+        name = "";
+    name_atom = JS_NewAtom(ctx, name);
+    js_function_set_properties(ctx, func_obj, name_atom, length);
+    JS_FreeAtom(ctx, name_atom);
+    return func_obj;
+}
+
+/* Note: at least 'length' arguments will be readable in 'argv' */
+JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func,
+                         const char *name,
+                         int length, JSCFunctionEnum cproto, int magic)
+{
+    return JS_NewCFunction3(ctx, func, name, length, cproto, magic,
+                            ctx->function_proto);
+}
+
+typedef struct JSCFunctionDataRecord {
+    JSCFunctionData *func;
+    uint8_t length;
+    uint8_t data_len;
+    uint16_t magic;
+    JSValue data[0];
+} JSCFunctionDataRecord;
+
+static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA);
+    int i;
+
+    if (s) {
+        for(i = 0; i < s->data_len; i++) {
+            JS_FreeValueRT(rt, s->data[i]);
+        }
+        js_free_rt(rt, s);
+    }
+}
+
+static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val,
+                                    JS_MarkFunc *mark_func)
+{
+    JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA);
+    int i;
+
+    if (s) {
+        for(i = 0; i < s->data_len; i++) {
+            JS_MarkValue(rt, s->data[i], mark_func);
+        }
+    }
+}
+
+static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj,
+                                       JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA);
+    JSValueConst *arg_buf;
+    int i;
+
+    /* XXX: could add the function on the stack for debug */
+    if (unlikely(argc < s->length)) {
+        arg_buf = alloca(sizeof(arg_buf[0]) * s->length);
+        for(i = 0; i < argc; i++)
+            arg_buf[i] = argv[i];
+        for(i = argc; i < s->length; i++)
+            arg_buf[i] = JS_UNDEFINED;
+    } else {
+        arg_buf = argv;
+    }
+
+    return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data);
+}
+
+JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func,
+                            int length, int magic, int data_len,
+                            JSValueConst *data)
+{
+    JSCFunctionDataRecord *s;
+    JSValue func_obj;
+    int i;
+
+    func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
+                                      JS_CLASS_C_FUNCTION_DATA);
+    if (JS_IsException(func_obj))
+        return func_obj;
+    s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue));
+    if (!s) {
+        JS_FreeValue(ctx, func_obj);
+        return JS_EXCEPTION;
+    }
+    s->func = func;
+    s->length = length;
+    s->data_len = data_len;
+    s->magic = magic;
+    for(i = 0; i < data_len; i++)
+        s->data[i] = JS_DupValue(ctx, data[i]);
+    JS_SetOpaque(func_obj, s);
+    js_function_set_properties(ctx, func_obj,
+                               JS_ATOM_empty_string, length);
+    return func_obj;
+}
+
+static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags)
+{
+    if (unlikely(prop_flags & JS_PROP_TMASK)) {
+        if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+            if (pr->u.getset.getter)
+                JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
+            if (pr->u.getset.setter)
+                JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
+        } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+            free_var_ref(rt, pr->u.var_ref);
+        } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+            /* nothing to do */
+        }
+    } else {
+        JS_FreeValueRT(rt, pr->u.value);
+    }
+}
+
+static force_inline JSShapeProperty *find_own_property1(JSObject *p,
+                                                        JSAtom atom)
+{
+    JSShape *sh;
+    JSShapeProperty *pr, *prop;
+    intptr_t h;
+    sh = p->shape;
+    h = (uintptr_t)atom & sh->prop_hash_mask;
+    h = sh->prop_hash_end[-h - 1];
+    prop = get_shape_prop(sh);
+    while (h) {
+        pr = &prop[h - 1];
+        if (likely(pr->atom == atom)) {
+            return pr;
+        }
+        h = pr->hash_next;
+    }
+    return NULL;
+}
+
+static force_inline JSShapeProperty *find_own_property(JSProperty **ppr,
+                                                       JSObject *p,
+                                                       JSAtom atom)
+{
+    JSShape *sh;
+    JSShapeProperty *pr, *prop;
+    intptr_t h;
+    sh = p->shape;
+    h = (uintptr_t)atom & sh->prop_hash_mask;
+    h = sh->prop_hash_end[-h - 1];
+    prop = get_shape_prop(sh);
+    while (h) {
+        pr = &prop[h - 1];
+        if (likely(pr->atom == atom)) {
+            *ppr = &p->prop[h - 1];
+            /* the compiler should be able to assume that pr != NULL here */
+            return pr;
+        }
+        h = pr->hash_next;
+    }
+    *ppr = NULL;
+    return NULL;
+}
+
+/* indicate that the object may be part of a function prototype cycle */
+static void set_cycle_flag(JSContext *ctx, JSValueConst obj)
+{
+}
+
+static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref)
+{
+    if (var_ref) {
+        assert(var_ref->header.ref_count > 0);
+        if (--var_ref->header.ref_count == 0) {
+            if (var_ref->link.prev != NULL) {
+                list_del(&var_ref->link); /* still on the stack */
+            } else {
+                JS_FreeValueRT(rt, var_ref->value);
+            }
+            js_free_rt(rt, var_ref);
+        }
+    }
+}
+
+static void js_array_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    int i;
+
+    for(i = 0; i < p->u.array.count; i++) {
+        JS_FreeValueRT(rt, p->u.array.u.values[i]);
+    }
+    js_free_rt(rt, p->u.array.u.values);
+}
+
+static void js_array_mark(JSRuntime *rt, JSValueConst val,
+                          JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    int i;
+
+    for(i = 0; i < p->u.array.count; i++) {
+        JS_MarkValue(rt, p->u.array.u.values[i], mark_func);
+    }
+}
+
+static void js_object_data_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JS_FreeValueRT(rt, p->u.object_data);
+    p->u.object_data = JS_UNDEFINED;
+}
+
+static void js_object_data_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JS_MarkValue(rt, p->u.object_data, mark_func);
+}
+
+static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p1, *p = JS_VALUE_GET_OBJ(val);
+    JSFunctionBytecode *b;
+    JSVarRef **var_refs;
+    int i;
+
+    p1 = p->u.func.home_object;
+    if (p1) {
+        JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1));
+    }
+    b = p->u.func.function_bytecode;
+    if (b) {
+        var_refs = p->u.func.var_refs;
+        if (var_refs) {
+            for(i = 0; i < b->closure_var_count; i++)
+                free_var_ref(rt, var_refs[i]);
+            js_free_rt(rt, var_refs);
+        }
+        JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b));
+    }
+}
+
+static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val,
+                                      JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSVarRef **var_refs = p->u.func.var_refs;
+    JSFunctionBytecode *b = p->u.func.function_bytecode;
+    int i;
+
+    if (p->u.func.home_object) {
+        JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object),
+                     mark_func);
+    }
+    if (b) {
+        if (var_refs) {
+            for(i = 0; i < b->closure_var_count; i++) {
+                JSVarRef *var_ref = var_refs[i];
+                if (var_ref) {
+                    JS_MarkValue(rt, JS_MKPTR(JS_TAG_VAR_REF, var_ref),
+                                 mark_func);
+                }
+            }
+        }
+        /* must mark the function bytecode because template objects may be
+           part of a cycle */
+        JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func);
+    }
+}
+
+static void js_bound_function_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSBoundFunction *bf = p->u.bound_function;
+    int i;
+
+    JS_FreeValueRT(rt, bf->func_obj);
+    JS_FreeValueRT(rt, bf->this_val);
+    for(i = 0; i < bf->argc; i++) {
+        JS_FreeValueRT(rt, bf->argv[i]);
+    }
+    js_free_rt(rt, bf);
+}
+
+static void js_bound_function_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSBoundFunction *bf = p->u.bound_function;
+    int i;
+
+    JS_MarkValue(rt, bf->func_obj, mark_func);
+    JS_MarkValue(rt, bf->this_val, mark_func);
+    for(i = 0; i < bf->argc; i++)
+        JS_MarkValue(rt, bf->argv[i], mark_func);
+}
+
+static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSForInIterator *it = p->u.for_in_iterator;
+    JS_FreeValueRT(rt, it->obj);
+    js_free_rt(rt, it);
+}
+
+static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSForInIterator *it = p->u.for_in_iterator;
+    JS_MarkValue(rt, it->obj, mark_func);
+}
+
+static void free_object_struct(JSRuntime *rt, JSObject *p)
+{
+    int i;
+    JSClassFinalizer *finalizer;
+    JSShape *sh;
+    JSShapeProperty *pr;
+
+    /* free all the fields */
+    sh = p->shape;
+    pr = get_shape_prop(sh);
+    for(i = 0; i < sh->prop_count; i++) {
+        free_property(rt, &p->prop[i], pr->flags);
+        pr++;
+    }
+    js_free_rt(rt, p->prop);
+    js_free_shape(rt, sh);
+
+    /* fail safe */
+    p->shape = NULL;
+    p->prop = NULL;
+
+    if (unlikely(p->first_weak_ref)) {
+        reset_weak_ref(rt, p);
+    }
+
+    finalizer = rt->class_array[p->class_id].finalizer;
+    if (finalizer)
+        (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p));
+
+    /* fail safe */
+    p->class_id = 0;
+    p->u.opaque = NULL;
+    p->u.func.var_refs = NULL;
+    p->u.func.home_object = NULL;
+}
+
+static void free_object2(JSRuntime *rt, JSObject *p)
+{
+    if (p->free_mark) {
+        /* already freed (happen when freeing cycles) */
+        return;
+    }
+    /* indicate that the object is being freed */
+    p->free_mark = 1;
+
+    free_object_struct(rt, p);
+
+    /* remove from the global object list */
+    list_del(&p->link);
+    if (rt->in_gc_sweep) {
+        list_add_tail(&p->link, &rt->free_obj_list);
+    } else {
+        js_free_rt(rt, p);
+    }
+}
+
+static void free_object(JSRuntime *rt, JSObject *p)
+{
+    assert(p->header.ref_count == 0);
+    if (!rt->in_gc_sweep)
+        free_object2(rt, p);
+}
+
+/* called with the ref_count of 'v' reaches zero. */
+void __JS_FreeValueRT(JSRuntime *rt, JSValue v)
+{
+    uint32_t tag = JS_VALUE_GET_TAG(v);
+
+#ifdef DUMP_FREE
+    {
+        printf("Freeing ");
+        if (tag == JS_TAG_OBJECT) {
+            JS_DumpObject(rt, JS_VALUE_GET_OBJ(v));
+        } else {
+            JS_DumpValueShort(rt, v);
+            printf("\n");
+        }
+    }
+#endif
+
+    switch(tag) {
+    case JS_TAG_STRING:
+        {
+            JSString *p = JS_VALUE_GET_STRING(v);
+            if (p->atom_type) {
+                JS_FreeAtomStruct(rt, p);
+            } else {
+#ifdef DUMP_LEAKS
+                list_del(&p->link);
+#endif
+                js_free_rt(rt, p);
+            }
+        }
+        break;
+    case JS_TAG_OBJECT:
+        free_object(rt, JS_VALUE_GET_OBJ(v));
+        break;
+    case JS_TAG_FUNCTION_BYTECODE:
+        free_function_bytecode(rt, JS_VALUE_GET_PTR(v));
+        break;
+    case JS_TAG_SHAPE:
+    case JS_TAG_ASYNC_FUNCTION:
+    case JS_TAG_VAR_REF:
+    case JS_TAG_MODULE:
+        abort(); /* never freed here */
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *bf = JS_VALUE_GET_PTR(v);
+            bf_delete(&bf->num);
+            js_free_rt(rt, bf);
+        }
+        break;
+#endif
+    case JS_TAG_SYMBOL:
+        {
+            JSAtomStruct *p = JS_VALUE_GET_PTR(v);
+            JS_FreeAtomStruct(rt, p);
+        }
+        break;
+    default:
+        printf("__JS_FreeValue: unknown tag=%d\n", tag);
+        abort();
+    }
+}
+
+void __JS_FreeValue(JSContext *ctx, JSValue v)
+{
+    __JS_FreeValueRT(ctx->rt, v);
+}
+
+/* garbage collection */
+
+static BOOL has_children(JSValueConst val)
+{
+    switch(JS_VALUE_GET_TAG(val)) {
+    case JS_TAG_VAR_REF:
+    case JS_TAG_OBJECT:
+    case JS_TAG_FUNCTION_BYTECODE:
+    case JS_TAG_ASYNC_FUNCTION:
+    case JS_TAG_SHAPE:
+        return TRUE;
+    default:
+        return FALSE;
+    }
+}
+
+void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
+{
+    if (JS_VALUE_HAS_REF_COUNT(val) && has_children(val)) {
+        mark_func(rt, val);
+    }
+}
+
+static void mark_children(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
+{
+    switch(JS_VALUE_GET_TAG(val)) {
+    case JS_TAG_OBJECT:
+        {
+            JSObject *p = JS_VALUE_GET_OBJ(val);
+            JSShapeProperty *prs;
+            JSShape *sh;
+            int i;
+            sh = p->shape;
+            mark_func(rt, JS_MKPTR(JS_TAG_SHAPE, sh));
+            /* mark all the fields */
+            prs = get_shape_prop(sh);
+            for(i = 0; i < sh->prop_count; i++) {
+                JSProperty *pr = &p->prop[i];
+                if (prs->atom != JS_ATOM_NULL) {
+                    if (prs->flags & JS_PROP_TMASK) {
+                        if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+                            if (pr->u.getset.getter)
+                                mark_func(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
+                            if (pr->u.getset.setter)
+                                mark_func(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
+                        } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                            JS_MarkValue(rt, JS_MKPTR(JS_TAG_VAR_REF, pr->u.var_ref), mark_func);
+                        } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                            /* nothing to do */
+                        }
+                    } else {
+                        JS_MarkValue(rt, pr->u.value, mark_func);
+                    }
+                }
+                prs++;
+            }
+
+            if (p->class_id != JS_CLASS_OBJECT) {
+                JSClassGCMark *gc_mark;
+                gc_mark = rt->class_array[p->class_id].gc_mark;
+                if (gc_mark)
+                    gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func);
+            }
+        }
+        break;
+    case JS_TAG_FUNCTION_BYTECODE:
+        /* the template objects can be part of a cycle */
+        {
+            JSFunctionBytecode *b = JS_VALUE_GET_PTR(val);
+            int i;
+            for(i = 0; i < b->cpool_count; i++) {
+                JS_MarkValue(rt, b->cpool[i], mark_func);
+            }
+        }
+        break;
+    case JS_TAG_VAR_REF:
+        {
+            JSVarRef *var_ref = JS_VALUE_GET_PTR(val);
+            /* the refcount of stack values is not incremented, hence
+               the test */
+            if (var_ref->link.prev == NULL) {
+                JS_MarkValue(rt, *var_ref->pvalue, mark_func);
+            }
+        }
+        break;
+    case JS_TAG_ASYNC_FUNCTION:
+        {
+            JSAsyncFunctionData *s = JS_VALUE_GET_PTR(val);
+            if (s->is_active)
+                async_func_mark(rt, &s->func_state, mark_func);
+            JS_MarkValue(rt, s->resolving_funcs[0], mark_func);
+            JS_MarkValue(rt, s->resolving_funcs[1], mark_func);
+        }
+        break;
+    case JS_TAG_SHAPE:
+        {
+            JSShape *sh = JS_VALUE_GET_PTR(val);
+            if (sh->proto != NULL) {
+                mark_func(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
+            }
+        }
+        break;
+    default:
+        /* no children */
+        break;
+    }
+}
+
+#if 0
+/* not useful until realms are supported */
+static void mark_context(JSRuntime *rt, JSContext *ctx)
+{
+    int i;
+    struct list_head *el;
+
+    list_for_each(el, &ctx->loaded_modules) {
+        JSModuleDef *m = list_entry(el, JSModuleDef, link);
+        JS_MarkValue(rt, m->module_ns);
+        JS_MarkValue(rt, m->func_obj);
+    }
+
+    JS_MarkValue(rt, ctx->current_exception);
+
+    for(i = 0; i < rt->class_count; i++)
+        JS_MarkValue(rt, ctx->class_proto[i]);
+    JS_MarkValue(rt, ctx->regexp_ctor);
+    JS_MarkValue(rt, ctx->function_ctor);
+    JS_MarkValue(rt, ctx->function_proto);
+    JS_MarkValue(rt, ctx->iterator_proto);
+    JS_MarkValue(rt, ctx->async_iterator_proto);
+    JS_MarkValue(rt, ctx->array_proto_values);
+
+    for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++)
+        JS_MarkValue(rt, ctx->native_error_proto[i]);
+
+    JS_MarkValue(rt, ctx->throw_type_error);
+    JS_MarkValue(rt, ctx->eval_obj);
+    JS_MarkValue(rt, ctx->global_obj);
+    JS_MarkValue(rt, ctx->global_var_obj);
+}
+#endif
+
+/* 1 = possible member of a cycle
+   2 = member of a cycle
+*/
+
+static void gc_decref_mark(JSRuntime *rt, JSValueConst obj);
+
+//#define DUMP_GC_DECREF
+
+#ifdef DUMP_GC_DECREF
+static int decref_indent;
+#endif
+
+static void gc_decref_child(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+
+#ifdef DUMP_LEAKS
+#ifdef DUMP_GC_DECREF
+    {
+        int i;
+        for(i = 0; i < decref_indent; i++)
+                printf("  ");
+        if (JS_IsObject(obj)) {
+            JS_DumpObject(rt, p);
+        } else {
+            JS_DumpValueShort(rt, obj); printf("\n");
+        }
+    }
+#endif
+    if (p->header.ref_count <= 0) {
+        printf("%p: invalid refcount (%d)\n", p, p->header.ref_count);
+        if (JS_IsObject(obj)) {
+            JS_DumpObject(rt, p);
+        } else {
+            JS_DumpValueShort(rt, obj);
+        }
+    }
+#endif
+    assert(p->header.ref_count > 0);
+    p->header.ref_count--;
+    gc_decref_mark(rt, obj);
+}
+
+static void gc_decref_mark(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+    if (p->gc_header.mark == 0) {
+        p->gc_header.mark = 1;
+#ifdef DUMP_GC_DECREF
+        decref_indent++;
+#endif
+        mark_children(rt, obj, gc_decref_child);
+#ifdef DUMP_GC_DECREF
+        decref_indent--;
+#endif
+    }
+}
+
+static void gc_decref(JSRuntime *rt)
+{
+    struct list_head *el;
+    JSObject *p;
+
+    list_for_each(el, &rt->obj_list) {
+        p = list_entry(el, JSObject, link);
+        gc_decref_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p));
+    }
+}
+
+static void gc_scan_incref(JSRuntime *rt, JSValueConst obj);
+
+static void gc_scan_incref_child(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+    p->header.ref_count++;
+    if (p->gc_header.mark != 0) {
+        gc_scan_incref(rt, obj);
+    }
+}
+
+static void gc_scan_incref(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+    p->gc_header.mark = 0; /* not freed */
+    if (JS_IsObject(obj)) {
+        /* remove the tmp_obj_list and update the next object to
+           explore */
+        if (rt->el_next == &p->link)
+            rt->el_next = rt->el_next->next;
+        /* add to rt->obj_list */
+        list_del(&p->link);
+        list_add_tail(&p->link, &rt->obj_list);
+    }
+    mark_children(rt, obj, gc_scan_incref_child);
+}
+
+static void gc_scan_obj(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+    if (p->gc_header.mark == 1) {
+        if (p->header.ref_count > 0) {
+            gc_scan_incref(rt, obj);
+        } else {
+            p->gc_header.mark = 2;
+            mark_children(rt, obj, gc_scan_obj);
+        }
+    }
+}
+
+static void gc_scan_obj2(JSRuntime *rt, JSValueConst obj);
+
+static void gc_scan_incref_child2(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+    p->header.ref_count++;
+    gc_scan_obj2(rt, obj);
+}
+
+static void gc_scan_obj2(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(obj);
+    if (p->gc_header.mark == 2) {
+        p->gc_header.mark = 3;
+        mark_children(rt, obj, gc_scan_incref_child2);
+    }
+}
+
+static void gc_scan(JSRuntime *rt)
+{
+    struct list_head *el;
+    JSObject *p;
+
+    /* move obj_list to tmp_obj_list */
+    list_add(&rt->tmp_obj_list, &rt->obj_list);
+    list_del(&rt->obj_list);
+    init_list_head(&rt->obj_list);
+
+    /* keep the objects with a refcount > 0 and their children. After
+       this pass, obj_list contains the objects to be deleted. Their
+       mark is 2. */
+    for(el = rt->tmp_obj_list.next; el != &rt->tmp_obj_list;
+        el = rt->el_next) {
+        rt->el_next = el->next; /* may be modified by gc_scan_obj() */
+        p = list_entry(el, JSObject, link);
+        gc_scan_obj(rt, JS_MKPTR(JS_TAG_OBJECT, p));
+    }
+
+    /* restore the refcount of the objects to be deleted. After this
+       pass, their mark is 3 */
+    list_for_each(el, &rt->tmp_obj_list) {
+        p = list_entry(el, JSObject, link);
+        gc_scan_obj2(rt, JS_MKPTR(JS_TAG_OBJECT, p));
+    }
+}
+
+static void gc_free_cycles(JSRuntime *rt)
+{
+    struct list_head *el, *el1;
+    JSObject *p;
+#ifdef DUMP_GC_FREE
+    BOOL header_done = FALSE;
+#endif
+    init_list_head(&rt->free_obj_list);
+    rt->in_gc_sweep = TRUE;
+    list_for_each_safe(el, el1, &rt->tmp_obj_list) {
+        p = list_entry(el, JSObject, link);
+        assert(p->gc_header.mark == 3);
+#ifdef DUMP_GC_FREE
+        if (!header_done) {
+            printf("Freeing cycles:\n");
+            JS_DumpObjectHeader(rt);
+            header_done = TRUE;
+        }
+        JS_DumpObject(rt, p);
+#endif
+        free_object2(rt, p);
+    }
+    rt->in_gc_sweep = FALSE;
+
+    /* free all the object structures */
+    list_for_each_safe(el, el1, &rt->free_obj_list) {
+        p = list_entry(el, JSObject, link);
+        js_free_rt(rt, p);
+    }
+}
+
+void JS_RunGC(JSRuntime *rt)
+{
+    /* decrement the reference of the children of each object. mark =
+       1 after this pass. */
+    gc_decref(rt);
+
+    /* keep objects with a non zero refcount and their childs */
+    gc_scan(rt);
+
+    /* free the objects with a zero refcount */
+    gc_free_cycles(rt);
+}
+
+/* Return false if not an object or if the object has already been
+   freed (zombie objects are visible in finalizers when freeing
+   cycles). */
+BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj)
+{
+    JSObject *p;
+    if (!JS_IsObject(obj))
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(obj);
+    return !p->free_mark;
+}
+
+/* Return true during the GC sweep phase (can be useful inside in finalizer) */
+BOOL JS_IsInGCSweep(JSRuntime *rt)
+{
+    return rt->in_gc_sweep;
+}
+
+/* Compute memory used by various object types */
+/* XXX: poor man's approach to handling multiply referenced objects */
+typedef struct JSMemoryUsage_helper {
+    double memory_used_count;
+    double str_count;
+    double str_size;
+    double js_func_count;
+    double js_func_size;
+    double js_func_code_size;
+    double js_func_pc2line_count;
+    double js_func_pc2line_size;
+} JSMemoryUsage_helper;
+
+static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp);
+
+static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp)
+{
+    if (!str->atom_type) {  /* atoms are handled separately */
+        double s_ref_count = str->header.ref_count;
+        hp->str_count += 1 / s_ref_count;
+        hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) +
+                          1 - str->is_wide_char) / s_ref_count);
+    }
+}
+
+static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp)
+{
+    int memory_used_count, js_func_size, i;
+    double ref_count = b->header.ref_count;
+
+    memory_used_count = 0;
+    js_func_size = offsetof(JSFunctionBytecode, debug);
+    if (b->vardefs) {
+        js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs);
+    }
+    if (b->cpool) {
+        js_func_size += b->cpool_count * sizeof(*b->cpool);
+        for (i = 0; i < b->cpool_count; i++) {
+            JSValueConst val = b->cpool[i];
+            compute_value_size(val, hp);
+        }
+    }
+    if (b->closure_var) {
+        js_func_size += b->closure_var_count * sizeof(*b->closure_var);
+    }
+    if (!b->read_only_bytecode && b->byte_code_buf) {
+        hp->js_func_code_size += b->byte_code_len / ref_count;
+    }
+    if (b->has_debug) {
+        js_func_size += sizeof(*b) - offsetof(JSFunctionBytecode, debug);
+        if (b->debug.source) {
+            memory_used_count++;
+            js_func_size += b->debug.source_len + 1;
+        }
+        if (b->debug.pc2line_len) {
+            memory_used_count++;
+            hp->js_func_pc2line_count += 1 / ref_count;
+            hp->js_func_pc2line_size += b->debug.pc2line_len / ref_count;
+        }
+    }
+    hp->js_func_size += js_func_size / ref_count;
+    hp->js_func_count += 1 / ref_count;
+    hp->memory_used_count += memory_used_count / ref_count;
+}
+
+static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp)
+{
+    switch(JS_VALUE_GET_TAG(val)) {
+    case JS_TAG_STRING:
+        compute_jsstring_size(JS_VALUE_GET_STRING(val), hp);
+        break;
+    case JS_TAG_FUNCTION_BYTECODE:
+        compute_bytecode_size(JS_VALUE_GET_PTR(val), hp);
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+    case JS_TAG_BIG_FLOAT:
+        /* should track JSBigFloat usage */
+        break;
+#endif
+    }
+}
+
+void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s)
+{
+    struct list_head *el, *el1;
+    int i;
+    JSMemoryUsage_helper mem = { 0 }, *hp = &mem;
+
+    memset(s, 0, sizeof(*s));
+    s->malloc_count = rt->malloc_state.malloc_count;
+    s->malloc_size = rt->malloc_state.malloc_size;
+    s->malloc_limit = rt->malloc_state.malloc_limit;
+
+    s->memory_used_count = 2; /* rt + rt->class_array */
+    s->memory_used_size = sizeof(JSRuntime) + sizeof(JSValue) * rt->class_count;
+
+    list_for_each(el, &rt->context_list) {
+        JSContext *ctx = list_entry(el, JSContext, link);
+        JSShape *sh = ctx->array_shape;
+        s->memory_used_count += 2; /* ctx + ctx->class_proto */
+        s->memory_used_size += sizeof(JSContext) +
+            sizeof(JSValue) * rt->class_count;
+        s->binary_object_count += ctx->binary_object_count;
+        s->binary_object_size += ctx->binary_object_size;
+
+        /* the hashed shapes are counted separately */
+        if (sh && !sh->is_hashed) {
+            int hash_size = sh->prop_hash_mask + 1;
+            s->shape_count++;
+            s->shape_size += get_shape_size(hash_size, sh->prop_size);
+        }
+        list_for_each(el1, &ctx->loaded_modules) {
+            JSModuleDef *m = list_entry(el1, JSModuleDef, link);
+            s->memory_used_count += 1;
+            s->memory_used_size += sizeof(*m);
+            if (m->req_module_entries) {
+                s->memory_used_count += 1;
+                s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries);
+            }
+            if (m->export_entries) {
+                s->memory_used_count += 1;
+                s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries);
+                for (i = 0; i < m->export_entries_count; i++) {
+                    JSExportEntry *me = &m->export_entries[i];
+                    if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) {
+                        /* potential multiple count */
+                        s->memory_used_count += 1;
+                        compute_value_size(me->u.local.var_ref->value, hp);
+                    }
+                }
+            }
+            if (m->star_export_entries) {
+                s->memory_used_count += 1;
+                s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries);
+            }
+            if (m->import_entries) {
+                s->memory_used_count += 1;
+                s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries);
+            }
+            compute_value_size(m->module_ns, hp);
+            compute_value_size(m->func_obj, hp);
+        }
+    }
+
+    list_for_each(el, &rt->obj_list) {
+        JSObject *p = list_entry(el, JSObject, link);
+        JSShape *sh = p->shape;
+        JSShapeProperty *prs;
+        s->obj_count++;
+        if (p->prop) {
+            s->memory_used_count++;
+            s->prop_size += sh->prop_size * sizeof(*p->prop);
+            s->prop_count += sh->prop_count;
+            prs = get_shape_prop(sh);
+            for(i = 0; i < sh->prop_count; i++) {
+                JSProperty *pr = &p->prop[i];
+                if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) {
+                    compute_value_size(pr->u.value, hp);
+                }
+                prs++;
+            }
+        }
+        /* the hashed shapes are counted separately */
+        if (!sh->is_hashed) {
+            int hash_size = sh->prop_hash_mask + 1;
+            s->shape_count++;
+            s->shape_size += get_shape_size(hash_size, sh->prop_size);
+        }
+
+        switch(p->class_id) {
+        case JS_CLASS_ARRAY:             /* u.array | length */
+        case JS_CLASS_ARGUMENTS:         /* u.array | length */
+            s->array_count++;
+            if (p->fast_array) {
+                s->fast_array_count++;
+                if (p->u.array.u.values) {
+                    s->memory_used_count++;
+                    s->memory_used_size += p->u.array.count *
+                        sizeof(*p->u.array.u.values);
+                    s->fast_array_elements += p->u.array.count;
+                    for (i = 0; i < p->u.array.count; i++) {
+                        compute_value_size(p->u.array.u.values[i], hp);
+                    }
+                }
+            }
+            break;
+        case JS_CLASS_NUMBER:            /* u.object_data */
+        case JS_CLASS_STRING:            /* u.object_data */
+        case JS_CLASS_BOOLEAN:           /* u.object_data */
+        case JS_CLASS_SYMBOL:            /* u.object_data */
+        case JS_CLASS_DATE:              /* u.object_data */
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT:           /* u.object_data */
+        case JS_CLASS_BIG_FLOAT:         /* u.object_data */
+#endif
+            compute_value_size(p->u.object_data, hp);
+            break;
+        case JS_CLASS_C_FUNCTION:        /* u.cfunc */
+            s->c_func_count++;
+            break;
+        case JS_CLASS_BYTECODE_FUNCTION: /* u.func */
+            {
+                JSFunctionBytecode *b = p->u.func.function_bytecode;
+                JSVarRef **var_refs = p->u.func.var_refs;
+                /* home_object: object will be accounted for in list scan */
+                if (var_refs) {
+                    s->memory_used_count++;
+                    s->js_func_size += b->closure_var_count * sizeof(*var_refs);
+                    for (i = 0; i < b->closure_var_count; i++) {
+                        if (var_refs[i]) {
+                            double ref_count = var_refs[i]->header.ref_count;
+                            s->memory_used_count += 1 / ref_count;
+                            s->js_func_size += sizeof(*var_refs[i]) / ref_count;
+                            /* handle non object closed values */
+                            if (var_refs[i]->pvalue == &var_refs[i]->value) {
+                                /* potential multiple count */
+                                compute_value_size(var_refs[i]->value, hp);
+                            }
+                        }
+                    }
+                }
+                compute_bytecode_size(b, hp);
+            }
+            break;
+        case JS_CLASS_BOUND_FUNCTION:    /* u.bound_function */
+            {
+                JSBoundFunction *bf = p->u.bound_function;
+                /* func_obj and this_val are objects */
+                for (i = 0; i < bf->argc; i++) {
+                    compute_value_size(bf->argv[i], hp);
+                }
+                s->memory_used_count += 1;
+                s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv);
+            }
+            break;
+        case JS_CLASS_C_FUNCTION_DATA:   /* u.c_function_data_record */
+            {
+                JSCFunctionDataRecord *fd = p->u.c_function_data_record;
+                if (fd) {
+                    for (i = 0; i < fd->data_len; i++) {
+                        compute_value_size(fd->data[i], hp);
+                    }
+                    s->memory_used_count += 1;
+                    s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data);
+                }
+            }
+            break;
+        case JS_CLASS_REGEXP:            /* u.regexp */
+            compute_jsstring_size(p->u.regexp.pattern, hp);
+            compute_jsstring_size(p->u.regexp.bytecode, hp);
+            break;
+
+        case JS_CLASS_FOR_IN_ITERATOR:   /* u.for_in_iterator */
+            {
+                JSForInIterator *it = p->u.for_in_iterator;
+                if (it) {
+                    compute_value_size(it->obj, hp);
+                    s->memory_used_count += 1;
+                    s->memory_used_size += sizeof(*it);
+                }
+            }
+            break;
+        case JS_CLASS_ARRAY_BUFFER:      /* u.array_buffer */
+        case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */
+            {
+                JSArrayBuffer *abuf = p->u.array_buffer;
+                if (abuf) {
+                    s->memory_used_count += 1;
+                    s->memory_used_size += sizeof(*abuf);
+                    if (abuf->data) {
+                        s->memory_used_count += 1;
+                        s->memory_used_size += abuf->byte_length;
+                    }
+                }
+            }
+            break;
+        case JS_CLASS_GENERATOR:         /* u.generator_data */
+        case JS_CLASS_UINT8C_ARRAY:      /* u.typed_array / u.array */
+        case JS_CLASS_INT8_ARRAY:        /* u.typed_array / u.array */
+        case JS_CLASS_UINT8_ARRAY:       /* u.typed_array / u.array */
+        case JS_CLASS_INT16_ARRAY:       /* u.typed_array / u.array */
+        case JS_CLASS_UINT16_ARRAY:      /* u.typed_array / u.array */
+        case JS_CLASS_INT32_ARRAY:       /* u.typed_array / u.array */
+        case JS_CLASS_UINT32_ARRAY:      /* u.typed_array / u.array */
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT64_ARRAY:   /* u.typed_array / u.array */
+        case JS_CLASS_BIG_UINT64_ARRAY:  /* u.typed_array / u.array */
+#endif
+        case JS_CLASS_FLOAT32_ARRAY:     /* u.typed_array / u.array */
+        case JS_CLASS_FLOAT64_ARRAY:     /* u.typed_array / u.array */
+        case JS_CLASS_DATAVIEW:          /* u.typed_array */
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_FLOAT_ENV:         /* u.float_env */
+#endif
+        case JS_CLASS_MAP:               /* u.map_state */
+        case JS_CLASS_SET:               /* u.map_state */
+        case JS_CLASS_WEAKMAP:           /* u.map_state */
+        case JS_CLASS_WEAKSET:           /* u.map_state */
+        case JS_CLASS_MAP_ITERATOR:      /* u.map_iterator_data */
+        case JS_CLASS_SET_ITERATOR:      /* u.map_iterator_data */
+        case JS_CLASS_ARRAY_ITERATOR:    /* u.array_iterator_data */
+        case JS_CLASS_STRING_ITERATOR:   /* u.array_iterator_data */
+        case JS_CLASS_PROXY:             /* u.proxy_data */
+        case JS_CLASS_PROMISE:           /* u.promise_data */
+        case JS_CLASS_PROMISE_RESOLVE_FUNCTION:  /* u.promise_function_data */
+        case JS_CLASS_PROMISE_REJECT_FUNCTION:   /* u.promise_function_data */
+        case JS_CLASS_ASYNC_FUNCTION_RESOLVE:    /* u.async_function_data */
+        case JS_CLASS_ASYNC_FUNCTION_REJECT:     /* u.async_function_data */
+        case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR:  /* u.async_from_sync_iterator_data */
+        case JS_CLASS_ASYNC_GENERATOR:   /* u.async_generator_data */
+            /* TODO */
+        default:
+            /* XXX: class definition should have an opaque block size */
+            if (p->u.opaque) {
+                s->memory_used_count += 1;
+            }
+            break;
+        }
+    }
+    s->obj_size += s->obj_count * sizeof(JSObject);
+
+    /* hashed shapes */
+    s->memory_used_count++; /* rt->shape_hash */
+    s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size;
+    for(i = 0; i < rt->shape_hash_size; i++) {
+        JSShape *sh;
+        for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) {
+            int hash_size = sh->prop_hash_mask + 1;
+            s->shape_count++;
+            s->shape_size += get_shape_size(hash_size, sh->prop_size);
+        }
+    }
+
+    /* atoms */
+    s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */
+    s->atom_count = rt->atom_count;
+    s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size +
+        sizeof(rt->atom_hash[0]) * rt->atom_hash_size;
+    for(i = 0; i < rt->atom_size; i++) {
+        JSAtomStruct *p = rt->atom_array[i];
+        if (!atom_is_free(p)) {
+            s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) +
+                             1 - p->is_wide_char);
+        }
+    }
+    s->str_count = round(mem.str_count);
+    s->str_size = round(mem.str_size);
+    s->js_func_count = round(mem.js_func_count);
+    s->js_func_size = round(mem.js_func_size);
+    s->js_func_code_size = round(mem.js_func_code_size);
+    s->js_func_pc2line_count = round(mem.js_func_pc2line_count);
+    s->js_func_pc2line_size = round(mem.js_func_pc2line_size);
+    s->memory_used_count += round(mem.memory_used_count) +
+        s->atom_count + s->str_count +
+        s->obj_count + s->shape_count +
+        s->js_func_count + s->js_func_pc2line_count;
+    s->memory_used_size += s->atom_size + s->str_size +
+        s->obj_size + s->prop_size + s->shape_size +
+        s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size;
+}
+
+void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt)
+{
+    fprintf(fp, "QuickJS memory usage -- "
+#ifdef CONFIG_BIGNUM
+            "BigNum "
+#endif
+            CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n",
+            (int)sizeof(void *) * 8, (int64_t)(ssize_t)s->malloc_limit);
+#if 1
+    if (rt) {
+        static const struct {
+            const char *name;
+            size_t size;
+        } object_types[] = {
+            { "JSRuntime", sizeof(JSRuntime) },
+            { "JSContext", sizeof(JSContext) },
+            { "JSObject", sizeof(JSObject) },
+            { "JSString", sizeof(JSString) },
+            { "JSFunctionBytecode", sizeof(JSFunctionBytecode) },
+        };
+        int i, usage_size_ok = 0;
+        for(i = 0; i < countof(object_types); i++) {
+            unsigned int size = object_types[i].size;
+            void *p = js_malloc_rt(rt, size);
+            if (p) {
+                unsigned int size1 = js_malloc_usable_size_rt(rt, p);
+                if (size1 >= size) {
+                    usage_size_ok = 1;
+                    fprintf(fp, "  %3u + %-2u  %s\n",
+                            size, size1 - size, object_types[i].name);
+                }
+                js_free_rt(rt, p);
+            }
+        }
+        if (!usage_size_ok) {
+            fprintf(fp, "  malloc_usable_size unavailable\n");
+        }
+        {
+            int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 };
+            int class_id;
+            struct list_head *el;
+            list_for_each(el, &rt->obj_list) {
+                JSObject *p = list_entry(el, JSObject, link);
+                obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++;
+            }
+            fprintf(fp, "\n" "JSObject classes\n");
+            if (obj_classes[0])
+                fprintf(fp, "  %5d  %2.0d %s\n", obj_classes[0], 0, "none");
+            for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) {
+                if (obj_classes[class_id]) {
+                    char buf[ATOM_GET_STR_BUF_SIZE];
+                    fprintf(fp, "  %5d  %2.0d %s\n", obj_classes[class_id], class_id,
+                            JS_AtomGetStrRT(rt, buf, sizeof(buf), js_std_class_def[class_id - 1].class_name));
+                }
+            }
+            if (obj_classes[JS_CLASS_INIT_COUNT])
+                fprintf(fp, "  %5d  %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other");
+        }
+        fprintf(fp, "\n");
+    }
+#endif
+    fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE");
+
+    if (s->malloc_count) {
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per block)\n",
+                "memory allocated", s->malloc_count, s->malloc_size,
+                (double)s->malloc_size / s->malloc_count);
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%d overhead, %0.1f average slack)\n",
+                "memory used", s->memory_used_count, s->memory_used_size,
+                MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) /
+                                  s->memory_used_count));
+    }
+    if (s->atom_count) {
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per atom)\n",
+                "atoms", s->atom_count, s->atom_size,
+                (double)s->atom_size / s->atom_count);
+    }
+    if (s->str_count) {
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per string)\n",
+                "strings", s->str_count, s->str_size,
+                (double)s->str_size / s->str_count);
+    }
+    if (s->obj_count) {
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per object)\n",
+                "objects", s->obj_count, s->obj_size,
+                (double)s->obj_size / s->obj_count);
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per object)\n",
+                "  properties", s->prop_count, s->prop_size,
+                (double)s->prop_count / s->obj_count);
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per shape)\n",
+                "  shapes", s->shape_count, s->shape_size,
+                (double)s->shape_size / s->shape_count);
+    }
+    if (s->js_func_count) {
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n",
+                "bytecode functions", s->js_func_count, s->js_func_size);
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per function)\n",
+                "  bytecode", s->js_func_count, s->js_func_code_size,
+                (double)s->js_func_code_size / s->js_func_count);
+        if (s->js_func_pc2line_count) {
+            fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per function)\n",
+                    "  pc2line", s->js_func_pc2line_count,
+                    s->js_func_pc2line_size,
+                    (double)s->js_func_pc2line_size / s->js_func_pc2line_count);
+        }
+    }
+    if (s->c_func_count) {
+        fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count);
+    }
+    if (s->array_count) {
+        fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count);
+        if (s->fast_array_count) {
+            fprintf(fp, "%-20s %8"PRId64"\n", "  fast arrays", s->fast_array_count);
+            fprintf(fp, "%-20s %8"PRId64" %8"PRId64"  (%0.1f per fast array)\n",
+                    "  elements", s->fast_array_elements,
+                    s->fast_array_elements * (int)sizeof(JSValue),
+                    (double)s->fast_array_elements / s->fast_array_count);
+        }
+    }
+    if (s->binary_object_count) {
+        fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n",
+                "binary objects", s->binary_object_count, s->binary_object_size);
+    }
+}
+
+JSValue JS_GetGlobalObject(JSContext *ctx)
+{
+    return JS_DupValue(ctx, ctx->global_obj);
+}
+
+/* WARNING: obj is freed */
+JSValue JS_Throw(JSContext *ctx, JSValue obj)
+{
+    JS_FreeValue(ctx, ctx->current_exception);
+    ctx->current_exception = obj;
+    ctx->exception_needs_backtrace = JS_IsError(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+/* return the pending exception (cannot be called twice). */
+JSValue JS_GetException(JSContext *ctx)
+{
+    JSValue val;
+    val = ctx->current_exception;
+    ctx->current_exception = JS_NULL;
+    ctx->exception_needs_backtrace = FALSE;
+    return val;
+}
+
+static void dbuf_put_leb128(DynBuf *s, uint32_t v)
+{
+    uint32_t a;
+    for(;;) {
+        a = v & 0x7f;
+        v >>= 7;
+        if (v != 0) {
+            dbuf_putc(s, a | 0x80);
+        } else {
+            dbuf_putc(s, a);
+            break;
+        }
+    }
+}
+
+static void dbuf_put_sleb128(DynBuf *s, int32_t v1)
+{
+    uint32_t v = v1;
+    dbuf_put_leb128(s, (2 * v) ^ -(v >> 31));
+}
+
+static int get_leb128(uint32_t *pval, const uint8_t *buf,
+                      const uint8_t *buf_end)
+{
+    const uint8_t *ptr = buf;
+    uint32_t v, a, i;
+    v = 0;
+    for(i = 0; i < 5; i++) {
+        if (unlikely(ptr >= buf_end))
+            break;
+        a = *ptr++;
+        v |= (a & 0x7f) << (i * 7);
+        if (!(a & 0x80)) {
+            *pval = v;
+            return ptr - buf;
+        }
+    }
+    *pval = 0;
+    return -1;
+}
+
+static int get_sleb128(int32_t *pval, const uint8_t *buf,
+                       const uint8_t *buf_end)
+{
+    int ret;
+    uint32_t val;
+    ret = get_leb128(&val, buf, buf_end);
+    if (ret < 0) {
+        *pval = 0;
+        return -1;
+    }
+    *pval = (val >> 1) ^ -(val & 1);
+    return ret;
+}
+
+static int find_line_num(JSContext *ctx, JSFunctionBytecode *b,
+                         uint32_t pc_value)
+{
+    const uint8_t *p_end, *p;
+    int new_line_num, line_num, pc, v, ret;
+    unsigned int op;
+
+    if (!b->has_debug || !b->debug.pc2line_buf) {
+        /* function was stripped */
+        return -1;
+    }
+
+    p = b->debug.pc2line_buf;
+    p_end = p + b->debug.pc2line_len;
+    pc = 0;
+    line_num = b->debug.line_num;
+    while (p < p_end) {
+        op = *p++;
+        if (op == 0) {
+            uint32_t val;
+            ret = get_leb128(&val, p, p_end);
+            if (ret < 0)
+                goto fail;
+            pc += val;
+            p += ret;
+            ret = get_sleb128(&v, p, p_end);
+            if (ret < 0) {
+            fail:
+                /* should never happen */
+                return b->debug.line_num;
+            }
+            p += ret;
+            new_line_num = line_num + v;
+        } else {
+            op -= PC2LINE_OP_FIRST;
+            pc += (op / PC2LINE_RANGE);
+            new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE;
+        }
+        if (pc_value < pc)
+            return line_num;
+        line_num = new_line_num;
+    }
+    return line_num;
+}
+
+/* in order to avoid executing arbitrary code during the stack trace
+   generation, we only look at simple 'name' properties containing a
+   string. */
+static const char *get_func_name(JSContext *ctx, JSValueConst func)
+{
+    JSProperty *pr;
+    JSShapeProperty *prs;
+    JSValueConst val;
+    
+    if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)
+        return NULL;
+    prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name);
+    if (!prs)
+        return NULL;
+    if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL)
+        return NULL;
+    val = pr->u.value;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING)
+        return NULL;
+    return JS_ToCString(ctx, val);
+}
+
+/* if filename != NULL, an additional level is added with the filename
+   and line number information (used for parse error). If 'pc' !=
+   NULL, it is used as the cur_pc value for the current stack frame */
+static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
+                            const char *filename, int line_num,
+                            const uint8_t *cur_pc)
+{
+    JSStackFrame *sf;
+    JSValue str;
+    DynBuf dbuf;
+    const char *func_name_str;
+    const char *str1;
+    JSObject *p;
+
+    ctx->exception_needs_backtrace = FALSE;
+    js_dbuf_init(ctx, &dbuf);
+    if (filename) {
+        dbuf_printf(&dbuf, "    at %s", filename);
+        if (line_num != -1)
+            dbuf_printf(&dbuf, ":%d", line_num);
+        dbuf_putc(&dbuf, '\n');
+        str = JS_NewString(ctx, filename);
+        JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str,
+                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+        JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num),
+                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    }
+    for(sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
+        func_name_str = get_func_name(ctx, sf->cur_func);
+        if (!func_name_str || func_name_str[0] == '\0')
+            str1 = "<anonymous>";
+        else
+            str1 = func_name_str;
+        dbuf_printf(&dbuf, "    at %s", str1);
+        JS_FreeCString(ctx, func_name_str);
+
+        p = JS_VALUE_GET_OBJ(sf->cur_func);
+        if (js_class_has_bytecode(p->class_id)) {
+            JSFunctionBytecode *b;
+            char atom_buf[ATOM_GET_STR_BUF_SIZE];
+            int line_num1;
+
+            b = p->u.func.function_bytecode;
+            if (b->has_debug) {
+                if (sf == ctx->current_stack_frame) {
+                    if (!cur_pc) {
+                        line_num1 = b->debug.line_num;
+                    } else {
+                        line_num1 = find_line_num(ctx, b,
+                                                  cur_pc - b->byte_code_buf - 1);
+                    }
+                } else {
+                    line_num1 = find_line_num(ctx, b,
+                                              sf->cur_pc - b->byte_code_buf - 1);
+                }
+                dbuf_printf(&dbuf, " (%s",
+                            JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf),
+                                          b->debug.filename));
+                if (line_num1 != -1)
+                    dbuf_printf(&dbuf, ":%d", line_num1);
+                dbuf_putc(&dbuf, ')');
+            }
+        } else {
+            dbuf_printf(&dbuf, " (native)");
+        }
+        dbuf_putc(&dbuf, '\n');
+    }
+    dbuf_putc(&dbuf, '\0');
+    str = JS_NewString(ctx, (char *)dbuf.buf);
+    dbuf_free(&dbuf);
+    JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, str,
+                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+}
+
+JSValue JS_NewError(JSContext *ctx)
+{
+    return JS_NewObjectClass(ctx, JS_CLASS_ERROR);
+}
+
+static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num,
+                             const char *fmt, va_list ap)
+{
+    char buf[256];
+    JSValue obj, ret;
+
+    vsnprintf(buf, sizeof(buf), fmt, ap);
+    obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num],
+                                 JS_CLASS_ERROR);
+    if (unlikely(JS_IsException(obj))) {
+        /* out of memory: throw JS_NULL to avoid recursing */
+        obj = JS_NULL;
+    } else {
+        JS_DefinePropertyValue(ctx, obj, JS_ATOM_message,
+                               JS_NewString(ctx, buf),
+                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    }
+    ret = JS_Throw(ctx, obj);
+    return ret;
+}
+
+JSValue __attribute__((format(printf, 2, 3))) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...)
+{
+    JSValue val;
+    va_list ap;
+
+    va_start(ap, fmt);
+    val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap);
+    va_end(ap);
+    return val;
+}
+
+JSValue __attribute__((format(printf, 2, 3))) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...)
+{
+    JSValue val;
+    va_list ap;
+
+    va_start(ap, fmt);
+    val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap);
+    va_end(ap);
+    return val;
+}
+
+static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...)
+{
+    va_list ap;
+
+    if ((flags & JS_PROP_THROW) ||
+        ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
+        va_start(ap, fmt);
+        JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap);
+        va_end(ap);
+        return -1;
+    } else {
+        return FALSE;
+    }
+}
+
+static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom)
+{
+    if ((flags & JS_PROP_THROW) ||
+        ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
+        char buf[ATOM_GET_STR_BUF_SIZE];
+        JS_ThrowTypeError(ctx, "%s is read-only",
+                          JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
+        return -1;
+    } else {
+        return FALSE;
+    }
+}
+
+JSValue __attribute__((format(printf, 2, 3))) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...)
+{
+    JSValue val;
+    va_list ap;
+
+    va_start(ap, fmt);
+    val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap);
+    va_end(ap);
+    return val;
+}
+
+JSValue __attribute__((format(printf, 2, 3))) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...)
+{
+    JSValue val;
+    va_list ap;
+
+    va_start(ap, fmt);
+    val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap);
+    va_end(ap);
+    return val;
+}
+
+JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...)
+{
+    JSValue val;
+    va_list ap;
+
+    va_start(ap, fmt);
+    val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap);
+    va_end(ap);
+    return val;
+}
+
+JSValue JS_ThrowOutOfMemory(JSContext *ctx)
+{
+    if (!ctx->in_out_of_memory) {
+        ctx->in_out_of_memory = TRUE;
+        JS_ThrowInternalError(ctx, "out of memory");
+        ctx->in_out_of_memory = FALSE;
+    }
+    return JS_EXCEPTION;
+}
+
+static JSValue JS_ThrowStackOverflow(JSContext *ctx)
+{
+    return JS_ThrowInternalError(ctx, "stack overflow");
+}
+
+static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx)
+{
+    return JS_ThrowTypeError(ctx, "not an object");
+}
+
+static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx)
+{
+    return JS_ThrowTypeError(ctx, "not a symbol");
+}
+
+static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    return JS_ThrowReferenceError(ctx, "%s is not defined",
+                                  JS_AtomGetStr(ctx, buf, sizeof(buf), name));
+}
+
+static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    return JS_ThrowReferenceError(ctx, "%s is not initialized",
+                                  name == JS_ATOM_NULL ? "lexical variable" :
+                                  JS_AtomGetStr(ctx, buf, sizeof(buf), name));
+}
+
+static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id)
+{
+    JSRuntime *rt = ctx->rt;
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    JSAtom name;
+    name = rt->class_array[class_id].class_name;
+    return JS_ThrowTypeError(ctx, "%s object expected",
+                             JS_AtomGetStr(ctx, buf, sizeof(buf), name));
+}
+
+/* return -1 (exception) or TRUE/FALSE */
+static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj,
+                                   JSValueConst proto_val,
+                                   BOOL throw_flag)
+{
+    JSObject *proto, *p, *p1;
+    JSShape *sh;
+
+    if (throw_flag) {
+        if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL ||
+            JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED)
+            goto not_obj;
+    } else {
+        if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+            goto not_obj;
+    }
+    p = JS_VALUE_GET_OBJ(obj);
+    if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) {
+        if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) {
+        not_obj:
+            JS_ThrowTypeErrorNotAnObject(ctx);
+            return -1;
+        }
+        proto = NULL;
+    } else {
+        proto = JS_VALUE_GET_OBJ(proto_val);
+    }
+
+    if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return TRUE;
+
+    if (unlikely(p->class_id == JS_CLASS_PROXY))
+        return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag);
+    sh = p->shape;
+    if (sh->proto == proto)
+        return TRUE;
+    if (!p->extensible) {
+        if (throw_flag) {
+            JS_ThrowTypeError(ctx, "object is not extensible");
+            return -1;
+        } else {
+            return FALSE;
+        }
+    }
+    if (proto) {
+        /* check if there is a cycle */
+        p1 = proto;
+        do {
+            if (p1 == p) {
+                if (throw_flag) {
+                    JS_ThrowTypeError(ctx, "circular prototype chain");
+                    return -1;
+                } else {
+                    return FALSE;
+                }
+            }
+            /* Note: for Proxy objects, proto is NULL */
+            p1 = p1->shape->proto;
+        } while (p1 != NULL);
+        JS_DupValue(ctx, proto_val);
+    }
+
+    if (js_shape_prepare_update(ctx, p, NULL))
+        return -1;
+    sh = p->shape;
+    if (sh->proto)
+        JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
+    sh->proto = proto;
+    return TRUE;
+}
+
+/* return -1 (exception) or TRUE/FALSE */
+int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val)
+{
+    return JS_SetPrototypeInternal(ctx, obj, proto_val, TRUE);
+}
+
+/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */
+JSValueConst JS_GetPrototype(JSContext *ctx, JSValueConst val)
+{
+    JSObject *p;
+
+    switch(JS_VALUE_GET_NORM_TAG(val)) {
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        val = ctx->class_proto[JS_CLASS_BIG_INT];
+        break;
+    case JS_TAG_INT:
+        if (is_bignum_mode(ctx)) {
+            val = ctx->class_proto[JS_CLASS_BIG_INT];
+        } else {
+            val = ctx->class_proto[JS_CLASS_NUMBER];
+        }
+        break;
+    case JS_TAG_FLOAT64:
+        val = ctx->class_proto[JS_CLASS_NUMBER];
+        break;
+    case JS_TAG_BIG_FLOAT:
+        val = ctx->class_proto[JS_CLASS_BIG_FLOAT];
+        break;
+#else
+    case JS_TAG_INT:
+    case JS_TAG_FLOAT64:
+        val = ctx->class_proto[JS_CLASS_NUMBER];
+        break;
+#endif
+    case JS_TAG_BOOL:
+        val = ctx->class_proto[JS_CLASS_BOOLEAN];
+        break;
+    case JS_TAG_STRING:
+        val = ctx->class_proto[JS_CLASS_STRING];
+        break;
+    case JS_TAG_SYMBOL:
+        val = ctx->class_proto[JS_CLASS_SYMBOL];
+        break;
+    case JS_TAG_OBJECT:
+        p = JS_VALUE_GET_OBJ(val);
+        if (unlikely(p->class_id == JS_CLASS_PROXY)) {
+            val = js_proxy_getPrototypeOf(ctx, val);
+        } else {
+            p = p->shape->proto;
+            if (!p)
+                val = JS_NULL;
+            else
+                val = JS_MKPTR(JS_TAG_OBJECT, p);
+        }
+        break;
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+    default:
+        val = JS_NULL;
+        break;
+    }
+    return val;
+}
+
+/* return TRUE, FALSE or (-1) in case of exception */
+static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val,
+                                   JSValueConst obj)
+{
+    JSValue obj_proto;
+    JSObject *proto;
+    const JSObject *p, *proto1;
+    BOOL ret;
+
+    if (!JS_IsFunction(ctx, obj))
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(obj);
+    if (p->class_id == JS_CLASS_BOUND_FUNCTION) {
+        JSBoundFunction *s = p->u.bound_function;
+        return JS_IsInstanceOf(ctx, val, s->func_obj);
+    }
+
+    /* Only explicitly boxed values are instances of constructors */
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return FALSE;
+    ret = FALSE;
+    obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype);
+    if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) {
+        if (!JS_IsException(obj_proto))
+            JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object");
+        ret = -1;
+        goto done;
+    }
+    proto = JS_VALUE_GET_OBJ(obj_proto);
+    p = JS_VALUE_GET_OBJ(val);
+    for(;;) {
+        proto1 = p->shape->proto;
+        if (!proto1) {
+            if (p->class_id == JS_CLASS_PROXY) {
+                JSValueConst proto_val;
+                proto_val = JS_GetPrototype(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p));
+                if (JS_IsException(proto_val)) {
+                    ret = -1;
+                    goto done;
+                }
+                proto1 = JS_VALUE_GET_OBJ(proto_val);
+                if (!proto1)
+                    break;
+            } else {
+                break;
+            }
+        }
+        p = proto1;
+        if (proto == p) {
+            ret = TRUE;
+            break;
+        }
+    }
+done:
+    JS_FreeValue(ctx, obj_proto);
+    return ret;
+}
+
+/* return TRUE, FALSE or (-1) in case of exception */
+int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj)
+{
+    JSValue method;
+
+    if (!JS_IsObject(obj))
+        goto fail;
+    method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance);
+    if (JS_IsException(method))
+        return -1;
+    if (!JS_IsNull(method) && !JS_IsUndefined(method)) {
+        JSValue ret;
+        ret = JS_CallFree(ctx, method, obj, 1, &val);
+        return JS_ToBoolFree(ctx, ret);
+    }
+
+    /* legacy case */
+    if (!JS_IsFunction(ctx, obj)) {
+    fail:
+        JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand");
+        return -1;
+    }
+    return JS_OrdinaryIsInstanceOf(ctx, val, obj);
+}
+
+static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSProperty *pr)
+{
+    return (*pr->u.init.init_func)(ctx, p, prop, pr->u.init.opaque);
+}
+
+JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj,
+                               JSAtom prop, JSValueConst this_obj,
+                               BOOL throw_ref_error)
+{
+    JSObject *p;
+    JSProperty *pr;
+    JSShapeProperty *prs;
+    uint32_t tag;
+
+    tag = JS_VALUE_GET_TAG(obj);
+    if (unlikely(tag != JS_TAG_OBJECT)) {
+        switch(tag) {
+        case JS_TAG_NULL:
+        case JS_TAG_UNDEFINED:
+            return JS_ThrowTypeError(ctx, "value has no property");
+        case JS_TAG_EXCEPTION:
+            return JS_EXCEPTION;
+        case JS_TAG_STRING:
+            {
+                JSString *p1 = JS_VALUE_GET_STRING(obj);
+                if (__JS_AtomIsTaggedInt(prop)) {
+                    uint32_t idx, ch;
+                    idx = __JS_AtomToUInt32(prop);
+                    if (idx < p1->len) {
+                        if (p1->is_wide_char)
+                            ch = p1->u.str16[idx];
+                        else
+                            ch = p1->u.str8[idx];
+                        return js_new_string_char(ctx, ch);
+                    }
+                } else if (prop == JS_ATOM_length) {
+                    return JS_NewInt32(ctx, p1->len);
+                }
+            }
+            break;
+        default:
+            break;
+        }
+        /* cannot raise an exception */
+        p = JS_VALUE_GET_OBJ(JS_GetPrototype(ctx, obj));
+        if (!p)
+            return JS_UNDEFINED;
+    } else {
+        p = JS_VALUE_GET_OBJ(obj);
+    }
+
+    for(;;) {
+        prs = find_own_property(&pr, p, prop);
+        if (prs) {
+            /* found */
+            if (unlikely(prs->flags & JS_PROP_TMASK)) {
+                if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+                    if (unlikely(!pr->u.getset.getter)) {
+                        return JS_UNDEFINED;
+                    } else {
+                        JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter);
+                        /* Note: the field could be removed in the getter */
+                        func = JS_DupValue(ctx, func);
+                        return JS_CallFree(ctx, func, this_obj, 0, NULL);
+                    }
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                    JSValue val = *pr->u.var_ref->pvalue;
+                    if (unlikely(JS_IsUninitialized(val)))
+                        return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+                    return JS_DupValue(ctx, val);
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                    /* Instantiate property and retry */
+                    if (JS_AutoInitProperty(ctx, p, prop, pr))
+                        return JS_EXCEPTION;
+                    continue;
+                }
+            } else {
+                return JS_DupValue(ctx, pr->u.value);
+            }
+        }
+        if (unlikely(p->is_exotic)) {
+            /* exotic behaviors */
+            if (p->fast_array) {
+                if (__JS_AtomIsTaggedInt(prop)) {
+                    uint32_t idx = __JS_AtomToUInt32(prop);
+                    if (idx < p->u.array.count) {
+                        /* we avoid duplicating the code */
+                        return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx);
+                    } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                               p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+                        goto typed_array_oob;
+                    }
+                } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                           p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+                    int ret;
+                    ret = JS_AtomIsNumericIndex(ctx, prop);
+                    if (ret != 0) {
+                        if (ret < 0)
+                            return JS_EXCEPTION;
+                    typed_array_oob:
+                        if (typed_array_is_detached(ctx, p))
+                            return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+                        return JS_UNDEFINED;
+                    }
+                }
+            } else {
+                const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+                if (em) {
+                    if (em->get_property) {
+                        /* XXX: should pass throw_ref_error */
+                        return em->get_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p),
+                                                prop, this_obj);
+                    }
+                    if (em->get_own_property) {
+                        JSPropertyDescriptor desc;
+                        int ret;
+
+                        ret = em->get_own_property(ctx, &desc, JS_MKPTR(JS_TAG_OBJECT, p), prop);
+                        if (ret < 0)
+                            return JS_EXCEPTION;
+                        if (ret) {
+                            if (desc.flags & JS_PROP_GETSET) {
+                                JS_FreeValue(ctx, desc.setter);
+                                return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL);
+                            } else {
+                                return desc.value;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        p = p->shape->proto;
+        if (!p)
+            break;
+    }
+    if (unlikely(throw_ref_error)) {
+        return JS_ThrowReferenceErrorNotDefined(ctx, prop);
+    } else {
+        return JS_UNDEFINED;
+    }
+}
+
+static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    return JS_ThrowTypeError(ctx, "private class field %s does not exist",
+                      JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
+}
+
+/* Private fields can be added even on non extensible objects or
+   Proxies */
+static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj,
+                                 JSValueConst name, JSValue val)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    JSAtom prop;
+
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        goto fail;
+    }
+    /* safety check */
+    if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) {
+        JS_ThrowTypeErrorNotASymbol(ctx);
+        goto fail;
+    }
+    prop = js_symbol_to_atom(ctx, (JSValue)name);
+    p = JS_VALUE_GET_OBJ(obj);
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        char buf[ATOM_GET_STR_BUF_SIZE];
+        JS_ThrowTypeError(ctx, "private class field %s already exists",
+                          JS_AtomGetStr(ctx, buf, sizeof(buf), prop));
+        goto fail;
+    }
+    pr = add_property(ctx, p, prop, JS_PROP_C_W_E);
+    if (unlikely(!pr)) {
+    fail:
+        JS_FreeValue(ctx, val);
+        return -1;
+    }
+    pr->u.value = val;
+    return 0;
+}
+
+static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj,
+                                  JSValueConst name)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    JSAtom prop;
+
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    /* safety check */
+    if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL))
+        return JS_ThrowTypeErrorNotASymbol(ctx);
+    prop = js_symbol_to_atom(ctx, (JSValue)name);
+    p = JS_VALUE_GET_OBJ(obj);
+    prs = find_own_property(&pr, p, prop);
+    if (!prs) {
+        JS_ThrowTypeErrorPrivateNotFound(ctx, prop);
+        return JS_EXCEPTION;
+    }
+    return JS_DupValue(ctx, pr->u.value);
+}
+
+static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj,
+                              JSValueConst name, JSValue val)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    JSAtom prop;
+
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        goto fail;
+    }
+    /* safety check */
+    if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) {
+        JS_ThrowTypeErrorNotASymbol(ctx);
+        goto fail;
+    }
+    prop = js_symbol_to_atom(ctx, (JSValue)name);
+    p = JS_VALUE_GET_OBJ(obj);
+    prs = find_own_property(&pr, p, prop);
+    if (!prs) {
+        JS_ThrowTypeErrorPrivateNotFound(ctx, prop);
+    fail:
+        JS_FreeValue(ctx, val);
+        return -1;
+    }
+    set_value(ctx, &pr->u.value, val);
+    return 0;
+}
+
+static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj)
+{
+    JSObject *p, *p1;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    JSValue brand;
+    JSAtom brand_atom;
+    
+    if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    p = JS_VALUE_GET_OBJ(home_obj);
+    prs = find_own_property(&pr, p, JS_ATOM_Private_brand);
+    if (!prs) {
+        brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE);
+        if (JS_IsException(brand))
+            return -1;
+        /* if the brand is not present, add it */
+        pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E);
+        if (!pr) {
+            JS_FreeValue(ctx, brand);
+            return -1;
+        }
+        pr->u.value = JS_DupValue(ctx, brand);
+    } else {
+        brand = JS_DupValue(ctx, pr->u.value);
+    }
+    brand_atom = js_symbol_to_atom(ctx, brand);
+    
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        JS_FreeAtom(ctx, brand_atom);
+        return -1;
+    }
+    p1 = JS_VALUE_GET_OBJ(obj);
+    pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E);
+    JS_FreeAtom(ctx, brand_atom);
+    if (!pr)
+        return -1;
+    pr->u.value = JS_UNDEFINED;
+    return 0;
+}
+
+static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func)
+{
+    JSObject *p, *p1, *home_obj;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    JSValueConst brand;
+    
+    /* get the home object of 'func' */
+    if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) {
+    not_obj:
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    p1 = JS_VALUE_GET_OBJ(func);
+    if (!js_class_has_bytecode(p1->class_id))
+        goto not_obj;
+    home_obj = p1->u.func.home_object;
+    if (!home_obj)
+        goto not_obj;
+    prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand);
+    if (!prs) {
+        JS_ThrowTypeError(ctx, "expecting <brand> private field");
+        return -1;
+    }
+    brand = pr->u.value;
+    /* safety check */
+    if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL))
+        goto not_obj;
+    
+    /* get the brand array of 'obj' */
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
+        goto not_obj;
+    p = JS_VALUE_GET_OBJ(obj);
+    prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand));
+    if (!prs) {
+        JS_ThrowTypeError(ctx, "invalid brand on object");
+        return -1;
+    }
+    return 0;
+}
+
+static int num_keys_cmp(const void *p1, const void *p2, void *opaque)
+{
+    JSContext *ctx = opaque;
+    JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom;
+    JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom;
+    uint32_t v1, v2;
+    BOOL atom1_is_integer, atom2_is_integer;
+
+    atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1);
+    atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2);
+    assert(atom1_is_integer && atom2_is_integer);
+    if (v1 < v2)
+        return -1;
+    else if (v1 == v2)
+        return 0;
+    else
+        return 1;
+}
+
+static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len)
+{
+    uint32_t i;
+    if (tab) {
+        for(i = 0; i < len; i++)
+            JS_FreeAtom(ctx, tab[i].atom);
+        js_free(ctx, tab);
+    }
+}
+
+/* return < 0 in case if exception, 0 if OK. ptab and its atoms must
+   be freed by the user. */
+static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx,
+                                                      JSPropertyEnum **ptab,
+                                                      uint32_t *plen,
+                                                      JSObject *p, int flags)
+{
+    int i, j;
+    JSShape *sh;
+    JSShapeProperty *prs;
+    JSPropertyEnum *tab_atom, *tab_exotic;
+    JSAtom atom;
+    uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count;
+    uint32_t num_index, str_index, sym_index, exotic_count;
+    BOOL is_enumerable, num_sorted;
+    uint32_t num_key;
+    JSAtomKindEnum kind;
+    
+    /* clear pointer for consistency in case of failure */
+    *ptab = NULL;
+    *plen = 0;
+
+    /* compute the number of returned properties */
+    num_keys_count = 0;
+    str_keys_count = 0;
+    sym_keys_count = 0;
+    exotic_count = 0;
+    tab_exotic = NULL;
+    sh = p->shape;
+    for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
+        atom = prs->atom;
+        if (atom != JS_ATOM_NULL) {
+            is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0);
+            kind = JS_AtomGetKind(ctx, atom);
+            if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) &&
+                ((flags >> kind) & 1) != 0) {
+                /* need to raise an exception in case of the module
+                   name space (implicit GetOwnProperty) */
+                if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) &&
+                    (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) {
+                    JSVarRef *var_ref = p->prop[i].u.var_ref;
+                    if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) {
+                        JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+                        return -1;
+                    }
+                }
+                if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
+                    num_keys_count++;
+                } else if (kind == JS_ATOM_KIND_STRING) {
+                    str_keys_count++;
+                } else {
+                    sym_keys_count++;
+                }
+            }
+        }
+    }
+
+    if (p->is_exotic) {
+        if (p->fast_array) {
+            /* the implicit GetOwnProperty raises an exception if the
+               typed array is detached */
+            if ((flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) &&
+                 (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                  p->class_id <= JS_CLASS_FLOAT64_ARRAY) &&
+                 typed_array_is_detached(ctx, p) &&
+                typed_array_get_length(ctx, p) != 0) {
+                JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+                return -1;
+            }
+            num_keys_count += p->u.array.count;
+        } else {
+            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+            if (em && em->get_own_property_names) {
+                if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count,
+                                               JS_MKPTR(JS_TAG_OBJECT, p)))
+                    return -1;
+                for(i = 0; i < exotic_count; i++) {
+                    atom = tab_exotic[i].atom;
+                    kind = JS_AtomGetKind(ctx, atom);
+                    if (((flags >> kind) & 1) != 0) {
+                        is_enumerable = FALSE;
+                        if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) {
+                            JSPropertyDescriptor desc;
+                            int res;
+                            /* set the "is_enumerable" field if necessary */
+                            res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
+                            if (res < 0) {
+                                js_free_prop_enum(ctx, tab_exotic, exotic_count);
+                                return -1;
+                            }
+                            if (res) {
+                                is_enumerable =
+                                    ((desc.flags & JS_PROP_ENUMERABLE) != 0);
+                                js_free_desc(ctx, &desc);
+                            }
+                            tab_exotic[i].is_enumerable = is_enumerable;
+                        }
+                        if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) {
+                            if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
+                                num_keys_count++;
+                            } else if (kind == JS_ATOM_KIND_STRING) {
+                                str_keys_count++;
+                            } else {
+                                sym_keys_count++;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /* fill them */
+
+    atom_count = num_keys_count + str_keys_count + sym_keys_count;
+    /* avoid allocating 0 bytes */
+    tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1));
+    if (!tab_atom) {
+        js_free_prop_enum(ctx, tab_exotic, exotic_count);
+        return -1;
+    }
+
+    num_index = 0;
+    str_index = num_keys_count;
+    sym_index = str_index + str_keys_count;
+
+    num_sorted = TRUE;
+    sh = p->shape;
+    for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
+        atom = prs->atom;
+        if (atom != JS_ATOM_NULL) {
+            is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0);
+            kind = JS_AtomGetKind(ctx, atom);
+            if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) &&
+                ((flags >> kind) & 1) != 0) {
+                if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
+                    j = num_index++;
+                    num_sorted = FALSE;
+                } else if (kind == JS_ATOM_KIND_STRING) {
+                    j = str_index++;
+                } else {
+                    j = sym_index++;
+                }
+                tab_atom[j].atom = JS_DupAtom(ctx, atom);
+                tab_atom[j].is_enumerable = is_enumerable;
+            }
+        }
+    }
+
+    if (p->is_exotic) {
+        if (p->fast_array) {
+            for(i = 0; i < p->u.array.count; i++) {
+                tab_atom[num_index].atom = __JS_AtomFromUInt32(i);
+                if (tab_atom[num_index].atom == JS_ATOM_NULL) {
+                    js_free_prop_enum(ctx, tab_exotic, exotic_count);
+                    js_free_prop_enum(ctx, tab_atom, num_index);
+                    return -1;
+                }
+                tab_atom[num_index].is_enumerable = TRUE;
+                num_index++;
+            }
+        }
+        if (exotic_count > 0) {
+            for(i = 0; i < exotic_count; i++) {
+                atom = tab_exotic[i].atom;
+                is_enumerable = tab_exotic[i].is_enumerable;
+                kind = JS_AtomGetKind(ctx, atom);
+                if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) &&
+                    ((flags >> kind) & 1) != 0) {
+                    if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
+                        j = num_index++;
+                        num_sorted = FALSE;
+                    } else if (kind == JS_ATOM_KIND_STRING) {
+                        j = str_index++;
+                    } else {
+                        j = sym_index++;
+                    }
+                    tab_atom[j].atom = atom;
+                    tab_atom[j].is_enumerable = is_enumerable;
+                } else {
+                    JS_FreeAtom(ctx, atom);
+                }
+            }
+        }
+        js_free(ctx, tab_exotic);
+    }
+
+    assert(num_index == num_keys_count);
+    assert(str_index == num_keys_count + str_keys_count);
+    assert(sym_index == atom_count);
+
+    if (num_keys_count != 0 && !num_sorted) {
+        rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp,
+               ctx);
+    }
+    *ptab = tab_atom;
+    *plen = atom_count;
+    return 0;
+}
+
+int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab,
+                           uint32_t *plen, JSValueConst obj, int flags)
+{
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen,
+                                          JS_VALUE_GET_OBJ(obj), flags);
+}
+
+/* Return -1 if exception,
+   FALSE if the property does not exist, TRUE if it exists. If TRUE is
+   returned, the property descriptor 'desc' is filled present. */
+static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc,
+                                     JSObject *p, JSAtom prop)
+{
+    JSShapeProperty *prs;
+    JSProperty *pr;
+
+retry:
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        if (desc) {
+            desc->flags = prs->flags & JS_PROP_C_W_E;
+            desc->getter = JS_UNDEFINED;
+            desc->setter = JS_UNDEFINED;
+            desc->value = JS_UNDEFINED;
+            if (unlikely(prs->flags & JS_PROP_TMASK)) {
+                if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+                    desc->flags |= JS_PROP_GETSET;
+                    if (pr->u.getset.getter)
+                        desc->getter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
+                    if (pr->u.getset.setter)
+                        desc->setter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                    JSValue val = *pr->u.var_ref->pvalue;
+                    if (unlikely(JS_IsUninitialized(val))) {
+                        JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+                        return -1;
+                    }
+                    desc->value = JS_DupValue(ctx, val);
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                    /* Instantiate property and retry */
+                    if (JS_AutoInitProperty(ctx, p, prop, pr))
+                        return -1;
+                    goto retry;
+                }
+            } else {
+                desc->value = JS_DupValue(ctx, pr->u.value);
+            }
+        } else {
+            /* for consistency, send the exception even if desc is NULL */
+            if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) {
+                if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) {
+                    JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+                    return -1;
+                }
+            } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                /* nothing to do: delay instantiation until actual value and/or attributes are read */
+            }
+        }
+        return TRUE;
+    }
+    if (p->is_exotic) {
+        if (p->fast_array) {
+            /* specific case for fast arrays */
+            if (__JS_AtomIsTaggedInt(prop)) {
+                uint32_t idx;
+                idx = __JS_AtomToUInt32(prop);
+                if (idx < p->u.array.count) {
+                    if (desc) {
+                        desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE;
+                        if (p->class_id == JS_CLASS_ARRAY ||
+                            p->class_id == JS_CLASS_ARGUMENTS)
+                            desc->flags |= JS_PROP_CONFIGURABLE;
+                        desc->getter = JS_UNDEFINED;
+                        desc->setter = JS_UNDEFINED;
+                        desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx);
+                    }
+                    return TRUE;
+                }
+            }
+            if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+                int ret;
+                ret = JS_AtomIsNumericIndex(ctx, prop);
+                if (ret != 0) {
+                    if (ret < 0)
+                        return -1;
+                    if (typed_array_is_detached(ctx, p)) {
+                        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+                        return -1;
+                    }
+                }
+            }
+        } else {
+            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+            if (em && em->get_own_property) {
+                return em->get_own_property(ctx, desc,
+                                            JS_MKPTR(JS_TAG_OBJECT, p), prop);
+            }
+        }
+    }
+    return FALSE;
+}
+
+int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc,
+                      JSValueConst obj, JSAtom prop)
+{
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop);
+}
+
+/* return -1 if exception (Proxy object only) or TRUE/FALSE */
+int JS_IsExtensible(JSContext *ctx, JSValueConst obj)
+{
+    JSObject *p;
+
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(obj);
+    if (unlikely(p->class_id == JS_CLASS_PROXY))
+        return js_proxy_isExtensible(ctx, obj);
+    else
+        return p->extensible;
+}
+
+/* return -1 if exception (Proxy object only) or TRUE/FALSE */
+int JS_PreventExtensions(JSContext *ctx, JSValueConst obj)
+{
+    JSObject *p;
+
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(obj);
+    if (unlikely(p->class_id == JS_CLASS_PROXY))
+        return js_proxy_preventExtensions(ctx, obj);
+    p->extensible = FALSE;
+    return TRUE;
+}
+
+/* return -1 if exception otherwise TRUE or FALSE */
+int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop)
+{
+    JSObject *p;
+    int ret;
+
+    if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(obj);
+    for(;;) {
+        if (p->is_exotic) {
+            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+            if (em && em->has_property)
+                return em->has_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop);
+        }
+        ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop);
+        if (ret != 0)
+            return ret;
+        if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+            p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+            ret = JS_AtomIsNumericIndex(ctx, prop);
+            if (ret != 0) {
+                if (ret < 0)
+                    return -1;
+                /* the detached array test was done in
+                   JS_GetOwnPropertyInternal() */
+                return FALSE;
+            }
+        }
+        p = p->shape->proto;
+        if (!p)
+            break;
+    }
+    return FALSE;
+}
+
+/* val must be a symbol */
+static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val)
+{
+    JSAtomStruct *p = JS_VALUE_GET_PTR(val);
+    return js_get_atom_index(ctx->rt, p);
+}
+
+/* return JS_ATOM_NULL in case of exception */
+JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val)
+{
+    JSAtom atom;
+    uint32_t tag;
+    tag = JS_VALUE_GET_TAG(val);
+    if (tag == JS_TAG_INT &&
+        (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) {
+        /* fast path for integer values */
+        atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val));
+    } else if (tag == JS_TAG_SYMBOL) {
+        JSAtomStruct *p = JS_VALUE_GET_PTR(val);
+        atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p));
+    } else {
+        JSValue str;
+        str = JS_ToPropertyKey(ctx, val);
+        if (JS_IsException(str))
+            return JS_ATOM_NULL;
+        if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) {
+            atom = js_symbol_to_atom(ctx, str);
+        } else {
+            atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str));
+        }
+    }
+    return atom;
+}
+
+static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj,
+                                   JSValue prop)
+{
+    JSAtom atom;
+    JSValue ret;
+
+    if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT &&
+               JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) {
+        JSObject *p;
+        uint32_t idx, len;
+        /* fast path for array access */
+        p = JS_VALUE_GET_OBJ(this_obj);
+        idx = JS_VALUE_GET_INT(prop);
+        len = (uint32_t)p->u.array.count;
+        if (unlikely(idx >= len))
+            goto slow_path;
+        switch(p->class_id) {
+        case JS_CLASS_ARRAY:
+        case JS_CLASS_ARGUMENTS:
+            return JS_DupValue(ctx, p->u.array.u.values[idx]);
+        case JS_CLASS_INT8_ARRAY:
+            return JS_NewInt32(ctx, p->u.array.u.int8_ptr[idx]);
+        case JS_CLASS_UINT8C_ARRAY:
+        case JS_CLASS_UINT8_ARRAY:
+            return JS_NewInt32(ctx, p->u.array.u.uint8_ptr[idx]);
+        case JS_CLASS_INT16_ARRAY:
+            return JS_NewInt32(ctx, p->u.array.u.int16_ptr[idx]);
+        case JS_CLASS_UINT16_ARRAY:
+            return JS_NewInt32(ctx, p->u.array.u.uint16_ptr[idx]);
+        case JS_CLASS_INT32_ARRAY:
+            return JS_NewInt32(ctx, p->u.array.u.int32_ptr[idx]);
+        case JS_CLASS_UINT32_ARRAY:
+            return JS_NewUint32(ctx, p->u.array.u.uint32_ptr[idx]);
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT64_ARRAY:
+            return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]);
+        case JS_CLASS_BIG_UINT64_ARRAY:
+            return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]);
+#endif
+        case JS_CLASS_FLOAT32_ARRAY:
+            return __JS_NewFloat64(ctx, p->u.array.u.float_ptr[idx]);
+        case JS_CLASS_FLOAT64_ARRAY:
+            return __JS_NewFloat64(ctx, p->u.array.u.double_ptr[idx]);
+        default:
+            goto slow_path;
+        }
+    } else {
+    slow_path:
+        atom = JS_ValueToAtom(ctx, prop);
+        JS_FreeValue(ctx, prop);
+        if (unlikely(atom == JS_ATOM_NULL))
+            return JS_EXCEPTION;
+        ret = JS_GetProperty(ctx, this_obj, atom);
+        JS_FreeAtom(ctx, atom);
+        return ret;
+    }
+}
+
+JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj,
+                             uint32_t idx)
+{
+    return JS_GetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx));
+}
+
+/* Check if an object has a generalized numeric property. Return value:
+   -1 for exception,
+   TRUE if property exists, stored into *pval,
+   FALSE if proprty does not exist.
+ */
+static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval)
+{
+    JSValue val = JS_UNDEFINED;
+    JSAtom prop;
+    int present;
+
+    if (likely((uint64_t)idx <= JS_ATOM_MAX_INT)) {
+        /* fast path */
+        present = JS_HasProperty(ctx, obj, __JS_AtomFromUInt32(idx));
+        if (present > 0) {
+            val = JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx));
+            if (unlikely(JS_IsException(val)))
+                present = -1;
+        }
+    } else {
+        prop = JS_NewAtomInt64(ctx, idx);
+        present = -1;
+        if (likely(prop != JS_ATOM_NULL)) {
+            present = JS_HasProperty(ctx, obj, prop);
+            if (present > 0) {
+                val = JS_GetProperty(ctx, obj, prop);
+                if (unlikely(JS_IsException(val)))
+                    present = -1;
+            }
+            JS_FreeAtom(ctx, prop);
+        }
+    }
+    *pval = val;
+    return present;
+}
+
+static JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx)
+{
+    JSAtom prop;
+    JSValue val;
+
+    if ((uint64_t)idx <= INT32_MAX) {
+        /* fast path for fast arrays */
+        return JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx));
+    }
+    prop = JS_NewAtomInt64(ctx, idx);
+    if (prop == JS_ATOM_NULL)
+        return JS_EXCEPTION;
+
+    val = JS_GetProperty(ctx, obj, prop);
+    JS_FreeAtom(ctx, prop);
+    return val;
+}
+
+JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj,
+                          const char *prop)
+{
+    JSAtom atom;
+    JSValue ret;
+    atom = JS_NewAtom(ctx, prop);
+    ret = JS_GetProperty(ctx, this_obj, atom);
+    JS_FreeAtom(ctx, atom);
+    return ret;
+}
+
+/* Note: the property value is not initialized. Return NULL if memory
+   error. */
+static JSProperty *add_property(JSContext *ctx,
+                                JSObject *p, JSAtom prop, int prop_flags)
+{
+    JSShape *sh, *new_sh;
+
+    sh = p->shape;
+    if (sh->is_hashed) {
+        /* try to find an existing shape */
+        new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags);
+        if (new_sh) {
+            /* matching shape found: use it */
+            /*  the property array may need to be resized */
+            if (new_sh->prop_size != sh->prop_size) {
+                JSProperty *new_prop;
+                new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) *
+                                      new_sh->prop_size);
+                if (!new_prop)
+                    return NULL;
+                p->prop = new_prop;
+            }
+            p->shape = js_dup_shape(new_sh);
+            js_free_shape(ctx->rt, sh);
+            return &p->prop[new_sh->prop_count - 1];
+        } else if (sh->header.ref_count != 1) {
+            /* if the shape is shared, clone it */
+            new_sh = js_clone_shape(ctx, sh);
+            if (!new_sh)
+                return NULL;
+            /* hash the cloned shape */
+            new_sh->is_hashed = TRUE;
+            js_shape_hash_link(ctx->rt, new_sh);
+            js_free_shape(ctx->rt, p->shape);
+            p->shape = new_sh;
+        }
+    }
+    assert(p->shape->header.ref_count == 1);
+    if (add_shape_property(ctx, &p->shape, p, prop, prop_flags))
+        return NULL;
+    return &p->prop[p->shape->prop_count - 1];
+}
+
+/* can be called on Array or Arguments objects. return < 0 if
+   memory alloc error. */
+static no_inline __exception int convert_fast_array_to_array(JSContext *ctx,
+                                                             JSObject *p)
+{
+    JSProperty *pr;
+    JSShape *sh;
+    JSValue *tab;
+    uint32_t i, len, new_count;
+
+    if (js_shape_prepare_update(ctx, p, NULL))
+        return -1;
+    len = p->u.array.count;
+    /* resize the properties once to simplify the error handling */
+    sh = p->shape;
+    new_count = sh->prop_count + len;
+    if (new_count > sh->prop_size) {
+        if (resize_properties(ctx, &p->shape, p, new_count))
+            return -1;
+    }
+
+    tab = p->u.array.u.values;
+    for(i = 0; i < len; i++) {
+        /* add_property cannot fail here but
+           __JS_AtomFromUInt32(i) fails for i > INT32_MAX */
+        pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E);
+        pr->u.value = *tab++;
+    }
+    js_free(ctx, p->u.array.u.values);
+    p->u.array.count = 0;
+    p->u.array.u.values = NULL; /* fail safe */
+    p->u.array.u1.size = 0;
+    p->fast_array = 0;
+    return 0;
+}
+
+static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom)
+{
+    JSShape *sh;
+    JSShapeProperty *pr, *lpr, *prop;
+    JSProperty *pr1;
+    uint32_t lpr_idx;
+    intptr_t h, h1;
+
+ redo:
+    sh = p->shape;
+    h1 = atom & sh->prop_hash_mask;
+    h = sh->prop_hash_end[-h1 - 1];
+    prop = get_shape_prop(sh);
+    lpr = NULL;
+    lpr_idx = 0;   /* prevent warning */
+    while (h != 0) {
+        pr = &prop[h - 1];
+        if (likely(pr->atom == atom)) {
+            /* found ! */
+            if (!(pr->flags & JS_PROP_CONFIGURABLE))
+                return FALSE;
+            /* realloc the shape if needed */
+            if (lpr)
+                lpr_idx = lpr - get_shape_prop(sh);
+            if (js_shape_prepare_update(ctx, p, &pr))
+                return -1;
+            sh = p->shape;
+            /* remove property */
+            if (lpr) {
+                lpr = get_shape_prop(sh) + lpr_idx;
+                lpr->hash_next = pr->hash_next;
+            } else {
+                sh->prop_hash_end[-h1 - 1] = pr->hash_next;
+            }
+            /* free the entry */
+            pr1 = &p->prop[h - 1];
+            free_property(ctx->rt, pr1, pr->flags);
+            JS_FreeAtom(ctx, pr->atom);
+            /* put default values */
+            pr->flags = 0;
+            pr->atom = JS_ATOM_NULL;
+            pr1->u.value = JS_UNDEFINED;
+            return TRUE;
+        }
+        lpr = pr;
+        h = pr->hash_next;
+    }
+
+    if (p->is_exotic) {
+        if (p->fast_array) {
+            uint32_t idx;
+            if (JS_AtomIsArrayIndex(ctx, &idx, atom) &&
+                idx < p->u.array.count) {
+                if (p->class_id == JS_CLASS_ARRAY ||
+                    p->class_id == JS_CLASS_ARGUMENTS) {
+                    /* Special case deleting the last element of a fast Array */
+                    if (idx == p->u.array.count - 1) {
+                        JS_FreeValue(ctx, p->u.array.u.values[idx]);
+                        p->u.array.count = idx;
+                        return TRUE;
+                    }
+                    if (convert_fast_array_to_array(ctx, p))
+                        return -1;
+                    goto redo;
+                } else {
+                    return FALSE; /* not configurable */
+                }
+            }
+        } else {
+            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+            if (em && em->delete_property) {
+                return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom);
+            }
+        }
+    }
+    /* not found */
+    return TRUE;
+}
+
+static int call_setter(JSContext *ctx, JSObject *setter,
+                       JSValueConst this_obj, JSValue val, int flags)
+{
+    JSValue ret, func;
+    if (likely(setter)) {
+        func = JS_MKPTR(JS_TAG_OBJECT, setter);
+        /* Note: the field could be removed in the setter */
+        func = JS_DupValue(ctx, func);
+        ret = JS_CallFree(ctx, func, this_obj, 1, (JSValueConst *)&val);
+        JS_FreeValue(ctx, val);
+        if (JS_IsException(ret))
+            return -1;
+        JS_FreeValue(ctx, ret);
+    } else {
+        JS_FreeValue(ctx, val);
+        if ((flags & JS_PROP_THROW) ||
+            ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
+            JS_ThrowTypeError(ctx, "no setter for property");
+            return -1;
+        }
+        /* XXX: should return FALSE? */
+    }
+    return TRUE;
+}
+
+/* set the array length and remove the array elements if necessary. */
+static int set_array_length(JSContext *ctx, JSObject *p, JSProperty *prop,
+                            JSValue val, int flags)
+{
+    uint32_t len, idx, cur_len;
+    int i, ret;
+
+    ret = JS_ToArrayLengthFree(ctx, &len, val);
+    if (ret)
+        return -1;
+    if (likely(p->fast_array)) {
+        uint32_t old_len = p->u.array.count;
+        if (len < old_len) {
+            for(i = len; i < old_len; i++) {
+                JS_FreeValue(ctx, p->u.array.u.values[i]);
+            }
+            p->u.array.count = len;
+        }
+#ifdef CONFIG_BIGNUM
+        set_value(ctx, &prop->u.value, JS_NewUint32(ctx, len));
+#else
+        prop->u.value = JS_NewUint32(ctx, len);
+#endif
+    } else {
+        /* Note: length is always a uint32 because the object is an
+           array */
+        JS_ToUint32(ctx, &cur_len, prop->u.value);
+        if (len < cur_len) {
+            uint32_t d;
+            JSShape *sh;
+            JSShapeProperty *pr;
+
+            d = cur_len - len;
+            sh = p->shape;
+            if (d <= sh->prop_count) {
+                JSAtom atom;
+
+                /* faster to iterate */
+                while (cur_len > len) {
+                    atom = JS_NewAtomUInt32(ctx, cur_len - 1);
+                    ret = delete_property(ctx, p, atom);
+                    JS_FreeAtom(ctx, atom);
+                    if (unlikely(!ret)) {
+                        /* unlikely case: property is not
+                           configurable */
+                        break;
+                    }
+                    cur_len--;
+                }
+            } else {
+                /* faster to iterate thru all the properties. Need two
+                   passes in case one of the property is not
+                   configurable */
+                cur_len = len;
+                for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count;
+                    i++, pr++) {
+                    if (pr->atom != JS_ATOM_NULL &&
+                        JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
+                        if (idx >= cur_len &&
+                            !(pr->flags & JS_PROP_CONFIGURABLE)) {
+                            cur_len = idx + 1;
+                        }
+                    }
+                }
+
+                for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count;
+                    i++, pr++) {
+                    if (pr->atom != JS_ATOM_NULL &&
+                        JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
+                        if (idx >= cur_len) {
+                            /* remove the property */
+                            delete_property(ctx, p, pr->atom);
+                            /* WARNING: the shape may have been modified */
+                            sh = p->shape;
+                            pr = get_shape_prop(sh) + i;
+                        }
+                    }
+                }
+            }
+        } else {
+            cur_len = len;
+        }
+        set_value(ctx, &p->prop[0].u.value, JS_NewUint32(ctx, cur_len));
+        if (unlikely(cur_len > len)) {
+            return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable");
+        }
+    }
+    return TRUE;
+}
+
+/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array =
+   TRUE and p->extensible = TRUE */
+static int add_fast_array_element(JSContext *ctx, JSObject *p,
+                                  JSValue val, int flags)
+{
+    uint32_t new_len, array_len;
+    /* extend the array by one */
+    /* XXX: convert to slow array if new_len > 2^31-1 elements */
+    new_len = p->u.array.count + 1;
+    /* update the length if necessary. We assume that if the length is
+       not an integer, then if it >= 2^31.  */
+    if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) {
+        array_len = JS_VALUE_GET_INT(p->prop[0].u.value);
+        if (new_len > array_len) {
+            if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) {
+                JS_FreeValue(ctx, val);
+                return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
+            }
+            p->prop[0].u.value = JS_NewInt32(ctx, new_len);
+        }
+    }
+    if (unlikely(new_len > p->u.array.u1.size)) {
+        uint32_t new_size;
+        size_t slack;
+        JSValue *new_array_prop;
+        /* XXX: potential arithmetic overflow */
+        new_size = max_int(new_len, p->u.array.u1.size * 3 / 2);
+        new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack);
+        if (!new_array_prop) {
+            JS_FreeValue(ctx, val);
+            return -1;
+        }
+        new_size += slack / sizeof(*new_array_prop);
+        p->u.array.u.values = new_array_prop;
+        p->u.array.u1.size = new_size;
+    }
+    p->u.array.u.values[new_len - 1] = val;
+    p->u.array.count = new_len;
+    return TRUE;
+}
+
+static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc)
+{
+    JS_FreeValue(ctx, desc->getter);
+    JS_FreeValue(ctx, desc->setter);
+    JS_FreeValue(ctx, desc->value);
+}
+
+/* generic (and slower) version of JS_SetProperty() for Reflect.set() */
+static int JS_SetPropertyGeneric(JSContext *ctx,
+                                 JSObject *p, JSAtom prop,
+                                 JSValue val, JSValueConst this_obj,
+                                 int flags)
+{
+    int ret;
+    JSPropertyDescriptor desc;
+
+    while (p != NULL) {
+        if (p->is_exotic) {
+            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+            if (em && em->set_property) {
+                ret = em->set_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop,
+                                       val, this_obj, flags);
+                JS_FreeValue(ctx, val);
+                return ret;
+            }
+        }
+
+        ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
+        if (ret < 0)
+            return ret;
+        if (ret) {
+            if (desc.flags & JS_PROP_GETSET) {
+                JSObject *setter;
+                if (JS_IsUndefined(desc.setter))
+                    setter = NULL;
+                else
+                    setter = JS_VALUE_GET_OBJ(desc.setter);
+                ret = call_setter(ctx, setter, this_obj, val, flags);
+                JS_FreeValue(ctx, desc.getter);
+                JS_FreeValue(ctx, desc.setter);
+                return ret;
+            } else {
+                JS_FreeValue(ctx, desc.value);
+                if (!(desc.flags & JS_PROP_WRITABLE)) {
+                    goto read_only_error;
+                }
+            }
+            break;
+        }
+        p = p->shape->proto;
+    }
+
+    if (!JS_IsObject(this_obj))
+        return JS_ThrowTypeErrorOrFalse(ctx, flags, "receiver is not an object");
+
+    p = JS_VALUE_GET_OBJ(this_obj);
+
+    /* modify the property in this_obj if it already exists */
+    ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
+    if (ret < 0)
+        return ret;
+    if (ret) {
+        if (desc.flags & JS_PROP_GETSET) {
+            JS_FreeValue(ctx, desc.getter);
+            JS_FreeValue(ctx, desc.setter);
+            JS_FreeValue(ctx, val);
+            return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden");
+        } else {
+            JS_FreeValue(ctx, desc.value);
+            if (!(desc.flags & JS_PROP_WRITABLE) ||
+                p->class_id == JS_CLASS_MODULE_NS) {
+            read_only_error:
+                JS_FreeValue(ctx, val);
+                return JS_ThrowTypeErrorReadOnly(ctx, flags, prop);
+            }
+        }
+        ret = JS_DefineProperty(ctx, this_obj, prop, val,
+                                JS_UNDEFINED, JS_UNDEFINED,
+                                JS_PROP_HAS_VALUE);
+        JS_FreeValue(ctx, val);
+        return ret;
+    }
+
+    ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED,
+                            flags |
+                            JS_PROP_HAS_VALUE |
+                            JS_PROP_HAS_ENUMERABLE |
+                            JS_PROP_HAS_WRITABLE |
+                            JS_PROP_HAS_CONFIGURABLE |
+                            JS_PROP_C_W_E);
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+/* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is
+   freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD,
+   JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set,
+   the new property is not added and an error is raised. */
+int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj,
+                           JSAtom prop, JSValue val, int flags)
+{
+    JSObject *p, *p1;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    uint32_t tag;
+    JSPropertyDescriptor desc;
+    int ret;
+#if 0
+    printf("JS_SetPropertyInternal: "); print_atom(ctx, prop); printf("\n");
+#endif
+    tag = JS_VALUE_GET_TAG(this_obj);
+    if (unlikely(tag != JS_TAG_OBJECT)) {
+        switch(tag) {
+        case JS_TAG_NULL:
+        case JS_TAG_UNDEFINED:
+            JS_FreeValue(ctx, val);
+            JS_ThrowTypeError(ctx, "value has no property");
+            return -1;
+        default:
+            /* even on a primitive type we can have setters on the prototype */
+            p = NULL;
+            p1 = JS_VALUE_GET_OBJ(JS_GetPrototype(ctx, this_obj));
+            goto prototype_lookup;
+        }
+    }
+    p = JS_VALUE_GET_OBJ(this_obj);
+retry:
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE |
+                                  JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) {
+            /* fast case */
+            set_value(ctx, &pr->u.value, val);
+            return TRUE;
+        } else if ((prs->flags & (JS_PROP_LENGTH | JS_PROP_WRITABLE)) ==
+                   (JS_PROP_LENGTH | JS_PROP_WRITABLE)) {
+            assert(p->class_id == JS_CLASS_ARRAY);
+            assert(prop == JS_ATOM_length);
+            return set_array_length(ctx, p, pr, val, flags);
+        } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+            return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags);
+        } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+            /* JS_PROP_WRITABLE is always true for variable
+               references, but they are write protected in module name
+               spaces. */
+            if (p->class_id == JS_CLASS_MODULE_NS)
+                goto read_only_prop;
+            set_value(ctx, pr->u.var_ref->pvalue, val);
+            return TRUE;
+        } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+            /* Instantiate property and retry (potentially useless) */
+            if (JS_AutoInitProperty(ctx, p, prop, pr)) {
+                JS_FreeValue(ctx, val);
+                return -1;
+            }
+            goto retry;
+        } else {
+            goto read_only_prop;
+        }
+    }
+
+    p1 = p;
+    for(;;) {
+        if (p1->is_exotic) {
+            if (p1->fast_array) {
+                if (__JS_AtomIsTaggedInt(prop)) {
+                    uint32_t idx = __JS_AtomToUInt32(prop);
+                    if (idx < p1->u.array.count) {
+                        if (unlikely(p == p1))
+                            return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, flags);
+                        else
+                            break;
+                    } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                               p1->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+                        goto typed_array_oob;
+                    }
+                } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                           p1->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+                    ret = JS_AtomIsNumericIndex(ctx, prop);
+                    if (ret != 0) {
+                        if (ret < 0) {
+                            JS_FreeValue(ctx, val);
+                            return -1;
+                        }
+                    typed_array_oob:
+                        val = JS_ToNumberFree(ctx, val);
+                        JS_FreeValue(ctx, val);
+                        if (JS_IsException(val))
+                            return -1;
+                        if (typed_array_is_detached(ctx, p1)) {
+                            JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+                            return -1;
+                        }
+                        return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index");
+                    }
+                }
+            } else {
+                const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic;
+                if (em) {
+                    if (em->set_property) {
+                        ret = em->set_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p1), prop,
+                                               val, this_obj, flags);
+                        JS_FreeValue(ctx, val);
+                        return ret;
+                    }
+                    if (em->get_own_property) {
+                        ret = em->get_own_property(ctx, &desc,
+                                                   JS_MKPTR(JS_TAG_OBJECT, p1), prop);
+                        if (ret < 0) {
+                            JS_FreeValue(ctx, val);
+                            return ret;
+                        }
+                        if (ret) {
+                            if (desc.flags & JS_PROP_GETSET) {
+                                JSObject *setter;
+                                if (JS_IsUndefined(desc.setter))
+                                    setter = NULL;
+                                else
+                                    setter = JS_VALUE_GET_OBJ(desc.setter);
+                                ret = call_setter(ctx, setter, this_obj, val, flags);
+                                JS_FreeValue(ctx, desc.getter);
+                                JS_FreeValue(ctx, desc.setter);
+                                return ret;
+                            } else {
+                                JS_FreeValue(ctx, desc.value);
+                                if (!(desc.flags & JS_PROP_WRITABLE))
+                                    goto read_only_prop;
+                                if (likely(p == p1)) {
+                                    ret = JS_DefineProperty(ctx, this_obj, prop, val,
+                                                            JS_UNDEFINED, JS_UNDEFINED,
+                                                            JS_PROP_HAS_VALUE);
+                                    JS_FreeValue(ctx, val);
+                                    return ret;
+                                } else {
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        p1 = p1->shape->proto;
+    prototype_lookup:
+        if (!p1)
+            break;
+
+    retry2:
+        prs = find_own_property(&pr, p1, prop);
+        if (prs) {
+            if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+                return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags);
+            } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                /* Instantiate property and retry (potentially useless) */
+                if (JS_AutoInitProperty(ctx, p1, prop, pr))
+                    return -1;
+                goto retry2;
+            } else if (!(prs->flags & JS_PROP_WRITABLE)) {
+            read_only_prop:
+                JS_FreeValue(ctx, val);
+                return JS_ThrowTypeErrorReadOnly(ctx, flags, prop);
+            }
+        }
+    }
+
+    if (unlikely(flags & JS_PROP_NO_ADD)) {
+        JS_FreeValue(ctx, val);
+        JS_ThrowReferenceErrorNotDefined(ctx, prop);
+        return -1;
+    }
+
+    if (unlikely(!p)) {
+        JS_FreeValue(ctx, val);
+        return JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object");
+    }
+
+    if (unlikely(!p->extensible)) {
+        JS_FreeValue(ctx, val);
+        return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible");
+    }
+
+    if (p->is_exotic) {
+        if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
+            uint32_t idx = __JS_AtomToUInt32(prop);
+            if (idx == p->u.array.count) {
+                /* fast case */
+                return add_fast_array_element(ctx, p, val, flags);
+            } else {
+                goto generic_create_prop;
+            }
+        } else {
+        generic_create_prop:
+            ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED,
+                                    flags |
+                                    JS_PROP_HAS_VALUE |
+                                    JS_PROP_HAS_ENUMERABLE |
+                                    JS_PROP_HAS_WRITABLE |
+                                    JS_PROP_HAS_CONFIGURABLE |
+                                    JS_PROP_C_W_E);
+            JS_FreeValue(ctx, val);
+            return ret;
+        }
+    }
+
+    pr = add_property(ctx, p, prop, JS_PROP_C_W_E);
+    if (unlikely(!pr)) {
+        JS_FreeValue(ctx, val);
+        return -1;
+    }
+    pr->u.value = val;
+    return TRUE;
+}
+
+/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */
+static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj,
+                               JSValue prop, JSValue val, int flags)
+{
+    if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT &&
+               JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) {
+        JSObject *p;
+        uint32_t idx;
+        double d;
+        int32_t v;
+
+        /* fast path for array access */
+        p = JS_VALUE_GET_OBJ(this_obj);
+        idx = JS_VALUE_GET_INT(prop);
+        switch(p->class_id) {
+        case JS_CLASS_ARRAY:
+            if (unlikely(idx >= (uint32_t)p->u.array.count)) {
+                JSObject *p1;
+                JSShape *sh1;
+
+                /* fast path to add an element to the array */
+                if (idx != (uint32_t)p->u.array.count ||
+                    !p->fast_array || !p->extensible)
+                    goto slow_path;
+                /* check if prototype chain has a numeric property */
+                p1 = p->shape->proto;
+                while (p1 != NULL) {
+                    sh1 = p1->shape;
+                    if (p1->class_id == JS_CLASS_ARRAY) {
+                        if (unlikely(!p1->fast_array))
+                            goto slow_path;
+                    } else if (p1->class_id == JS_CLASS_OBJECT) {
+                        if (unlikely(sh1->has_small_array_index))
+                            goto slow_path;
+                    } else {
+                        goto slow_path;
+                    }
+                    p1 = sh1->proto;
+                }
+                /* add element */
+                return add_fast_array_element(ctx, p, val, flags);
+            }
+            set_value(ctx, &p->u.array.u.values[idx], val);
+            break;
+        case JS_CLASS_ARGUMENTS:
+            if (unlikely(idx >= (uint32_t)p->u.array.count))
+                goto slow_path;
+            set_value(ctx, &p->u.array.u.values[idx], val);
+            break;
+        case JS_CLASS_UINT8C_ARRAY:
+            if (JS_ToUint8ClampFree(ctx, &v, val))
+                return -1;
+            /* Note: the conversion can detach the typed array, so the
+               array bound check must be done after */
+            if (unlikely(idx >= (uint32_t)p->u.array.count))
+                goto ta_out_of_bound;
+            p->u.array.u.uint8_ptr[idx] = v;
+            break;
+        case JS_CLASS_INT8_ARRAY:
+        case JS_CLASS_UINT8_ARRAY:
+            if (JS_ToInt32Free(ctx, &v, val))
+                return -1;
+            if (unlikely(idx >= (uint32_t)p->u.array.count))
+                goto ta_out_of_bound;
+            p->u.array.u.uint8_ptr[idx] = v;
+            break;
+        case JS_CLASS_INT16_ARRAY:
+        case JS_CLASS_UINT16_ARRAY:
+            if (JS_ToInt32Free(ctx, &v, val))
+                return -1;
+            if (unlikely(idx >= (uint32_t)p->u.array.count))
+                goto ta_out_of_bound;
+            p->u.array.u.uint16_ptr[idx] = v;
+            break;
+        case JS_CLASS_INT32_ARRAY:
+        case JS_CLASS_UINT32_ARRAY:
+            if (JS_ToInt32Free(ctx, &v, val))
+                return -1;
+            if (unlikely(idx >= (uint32_t)p->u.array.count))
+                goto ta_out_of_bound;
+            p->u.array.u.uint32_ptr[idx] = v;
+            break;
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT64_ARRAY:
+        case JS_CLASS_BIG_UINT64_ARRAY:
+            /* XXX: need specific conversion function */
+            {
+                int64_t v;
+                if (JS_ToBigInt64Free(ctx, &v, val))
+                    return -1;
+                if (unlikely(idx >= (uint32_t)p->u.array.count))
+                    goto ta_out_of_bound;
+                p->u.array.u.uint64_ptr[idx] = v;
+            }
+            break;
+#endif
+        case JS_CLASS_FLOAT32_ARRAY:
+            if (JS_ToFloat64Free(ctx, &d, val))
+                return -1;
+            if (unlikely(idx >= (uint32_t)p->u.array.count))
+                goto ta_out_of_bound;
+            p->u.array.u.float_ptr[idx] = d;
+            break;
+        case JS_CLASS_FLOAT64_ARRAY:
+            if (JS_ToFloat64Free(ctx, &d, val))
+                return -1;
+            if (unlikely(idx >= (uint32_t)p->u.array.count)) {
+            ta_out_of_bound:
+                if (typed_array_is_detached(ctx, p)) {
+                    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+                    return -1;
+                } else {
+                    return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index");
+                }
+            }
+            p->u.array.u.double_ptr[idx] = d;
+            break;
+        default:
+            goto slow_path;
+        }
+        return TRUE;
+    } else {
+        JSAtom atom;
+        int ret;
+    slow_path:
+        atom = JS_ValueToAtom(ctx, prop);
+        JS_FreeValue(ctx, prop);
+        if (unlikely(atom == JS_ATOM_NULL)) {
+            JS_FreeValue(ctx, val);
+            return -1;
+        }
+        ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags);
+        JS_FreeAtom(ctx, atom);
+        return ret;
+    }
+}
+
+int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj,
+                         uint32_t idx, JSValue val)
+{
+    return JS_SetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx), val,
+                               JS_PROP_THROW);
+}
+
+int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj,
+                        int64_t idx, JSValue val)
+{
+    JSAtom prop;
+    int res;
+
+    if ((uint64_t)idx <= INT32_MAX) {
+        /* fast path for fast arrays */
+        return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val,
+                                   JS_PROP_THROW);
+    }
+    prop = JS_NewAtomInt64(ctx, idx);
+    if (prop == JS_ATOM_NULL) {
+        JS_FreeValue(ctx, val);
+        return -1;
+    }
+    res = JS_SetProperty(ctx, this_obj, prop, val);
+    JS_FreeAtom(ctx, prop);
+    return res;
+}
+
+int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj,
+                      const char *prop, JSValue val)
+{
+    JSAtom atom;
+    int ret;
+    atom = JS_NewAtom(ctx, prop);
+    ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW);
+    JS_FreeAtom(ctx, atom);
+    return ret;
+}
+
+/* compute the property flags. For each flag: (JS_PROP_HAS_x forces
+   it, otherwise def_flags is used)
+   Note: makes assumption about the bit pattern of the flags
+*/
+static int get_prop_flags(int flags, int def_flags)
+{
+    int mask;
+    mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E;
+    return (flags & mask) | (def_flags & ~mask);
+}
+
+static int JS_CreateProperty(JSContext *ctx, JSObject *p,
+                             JSAtom prop, JSValueConst val,
+                             JSValueConst getter, JSValueConst setter,
+                             int flags)
+{
+    JSProperty *pr;
+    int ret, prop_flags;
+
+    /* add a new property or modify an existing exotic one */
+    if (p->is_exotic) {
+        if (p->class_id == JS_CLASS_ARRAY) {
+            uint32_t idx, len;
+
+            if (p->fast_array) {
+                if (__JS_AtomIsTaggedInt(prop)) {
+                    idx = __JS_AtomToUInt32(prop);
+                    if (idx == p->u.array.count) {
+                        if (!p->extensible)
+                            goto not_extensible;
+                        if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET))
+                            goto convert_to_array;
+                        prop_flags = get_prop_flags(flags, 0);
+                        if (prop_flags != JS_PROP_C_W_E)
+                            goto convert_to_array;
+                        return add_fast_array_element(ctx, p,
+                                                      JS_DupValue(ctx, val), flags);
+                    } else {
+                        goto convert_to_array;
+                    }
+                } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) {
+                    /* convert the fast array to normal array */
+                convert_to_array:
+                    if (convert_fast_array_to_array(ctx, p))
+                        return -1;
+                    goto generic_array;
+                }
+            } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) {
+                JSProperty *plen;
+                JSShapeProperty *pslen;
+            generic_array:
+                /* update the length field */
+                plen = &p->prop[0];
+                JS_ToUint32(ctx, &len, plen->u.value);
+                if ((idx + 1) > len) {
+                    pslen = get_shape_prop(p->shape);
+                    if (unlikely(!(pslen->flags & JS_PROP_WRITABLE)))
+                        return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
+                    /* XXX: should update the length after defining
+                       the property */
+                    len = idx + 1;
+                    set_value(ctx, &plen->u.value, JS_NewUint32(ctx, len));
+                }
+            }
+        } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                   p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+            ret = JS_AtomIsNumericIndex(ctx, prop);
+            if (ret != 0) {
+                if (ret < 0)
+                    return -1;
+                return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array");
+            }
+        } else if (!(flags & JS_PROP_NO_EXOTIC)) {
+            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
+            if (em) {
+                if (em->define_own_property) {
+                    return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p),
+                                                   prop, val, getter, setter, flags);
+                }
+                ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p));
+                if (ret < 0)
+                    return -1;
+                if (!ret)
+                    goto not_extensible;
+            }
+        }
+    }
+
+    if (!p->extensible) {
+    not_extensible:
+        return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible");
+    }
+
+    if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+        prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) |
+            JS_PROP_GETSET;
+    } else {
+        prop_flags = flags & JS_PROP_C_W_E;
+    }
+    pr = add_property(ctx, p, prop, prop_flags);
+    if (unlikely(!pr))
+        return -1;
+    if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+        pr->u.getset.getter = NULL;
+        if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) {
+            pr->u.getset.getter =
+                JS_VALUE_GET_OBJ(JS_DupValue(ctx, getter));
+        }
+        pr->u.getset.setter = NULL;
+        if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) {
+            pr->u.getset.setter =
+                JS_VALUE_GET_OBJ(JS_DupValue(ctx, setter));
+        }
+    } else {
+        if (flags & JS_PROP_HAS_VALUE) {
+            pr->u.value = JS_DupValue(ctx, val);
+        } else {
+            pr->u.value = JS_UNDEFINED;
+        }
+    }
+    return TRUE;
+}
+
+/* return FALSE if not OK */
+static BOOL check_define_prop_flags(int prop_flags, int flags)
+{
+    BOOL has_accessor, is_getset;
+
+    if (!(prop_flags & JS_PROP_CONFIGURABLE)) {
+        if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) ==
+            (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) {
+            return FALSE;
+        }
+        if ((flags & JS_PROP_HAS_ENUMERABLE) &&
+            (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE))
+            return FALSE;
+    }
+    if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE |
+                 JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+        if (!(prop_flags & JS_PROP_CONFIGURABLE)) {
+            has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0);
+            is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET);
+            if (has_accessor != is_getset)
+                return FALSE;
+            if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) {
+                /* not writable: cannot set the writable bit */
+                if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) ==
+                    (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE))
+                    return FALSE;
+            }
+        }
+    }
+    return TRUE;
+}
+
+/* ensure that the shape can be safely modified */
+static int js_shape_prepare_update(JSContext *ctx, JSObject *p,
+                                   JSShapeProperty **pprs)
+{
+    JSShape *sh;
+    uint32_t idx = 0;    /* prevent warning */
+
+    sh = p->shape;
+    if (sh->is_hashed) {
+        if (sh->header.ref_count != 1) {
+            if (pprs)
+                idx = *pprs - get_shape_prop(sh);
+            /* clone the shape (the resulting one is no longer hashed) */
+            sh = js_clone_shape(ctx, sh);
+            if (!sh)
+                return -1;
+            js_free_shape(ctx->rt, p->shape);
+            p->shape = sh;
+            if (pprs)
+                *pprs = get_shape_prop(sh) + idx;
+        } else {
+            js_shape_hash_unlink(ctx->rt, sh);
+            sh->is_hashed = FALSE;
+        }
+    }
+    return 0;
+}
+
+static int js_update_property_flags(JSContext *ctx, JSObject *p,
+                                    JSShapeProperty **pprs, int flags)
+{
+    if (flags != (*pprs)->flags) {
+        if (js_shape_prepare_update(ctx, p, pprs))
+            return -1;
+        (*pprs)->flags = flags;
+    }
+    return 0;
+}
+
+/* allowed flags:
+   JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE
+   JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE,
+   JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE,
+   JS_PROP_THROW, JS_PROP_NO_EXOTIC.
+   If JS_PROP_THROW is set, return an exception instead of FALSE.
+   if JS_PROP_NO_EXOTIC is set, do not call the exotic
+   define_own_property callback.
+   return -1 (exception), FALSE or TRUE.
+*/
+int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj,
+                      JSAtom prop, JSValueConst val,
+                      JSValueConst getter, JSValueConst setter, int flags)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    int mask, res;
+
+    if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    p = JS_VALUE_GET_OBJ(this_obj);
+
+ redo_prop_update:
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        /* property already exists */
+        if (!check_define_prop_flags(prs->flags, flags)) {
+        not_configurable:
+            return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable");
+        }
+
+    retry:
+        if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE |
+                     JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+            if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+                JSObject *new_getter, *new_setter;
+
+                if (JS_IsFunction(ctx, getter)) {
+                    new_getter = JS_VALUE_GET_OBJ(getter);
+                } else {
+                    new_getter = NULL;
+                }
+                if (JS_IsFunction(ctx, setter)) {
+                    new_setter = JS_VALUE_GET_OBJ(setter);
+                } else {
+                    new_setter = NULL;
+                }
+
+                if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) {
+                    if (js_shape_prepare_update(ctx, p, &prs))
+                        return -1;
+                    /* convert to getset */
+                    if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                        free_var_ref(ctx->rt, pr->u.var_ref);
+                    } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                        /* clear property and update */
+                        if (js_shape_prepare_update(ctx, p, &prs))
+                            return -1;
+                        prs->flags &= ~JS_PROP_TMASK;
+                        pr->u.value = JS_UNDEFINED;
+                        goto retry;
+                    } else {
+                        JS_FreeValue(ctx, pr->u.value);
+                    }
+                    prs->flags = (prs->flags &
+                                  (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) |
+                        JS_PROP_GETSET;
+                    pr->u.getset.getter = NULL;
+                    pr->u.getset.setter = NULL;
+                } else {
+                    if (!(prs->flags & JS_PROP_CONFIGURABLE)) {
+                        if ((flags & JS_PROP_HAS_GET) &&
+                            new_getter != pr->u.getset.getter) {
+                            goto not_configurable;
+                        }
+                        if ((flags & JS_PROP_HAS_SET) &&
+                            new_setter != pr->u.getset.setter) {
+                            goto not_configurable;
+                        }
+                    }
+                }
+                if (flags & JS_PROP_HAS_GET) {
+                    if (pr->u.getset.getter)
+                        JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
+                    if (new_getter)
+                        JS_DupValue(ctx, getter);
+                    pr->u.getset.getter = new_getter;
+                }
+                if (flags & JS_PROP_HAS_SET) {
+                    if (pr->u.getset.setter)
+                        JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
+                    if (new_setter)
+                        JS_DupValue(ctx, setter);
+                    pr->u.getset.setter = new_setter;
+                }
+            } else {
+                if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+                    /* convert to data descriptor */
+                    if (js_shape_prepare_update(ctx, p, &prs))
+                        return -1;
+                    if (pr->u.getset.getter)
+                        JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
+                    if (pr->u.getset.setter)
+                        JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
+                    prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE);
+                    pr->u.value = JS_UNDEFINED;
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                    /* Note: JS_PROP_VARREF is always writable */
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                    /* clear property and update */
+                    if (js_shape_prepare_update(ctx, p, &prs))
+                        return -1;
+                    prs->flags &= ~JS_PROP_TMASK;
+                    pr->u.value = JS_UNDEFINED;
+                } else {
+                    if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 &&
+                        (flags & JS_PROP_HAS_VALUE) &&
+                        !js_same_value(ctx, val, pr->u.value)) {
+                        goto not_configurable;
+                    }
+                }
+                if (prs->flags & JS_PROP_LENGTH) {
+                    if (flags & JS_PROP_HAS_VALUE) {
+                        res = set_array_length(ctx, p, pr, JS_DupValue(ctx, val),
+                                               flags);
+                    } else {
+                        res = TRUE;
+                    }
+                    /* still need to reset the writable flag if needed.
+                       The JS_PROP_LENGTH is reset to have the correct
+                       read-only behavior in JS_SetProperty(). */
+                    if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) ==
+                        JS_PROP_HAS_WRITABLE) {
+                        prs = get_shape_prop(p->shape);
+                        if (js_update_property_flags(ctx, p, &prs,
+                                                     prs->flags & ~(JS_PROP_WRITABLE | JS_PROP_LENGTH)))
+                            return -1;
+                    }
+                    return res;
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                    if (flags & JS_PROP_HAS_VALUE) {
+                        if (p->class_id == JS_CLASS_MODULE_NS) {
+                            /* JS_PROP_WRITABLE is always true for variable
+                               references, but they are write protected in module name
+                               spaces. */
+                            if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue))
+                                goto not_configurable;
+                        }
+                        /* update the reference */
+                        set_value(ctx, pr->u.var_ref->pvalue,
+                                  JS_DupValue(ctx, val));
+                    }
+                    /* if writable is set to false, no longer a
+                       reference (for mapped arguments) */
+                    if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) {
+                        JSValue val1;
+                        if (js_shape_prepare_update(ctx, p, &prs))
+                            return -1;
+                        val1 = JS_DupValue(ctx, *pr->u.var_ref->pvalue);
+                        free_var_ref(ctx->rt, pr->u.var_ref);
+                        pr->u.value = val1;
+                        prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE);
+                    }
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                    /* XXX: should never happen, type was reset above */
+                    abort();
+                } else {
+                    if (flags & JS_PROP_HAS_VALUE) {
+                        JS_FreeValue(ctx, pr->u.value);
+                        pr->u.value = JS_DupValue(ctx, val);
+                    }
+                    if (flags & JS_PROP_HAS_WRITABLE) {
+                        if (js_update_property_flags(ctx, p, &prs,
+                                                     (prs->flags & ~JS_PROP_WRITABLE) |
+                                                     (flags & JS_PROP_WRITABLE)))
+                            return -1;
+                    }
+                }
+            }
+        }
+        mask = 0;
+        if (flags & JS_PROP_HAS_CONFIGURABLE)
+            mask |= JS_PROP_CONFIGURABLE;
+        if (flags & JS_PROP_HAS_ENUMERABLE)
+            mask |= JS_PROP_ENUMERABLE;
+        if (js_update_property_flags(ctx, p, &prs,
+                                     (prs->flags & ~mask) | (flags & mask)))
+            return -1;
+        return TRUE;
+    }
+
+    /* handle modification of fast array elements */
+    if (p->fast_array) {
+        uint32_t idx;
+        uint32_t prop_flags;
+        if (p->class_id == JS_CLASS_ARRAY) {
+            if (__JS_AtomIsTaggedInt(prop)) {
+                idx = __JS_AtomToUInt32(prop);
+                if (idx < p->u.array.count) {
+                    prop_flags = get_prop_flags(flags, JS_PROP_C_W_E);
+                    if (prop_flags != JS_PROP_C_W_E)
+                        goto convert_to_slow_array;
+                    if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+                    convert_to_slow_array:
+                        if (convert_fast_array_to_array(ctx, p))
+                            return -1;
+                        else
+                            goto redo_prop_update;
+                    }
+                    if (flags & JS_PROP_HAS_VALUE) {
+                        set_value(ctx, &p->u.array.u.values[idx], JS_DupValue(ctx, val));
+                    }
+                    return TRUE;
+                }
+            }
+        } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                   p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+            JSValue num;
+            int ret;
+
+            if (!__JS_AtomIsTaggedInt(prop)) {
+                /* slow path with to handle all numeric indexes */
+                num = JS_AtomIsNumericIndex1(ctx, prop);
+                if (JS_IsUndefined(num))
+                    goto typed_array_done;
+                if (JS_IsException(num))
+                    return -1;
+                ret = JS_NumberIsInteger(ctx, num);
+                if (ret < 0) {
+                    JS_FreeValue(ctx, num);
+                    return -1;
+                }
+                if (!ret) {
+                    JS_FreeValue(ctx, num);
+                    return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array");
+                }
+                ret = JS_NumberIsNegativeOrMinusZero(ctx, num);
+                JS_FreeValue(ctx, num);
+                if (ret) {
+                    return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array");
+                }
+                if (!__JS_AtomIsTaggedInt(prop))
+                    goto typed_array_oob;
+            }
+            idx = __JS_AtomToUInt32(prop);
+            /* if the typed array is detached, p->u.array.count = 0 */
+            if (idx >= typed_array_get_length(ctx, p)) {
+            typed_array_oob:
+                return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array");
+            }
+            prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE);
+            if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) ||
+                prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE)) {
+                return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags");
+            }
+            if (flags & JS_PROP_HAS_VALUE) {
+                return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), JS_DupValue(ctx, val), flags);
+            }
+            return TRUE;
+        typed_array_done: ;
+        }
+    }
+
+    return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags);
+}
+
+static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj,
+                                     JSAtom prop, int (*init_func)(JSContext *ctx, JSObject *obj,
+                                                                   JSAtom prop, void *opaque),
+                                     void *opaque, int flags)
+{
+    JSObject *p;
+    JSProperty *pr;
+
+    if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT)
+        return FALSE;
+
+    p = JS_VALUE_GET_OBJ(this_obj);
+
+    if (find_own_property(&pr, p, prop)) {
+        /* property already exists */
+        abort();
+        return FALSE;
+    }
+
+    /* Specialized CreateProperty */
+    pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT);
+    if (unlikely(!pr))
+        return -1;
+
+    pr->u.init.init_func = init_func;
+    pr->u.init.opaque = opaque;
+    return TRUE;
+}
+
+/* shortcut to add or redefine a new property value */
+int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj,
+                           JSAtom prop, JSValue val, int flags)
+{
+    int ret;
+    ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED,
+                            flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE);
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj,
+                                JSValue prop, JSValue val, int flags)
+{
+    JSAtom atom;
+    int ret;
+    atom = JS_ValueToAtom(ctx, prop);
+    JS_FreeValue(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL)) {
+        JS_FreeValue(ctx, val);
+        return -1;
+    }
+    ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags);
+    JS_FreeAtom(ctx, atom);
+    return ret;
+}
+
+int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj,
+                                 uint32_t idx, JSValue val, int flags)
+{
+    return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewUint32(ctx, idx),
+                                       val, flags);
+}
+
+int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj,
+                                int64_t idx, JSValue val, int flags)
+{
+    return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx),
+                                       val, flags);
+}
+
+int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj,
+                              const char *prop, JSValue val, int flags)
+{
+    JSAtom atom;
+    int ret;
+    atom = JS_NewAtom(ctx, prop);
+    ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags);
+    JS_FreeAtom(ctx, atom);
+    return ret;
+}
+
+/* shortcut to add getter & setter */
+int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj,
+                            JSAtom prop, JSValue getter, JSValue setter,
+                            int flags)
+{
+    int ret;
+    ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter,
+                            flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET |
+                            JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE);
+    JS_FreeValue(ctx, getter);
+    JS_FreeValue(ctx, setter);
+    return ret;
+}
+
+static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj,
+                                       int64_t idx, JSValue val, int flags)
+{
+    return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx),
+                                       val, flags | JS_PROP_CONFIGURABLE |
+                                       JS_PROP_ENUMERABLE | JS_PROP_WRITABLE);
+}
+
+
+/* return TRUE if 'obj' has a non empty 'name' string */
+static BOOL js_object_has_name(JSContext *ctx, JSValueConst obj)
+{
+    JSProperty *pr;
+    JSShapeProperty *prs;
+    JSValueConst val;
+    JSString *p;
+    
+    prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name);
+    if (!prs)
+        return FALSE;
+    if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL)
+        return TRUE;
+    val = pr->u.value;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING)
+        return TRUE;
+    p = JS_VALUE_GET_STRING(val);
+    return (p->len != 0);
+}
+
+static int JS_DefineObjectName(JSContext *ctx, JSValueConst obj,
+                               JSAtom name, int flags)
+{
+    if (name != JS_ATOM_NULL
+    &&  JS_IsObject(obj)
+    &&  !js_object_has_name(ctx, obj)
+    &&  JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) {
+        return -1;
+    }
+    return 0;
+}
+
+static int JS_DefineObjectNameComputed(JSContext *ctx, JSValueConst obj,
+                                       JSValueConst str, int flags)
+{
+    if (JS_IsObject(obj) &&
+        !js_object_has_name(ctx, obj)) {
+        JSAtom prop;
+        JSValue name_str;
+        prop = JS_ValueToAtom(ctx, str);
+        if (prop == JS_ATOM_NULL)
+            return -1;
+        name_str = js_get_function_name(ctx, prop);
+        JS_FreeAtom(ctx, prop);
+        if (JS_IsException(name_str))
+            return -1;
+        if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+#define DEFINE_GLOBAL_LEX_VAR (1 << 7)
+#define DEFINE_GLOBAL_FUNC_VAR (1 << 6)
+
+static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    return JS_ThrowSyntaxError(ctx, "redeclaration of %s",
+                               JS_AtomGetStr(ctx, buf, sizeof(buf), prop));
+}
+
+/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */
+/* XXX: could support exotic global object. */
+static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    char buf[ATOM_GET_STR_BUF_SIZE];
+
+    p = JS_VALUE_GET_OBJ(ctx->global_obj);
+    prs = find_own_property1(p, prop);
+    /* XXX: should handle JS_PROP_AUTOINIT */
+    if (flags & DEFINE_GLOBAL_LEX_VAR) {
+        if (prs && !(prs->flags & JS_PROP_CONFIGURABLE))
+            goto fail_redeclaration;
+    } else {
+        if (!prs && !p->extensible)
+            goto define_error;
+        if (flags & DEFINE_GLOBAL_FUNC_VAR) {
+            if (prs) {
+                if (!(prs->flags & JS_PROP_CONFIGURABLE) &&
+                    ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET ||
+                     ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) !=
+                      (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) {
+                define_error:
+                    JS_ThrowTypeError(ctx, "cannot define variable %s",
+                                      JS_AtomGetStr(ctx, buf, sizeof(buf), prop));
+                    return -1;
+                }
+            }
+        }
+    }
+    /* check if there already is a lexical declaration */
+    p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
+    prs = find_own_property1(p, prop);
+    if (prs) {
+    fail_redeclaration:
+        JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop);
+        return -1;
+    }
+    return 0;
+}
+
+/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) |
+   JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */
+/* XXX: could support exotic global object. */
+static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    JSValue val;
+    int flags;
+
+    if (def_flags & DEFINE_GLOBAL_LEX_VAR) {
+        p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
+        flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) |
+            JS_PROP_CONFIGURABLE;
+        val = JS_UNINITIALIZED;
+    } else {
+        p = JS_VALUE_GET_OBJ(ctx->global_obj);
+        flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE |
+            (def_flags & JS_PROP_CONFIGURABLE);
+        val = JS_UNDEFINED;
+    }
+    prs = find_own_property1(p, prop);
+    if (prs)
+        return 0;
+    if (!p->extensible)
+        return 0;
+    pr = add_property(ctx, p, prop, flags);
+    if (unlikely(!pr))
+        return -1;
+    pr->u.value = val;
+    return 0;
+}
+
+/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */
+/* XXX: could support exotic global object. */
+static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop,
+                                   JSValueConst func, int def_flags)
+{
+
+    JSObject *p;
+    JSShapeProperty *prs;
+    int flags;
+
+    p = JS_VALUE_GET_OBJ(ctx->global_obj);
+    prs = find_own_property1(p, prop);
+    flags = JS_PROP_HAS_VALUE | JS_PROP_THROW;
+    if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) {
+        flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags |
+            JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE;
+    }
+    if (JS_DefineProperty(ctx, ctx->global_obj, prop, func,
+                          JS_UNDEFINED, JS_UNDEFINED, flags) < 0)
+        return -1;
+    return 0;
+}
+
+static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop,
+                               BOOL throw_ref_error)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+
+    /* no exotic behavior is possible in global_var_obj */
+    p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        /* XXX: should handle JS_PROP_TMASK properties */
+        if (unlikely(JS_IsUninitialized(pr->u.value)))
+            return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+        return JS_DupValue(ctx, pr->u.value);
+    }
+    return JS_GetPropertyInternal(ctx, ctx->global_obj, prop,
+                                 ctx->global_obj, throw_ref_error);
+}
+
+/* construct a reference to a global variable */
+static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+
+    /* no exotic behavior is possible in global_var_obj */
+    p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        /* XXX: should handle JS_PROP_AUTOINIT properties? */
+        /* XXX: conformance: do these tests in
+           OP_put_var_ref/OP_get_var_ref ? */
+        if (unlikely(JS_IsUninitialized(pr->u.value))) {
+            JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+            return -1;
+        }
+        if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) {
+            return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop);
+        }
+        sp[0] = JS_DupValue(ctx, ctx->global_var_obj);
+    } else {
+        int ret;
+        ret = JS_HasProperty(ctx, ctx->global_obj, prop);
+        if (ret < 0)
+            return -1;
+        if (ret) {
+            sp[0] = JS_DupValue(ctx, ctx->global_obj);
+        } else {
+            sp[0] = JS_UNDEFINED;
+        }
+    }
+    sp[1] = JS_AtomToValue(ctx, prop);
+    return 0;
+}
+
+/* use for strict variable access: test if the variable exists */
+static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    int ret;
+
+    /* no exotic behavior is possible in global_var_obj */
+    p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
+    prs = find_own_property1(p, prop);
+    if (prs) {
+        ret = TRUE;
+    } else {
+        ret = JS_HasProperty(ctx, ctx->global_obj, prop);
+        if (ret < 0)
+            return -1;
+    }
+    return ret;
+}
+
+/* flag = 0: normal variable write
+   flag = 1: initialize lexical variable
+   flag = 2: normal variable write, strict check was done before
+*/
+static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val,
+                           int flag)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    int flags;
+
+    /* no exotic behavior is possible in global_var_obj */
+    p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
+    prs = find_own_property(&pr, p, prop);
+    if (prs) {
+        /* XXX: should handle JS_PROP_AUTOINIT properties? */
+        if (flag != 1) {
+            if (unlikely(JS_IsUninitialized(pr->u.value))) {
+                JS_FreeValue(ctx, val);
+                JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
+                return -1;
+            }
+            if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) {
+                JS_FreeValue(ctx, val);
+                return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop);
+            }
+        }
+        set_value(ctx, &pr->u.value, val);
+        return 0;
+    }
+
+    flags = JS_PROP_THROW_STRICT;
+    if (flag != 2 && is_strict_mode(ctx))
+        flags |= JS_PROP_NO_ADD;
+    return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags);
+}
+
+/* return -1, FALSE or TRUE. return FALSE if not configurable or
+   invalid object. return -1 in case of exception.
+   flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */
+int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags)
+{
+    JSValue obj1;
+    JSObject *p;
+    int res;
+    
+    obj1 = JS_ToObject(ctx, obj);
+    if (JS_IsException(obj1))
+        return -1;
+    p = JS_VALUE_GET_OBJ(obj1);
+    res = delete_property(ctx, p, prop);
+    JS_FreeValue(ctx, obj1);
+    if (res != FALSE)
+        return res;
+    if ((flags & JS_PROP_THROW) ||
+        ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
+        JS_ThrowTypeError(ctx, "could not delete property");
+        return -1;
+    }
+    return FALSE;
+}
+
+int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags)
+{
+    JSAtom prop;
+    int res;
+
+    if ((uint64_t)idx <= JS_ATOM_MAX_INT) {
+        /* fast path for fast arrays */
+        return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags);
+    }
+    prop = JS_NewAtomInt64(ctx, idx);
+    if (prop == JS_ATOM_NULL)
+        return -1;
+    res = JS_DeleteProperty(ctx, obj, prop, flags);
+    JS_FreeAtom(ctx, prop);
+    return res;
+}
+
+BOOL JS_IsFunction(JSContext *ctx, JSValueConst val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(val);
+    switch(p->class_id) {
+    case JS_CLASS_BYTECODE_FUNCTION:
+    case JS_CLASS_C_FUNCTION:
+    case JS_CLASS_BOUND_FUNCTION:
+        return TRUE;
+    case JS_CLASS_PROXY:
+        return p->u.proxy_data->is_func;
+    default:
+        return (ctx->rt->class_array[p->class_id].call != NULL);
+    }
+}
+
+BOOL JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, int magic)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(val);
+    if (p->class_id == JS_CLASS_C_FUNCTION)
+        return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic);
+    else
+        return FALSE;
+}
+
+BOOL JS_IsConstructor(JSContext *ctx, JSValueConst val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(val);
+    return p->is_constructor;
+}
+
+static BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj,
+                                 BOOL val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(func_obj);
+    p->is_constructor = val;
+    return TRUE;
+}
+
+BOOL JS_IsError(JSContext *ctx, JSValueConst val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(val);
+    if (p->class_id == JS_CLASS_ERROR)
+        return TRUE;
+    if (ctx->is_error_property_enabled) {
+        /* check for a special property for test262 test suites */
+        JSValue isError;
+        isError = JS_GetPropertyStr(ctx, val, "isError");
+        return JS_ToBoolFree(ctx, isError);
+    } else {
+        return FALSE;
+    }
+}
+
+/* only used for test262 test suites */
+void JS_EnableIsErrorProperty(JSContext *ctx, BOOL enable)
+{
+    ctx->is_error_property_enabled = enable;
+}
+
+/* used to avoid catching interrupt exceptions */
+BOOL JS_IsUncatchableError(JSContext *ctx, JSValueConst val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return FALSE;
+    p = JS_VALUE_GET_OBJ(val);
+    return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error;
+}
+
+void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return;
+    p = JS_VALUE_GET_OBJ(val);
+    if (p->class_id == JS_CLASS_ERROR)
+        p->is_uncatchable_error = flag;
+}
+
+void JS_ResetUncatchableError(JSContext *ctx)
+{
+    JS_SetUncatchableError(ctx, ctx->current_exception, FALSE);
+}
+
+void JS_SetOpaque(JSValue obj, void *opaque)
+{
+   JSObject *p;
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        p = JS_VALUE_GET_OBJ(obj);
+        p->u.opaque = opaque;
+    }
+}
+
+/* return NULL if not an object of class class_id */
+void *JS_GetOpaque(JSValueConst obj, JSClassID class_id)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return NULL;
+    p = JS_VALUE_GET_OBJ(obj);
+    if (p->class_id != class_id)
+        return NULL;
+    return p->u.opaque;
+}
+
+void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id)
+{
+    void *p = JS_GetOpaque(obj, class_id);
+    if (unlikely(!p)) {
+        JS_ThrowTypeErrorInvalidClass(ctx, class_id);
+    }
+    return p;
+}
+
+#define HINT_STRING  0
+#define HINT_NUMBER  1
+#define HINT_NONE    2
+#ifdef CONFIG_BIGNUM
+#define HINT_INTEGER 3
+#endif
+/* don't try Symbol.toPrimitive */
+#define HINT_FORCE_ORDINARY (1 << 4)
+
+static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint)
+{
+    int i;
+    BOOL force_ordinary;
+
+    JSAtom method_name;
+    JSValue method, ret;
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
+        return val;
+    force_ordinary = hint & HINT_FORCE_ORDINARY;
+    hint &= ~HINT_FORCE_ORDINARY;
+    if (!force_ordinary) {
+        method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive);
+        if (JS_IsException(method))
+            goto exception;
+        /* ECMA says *If exoticToPrim is not undefined* but tests in
+           test262 use null as a non callable converter */
+        if (!JS_IsUndefined(method) && !JS_IsNull(method)) {
+            JSAtom atom;
+            JSValue arg;
+            switch(hint) {
+            case HINT_STRING:
+                atom = JS_ATOM_string;
+                break;
+            case HINT_NUMBER:
+                atom = JS_ATOM_number;
+                break;
+            default:
+            case HINT_NONE:
+                atom = JS_ATOM_default;
+                break;
+#ifdef CONFIG_BIGNUM
+            case HINT_INTEGER:
+                atom = JS_ATOM_integer;
+                break;
+#endif
+            }
+            arg = JS_AtomToString(ctx, atom);
+            ret = JS_CallFree(ctx, method, val, 1, (JSValueConst *)&arg);
+            JS_FreeValue(ctx, arg);
+            if (JS_IsException(ret))
+                goto exception;
+            JS_FreeValue(ctx, val);
+            if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT)
+                return ret;
+            JS_FreeValue(ctx, ret);
+            return JS_ThrowTypeError(ctx, "toPrimitive");
+        }
+    }
+    if (hint != HINT_STRING)
+        hint = HINT_NUMBER;
+    for(i = 0; i < 2; i++) {
+        if ((i ^ hint) == 0) {
+            method_name = JS_ATOM_toString;
+        } else {
+            method_name = JS_ATOM_valueOf;
+        }
+        method = JS_GetProperty(ctx, val, method_name);
+        if (JS_IsException(method))
+            goto exception;
+        if (JS_IsFunction(ctx, method)) {
+            ret = JS_CallFree(ctx, method, val, 0, NULL);
+            if (JS_IsException(ret))
+                goto exception;
+            if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) {
+                JS_FreeValue(ctx, val);
+                return ret;
+            }
+            JS_FreeValue(ctx, ret);
+        } else {
+            JS_FreeValue(ctx, method);
+        }
+    }
+    JS_ThrowTypeError(ctx, "toPrimitive");
+exception:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint)
+{
+    return JS_ToPrimitiveFree(ctx, JS_DupValue(ctx, val), hint);
+}
+
+static int JS_ToBoolFree(JSContext *ctx, JSValue val)
+{
+    uint32_t tag = JS_VALUE_GET_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+        return JS_VALUE_GET_INT(val) != 0;
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        return JS_VALUE_GET_INT(val);
+    case JS_TAG_EXCEPTION:
+        return -1;
+    case JS_TAG_STRING:
+        {
+            BOOL ret = JS_VALUE_GET_STRING(val)->len != 0;
+            JS_FreeValue(ctx, val);
+            return ret;
+        }
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            BOOL ret;
+            ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN;
+            JS_FreeValue(ctx, val);
+            return ret;
+        }
+#endif
+    default:
+        if (JS_TAG_IS_FLOAT64(tag)) {
+            double d = JS_VALUE_GET_FLOAT64(val);
+            return !isnan(d) && d != 0;
+        } else {
+            JS_FreeValue(ctx, val);
+            return TRUE;
+        }
+    }
+}
+
+int JS_ToBool(JSContext *ctx, JSValueConst val)
+{
+    return JS_ToBoolFree(ctx, JS_DupValue(ctx, val));
+}
+
+static int skip_spaces(const char *pc)
+{
+    const uint8_t *p, *p_next, *p_start;
+    uint32_t c;
+
+    p = p_start = (const uint8_t *)pc;
+    for (;;) {
+        c = *p;
+        if (c < 128) {
+            if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20)))
+                break;
+            p++;
+        } else {
+            c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next);
+            if (!lre_is_space(c))
+                break;
+            p = p_next;
+        }
+    }
+    return p - p_start;
+}
+
+#ifdef CONFIG_BIGNUM
+
+/* force big int type if integer result */
+#define BF_ATOF_BIG_INT     (1 << 30)
+/* return JS_EXCEPTION if invalid syntax. Otherwise return NaN */
+#define BF_ATOF_THROW       (1 << 29)
+#define BF_ATOF_FLOAT64     (1 << 28)
+
+static JSValue js_atof(JSContext *ctx, const char *p, const char **pp,
+                       int radix, int flags)
+{
+    bf_t a_s, *a = &a_s;
+    int res;
+    slimb_t prec;
+    JSValue val;
+
+    bf_init(ctx->bf_ctx, a);
+    if (flags & BF_ATOF_FLOAT64) {
+        prec = 53;
+        flags |= bf_set_exp_bits(11) | BF_RNDN | BF_FLAG_SUBNORMAL;
+    } else {
+        /* use the current precision */
+        prec = ctx->fp_env.prec;
+        flags |= ctx->fp_env.flags;
+    }
+    p += skip_spaces(p);
+    res = bf_atof(a, p, &p, radix, prec, flags);
+    if ((flags & BF_ATOF_THROW) && bf_is_nan(a)) {
+        bf_delete(a);
+        return JS_EXCEPTION;
+    }
+    if (res & BF_ATOF_ST_INTEGER) {
+        val = JS_NewBigInt2(ctx, a, (flags & BF_ATOF_BIG_INT) != 0);
+    } else if (flags & BF_ATOF_FLOAT64) {
+        double d;
+        bf_get_float64(a, &d, BF_RNDN);
+        bf_delete(a);
+        /* return int or float64 */
+        val = JS_NewFloat64(ctx, d);
+    } else {
+        val = JS_NewBigFloat(ctx, a);
+    }
+    if (pp)
+        *pp = p;
+    return val;
+}
+
+#else
+
+static inline int to_digit(int c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    else if (c >= 'A' && c <= 'Z')
+        return c - 'A' + 10;
+    else if (c >= 'a' && c <= 'z')
+        return c - 'a' + 10;
+    else
+        return 36;
+}
+
+#define ATOD_INT_ONLY        (1 << 0)
+/* return JS_EXCEPTION if invalid syntax. Otherwise return NaN */
+#define ATOD_THROW           (1 << 1)
+/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
+#define ATOD_ACCEPT_BIN_OCT  (1 << 2)
+/* if set return NaN if empty number string */
+#define ATOD_NAN_IF_EMPTY    (1 << 3)
+/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
+#define ATOD_ACCEPT_LEGACY_OCTAL  (1 << 4)
+/* accept _ between digits as a digit separator */
+#define ATOD_ACCEPT_UNDERSCORES  (1 << 5)
+
+/* radix = 0 accepts prefixes. radix = 16 also
+   accepts 0x prefix. radix must be 0 or between 2 and 36 */
+static JSValue js_atod(JSContext *ctx, const char *str, const char **pp,
+                       int radix, int flags)
+{
+    const char *p;
+    const char *p_start;
+    int is_neg, c, sep;
+    double d;
+
+    /* optional separator between digits */
+    sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256;
+
+    p = str + skip_spaces(str);
+    p_start = p;
+    is_neg = 0;
+    if (p[0] == '+') {
+        p++;
+        p_start++;
+        if (!(flags & ATOD_INT_ONLY))
+            goto no_radix_prefix;
+    } else if (p[0] == '-') {
+        is_neg = 1;
+        p++;
+        p_start++;
+        if (!(flags & ATOD_INT_ONLY))
+            goto no_radix_prefix;
+    }
+    if (p[0] == '0') {
+        if ((p[1] == 'x' || p[1] == 'X') &&
+            (radix == 0 || radix == 16)) {
+            p += 2;
+            radix = 16;
+        } else if ((p[1] == 'o' || p[1] == 'O') &&
+                   radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) {
+            p += 2;
+            radix = 8;
+        } else if ((p[1] == 'b' || p[1] == 'B') &&
+                   radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) {
+            p += 2;
+            radix = 2;
+        } else if ((p[1] >= '0' && p[1] <= '9') &&
+                   radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) {
+            int i;
+            sep = 256;
+            for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++)
+                continue;
+            if (p[i] == '8' || p[i] == '9')
+                goto no_prefix;
+            p += 1;
+            radix = 8;
+        } else {
+            goto no_prefix;
+        }
+        /* there must be a digit after the prefix */
+        if (to_digit((uint8_t)*p) >= radix)
+            goto fail;
+    no_prefix: ;
+    } else {
+ no_radix_prefix:
+        if (!(flags & ATOD_INT_ONLY) && strstart(p, "Infinity", &p)) {
+            d = 1.0 / 0.0;
+            goto done;
+        }
+    }
+    if (radix == 0)
+        radix = 10;
+    if ((flags & ATOD_INT_ONLY) || radix != 10) {
+        uint64_t n_max, n;
+        int int_exp;
+
+        /* skip leading zeros */
+        while (*p == '0')
+            p++;
+        n = 0;
+        if (radix == 10)
+            n_max = ((uint64_t)-1 - 9) / 10; /* most common case */
+        else
+            n_max = ((uint64_t)-1 - (radix - 1)) / radix;
+        /* XXX: could be more precise */
+        int_exp = 0;
+        while (*p != '\0') {
+            if (*p == sep && to_digit(p[1]) < radix)
+                p++;
+            c = to_digit((uint8_t)*p);
+            if (c >= radix)
+                break;
+            if (n <= n_max) {
+                n = n * radix + c;
+            } else {
+                int_exp++;
+            }
+            p++;
+        }
+        d = n;
+        if (int_exp != 0) {
+            d *= pow(radix, int_exp);
+        }
+    } else {
+        p_start = p;
+        while (is_digit((uint8_t)*p)
+           ||  (*p == sep && (p != p_start + 1 || p[-1] != '0') &&
+               is_digit((uint8_t)p[1]))) {
+            p++;
+        }
+        if (*p == '.' && (p > p_start || is_digit((uint8_t)p[1]))) {
+            p++;
+            while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1])))
+                p++;
+        }
+        if (p > p_start && (*p == 'e' || *p == 'E')) {
+            const char *p1 = p + 1;
+            if (*p1 == '+') {
+                p1++;
+            } else if (*p1 == '-') {
+                p1++;
+            }
+            if (is_digit((uint8_t)*p1)) {
+                p = p1 + 1;
+                while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1])))
+                    p++;
+            }
+        }
+        if (*p == '\0' && sep != '_') {
+            d = strtod(p_start, NULL);
+        } else {
+            char buf1[64], *buf;
+            int i, j, len;
+            BOOL buf_allocated;
+
+            buf = buf1;
+            buf_allocated = FALSE;
+            len = p - p_start;
+            if (len >= sizeof(buf1)) {
+                buf = js_malloc(ctx, len + 1);
+                if (!buf)
+                    return JS_EXCEPTION;
+                buf_allocated = TRUE;
+            }
+            for (i = j = 0; i < len; i++) {
+                if (p_start[i] != '_')
+                    buf[j++] = p_start[i];
+            }
+            buf[j] = '\0';
+            d = strtod(buf, NULL);
+            if (buf_allocated)
+                js_free(ctx, buf);
+        }
+    }
+done:
+    if (is_neg)
+        d = -d;
+    if (p == p_start && (flags & ATOD_NAN_IF_EMPTY)) {
+        d = JS_FLOAT64_NAN;
+    }
+    if (pp)
+        *pp = p;
+    return JS_NewFloat64(ctx, d);
+ fail:
+    if (pp)
+        *pp = p;
+    if (flags & ATOD_THROW)
+        return JS_EXCEPTION;
+    else
+        return JS_NAN;
+}
+#endif
+
+typedef enum JSToNumberHintEnum {
+    TON_FLAG_NUMBER,
+    TON_FLAG_INTEGER,
+    TON_FLAG_NUMERIC,
+} JSToNumberHintEnum;
+
+static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val,
+                                   JSToNumberHintEnum flag)
+{
+    uint32_t tag;
+    JSValue ret;
+    int hint;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        if (flag == TON_FLAG_NUMBER && !is_bignum_mode(ctx)) {
+            JS_FreeValue(ctx, val);
+            return JS_ThrowTypeError(ctx, "cannot convert bigint to number");
+        }
+        /* fall thru */
+    case JS_TAG_BIG_FLOAT:
+#endif
+    case JS_TAG_FLOAT64:
+    case JS_TAG_INT:
+    case JS_TAG_EXCEPTION:
+        ret = val;
+        break;
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+        ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val));
+        break;
+    case JS_TAG_UNDEFINED:
+        ret = JS_NAN;
+        break;
+    case JS_TAG_OBJECT:
+#ifdef CONFIG_BIGNUM
+        hint = flag == TON_FLAG_INTEGER ? HINT_INTEGER : HINT_NUMBER;
+#else
+        hint = HINT_NUMBER;
+#endif
+        val = JS_ToPrimitiveFree(ctx, val, hint);
+        if (JS_IsException(val))
+            return JS_EXCEPTION;
+        goto redo;
+    case JS_TAG_STRING:
+        {
+            const char *str;
+            const char *p;
+
+            str = JS_ToCString(ctx, val);
+            JS_FreeValue(ctx, val);
+            if (!str)
+                return JS_EXCEPTION;
+#ifdef CONFIG_BIGNUM
+            {
+                int flags;
+                flags = BF_ATOF_BIN_OCT | BF_ATOF_NO_PREFIX_AFTER_SIGN |
+                    BF_ATOF_JS_QUIRKS | BF_ATOF_FLOAT64;
+                if (is_bignum_mode(ctx))
+                    flags |= BF_ATOF_INT_PREC_INF;
+                else
+                    flags |= BF_ATOF_ONLY_DEC_FLOAT;
+                ret = js_atof(ctx, str, &p, 0, flags);
+            }
+#else
+            ret = js_atod(ctx, str, &p, 0, ATOD_ACCEPT_BIN_OCT);
+#endif
+            p += skip_spaces(p);
+            if (*p != '\0') {
+                JS_FreeValue(ctx, ret);
+                ret = JS_NAN;
+            }
+            JS_FreeCString(ctx, str);
+        }
+        break;
+    case JS_TAG_SYMBOL:
+        JS_FreeValue(ctx, val);
+        return JS_ThrowTypeError(ctx, "cannot convert symbol to number");
+    default:
+        JS_FreeValue(ctx, val);
+        ret = JS_NAN;
+        break;
+    }
+    return ret;
+}
+
+static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val)
+{
+    return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER);
+}
+
+#ifdef CONFIG_BIGNUM
+static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val)
+{
+    return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC);
+}
+
+static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val)
+{
+    return JS_ToNumericFree(ctx, JS_DupValue(ctx, val));
+}
+#endif
+
+static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres,
+                                          JSValue val)
+{
+    double d;
+    uint32_t tag;
+
+    val = JS_ToNumberFree(ctx, val);
+    if (JS_IsException(val)) {
+        *pres = JS_FLOAT64_NAN;
+        return -1;
+    }
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+        d = JS_VALUE_GET_INT(val);
+        break;
+    case JS_TAG_FLOAT64:
+        d = JS_VALUE_GET_FLOAT64(val);
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            /* XXX: there can be a double rounding issue with some
+               primitives (such as JS_ToUint8ClampFree()), but it is
+               not critical to fix it. */
+            bf_get_float64(&p->num, &d, BF_RNDN);
+            JS_FreeValue(ctx, val);
+        }
+        break;
+#endif
+    default:
+        abort();
+    }
+    *pres = d;
+    return 0;
+}
+
+static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val)
+{
+    uint32_t tag;
+
+    tag = JS_VALUE_GET_TAG(val);
+    if (tag <= JS_TAG_NULL) {
+        *pres = JS_VALUE_GET_INT(val);
+        return 0;
+    } else if (JS_TAG_IS_FLOAT64(tag)) {
+        *pres = JS_VALUE_GET_FLOAT64(val);
+        return 0;
+    } else {
+        return __JS_ToFloat64Free(ctx, pres, val);
+    }
+}
+
+int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val)
+{
+    return JS_ToFloat64Free(ctx, pres, JS_DupValue(ctx, val));
+}
+
+static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val)
+{
+    return JS_ToNumberFree(ctx, JS_DupValue(ctx, val));
+}
+
+static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val)
+{
+    uint32_t tag;
+    JSValue ret;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val));
+        break;
+    case JS_TAG_FLOAT64:
+        {
+            double d = JS_VALUE_GET_FLOAT64(val);
+            if (isnan(d)) {
+                ret = JS_NewInt32(ctx, 0);
+            } else {
+                /* convert -0 to +0 */
+                /* XXX: should not be done here ? */
+                d = trunc(d) + 0.0;
+                ret = JS_NewFloat64(ctx, d);
+            }
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        if (!is_bignum_mode(ctx))
+            goto to_number;
+        ret = val;
+        break;
+    case JS_TAG_BIG_FLOAT:
+        {
+            bf_t a_s, *a, r_s, *r = &r_s;
+            BOOL is_float, is_nan;
+
+            a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
+            if (!bf_is_finite(a)) {
+                is_nan = bf_is_nan(a);
+                if (is_nan)
+                    ret = JS_NewInt32(ctx, 0);
+                else
+                    ret = JS_DupValue(ctx, val);
+            } else {
+                bf_init(ctx->bf_ctx, r);
+                bf_set(r, a);
+                bf_rint(r, BF_PREC_INF, BF_RNDZ);
+                ret = JS_NewBigInt(ctx, r);
+            }
+            if (a == &a_s)
+                bf_delete(a);
+            JS_FreeValue(ctx, val);
+        }
+        break;
+#endif
+    default:
+#ifdef CONFIG_BIGNUM
+    to_number:
+#endif
+        val = JS_ToNumberFree(ctx, val);
+        if (JS_IsException(val))
+            return val;
+        goto redo;
+    }
+    return ret;
+}
+
+/* Note: the integer value is satured to 32 bits */
+static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val)
+{
+    uint32_t tag;
+    int ret;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        ret = JS_VALUE_GET_INT(val);
+        break;
+    case JS_TAG_EXCEPTION:
+        *pres = 0;
+        return -1;
+    case JS_TAG_FLOAT64:
+        {
+            double d = JS_VALUE_GET_FLOAT64(val);
+            if (isnan(d)) {
+                ret = 0;
+            } else {
+                if (d < INT32_MIN)
+                    ret = INT32_MIN;
+                else if (d > INT32_MAX)
+                    ret = INT32_MAX;
+                else
+                    ret = (int)d;
+            }
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_FLOAT:
+    to_bf:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_get_int32(&ret, &p->num, 0);
+            JS_FreeValue(ctx, val);
+        }
+        break;
+    case JS_TAG_BIG_INT:
+        if (is_bignum_mode(ctx))
+            goto to_bf;
+        /* fall thru */
+#endif
+    default:
+        val = JS_ToNumberFree(ctx, val);
+        if (JS_IsException(val)) {
+            *pres = 0;
+            return -1;
+        }
+        goto redo;
+    }
+    *pres = ret;
+    return 0;
+}
+
+int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val)
+{
+    return JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val));
+}
+
+int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val,
+                    int min, int max, int min_offset)
+{
+    int res = JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val));
+    if (res == 0) {
+        if (*pres < min) {
+            *pres += min_offset;
+            if (*pres < min)
+                *pres = min;
+        } else {
+            if (*pres > max)
+                *pres = max;
+        }
+    }
+    return res;
+}
+
+static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val)
+{
+    uint32_t tag;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        *pres = JS_VALUE_GET_INT(val);
+        return 0;
+    case JS_TAG_EXCEPTION:
+        *pres = 0;
+        return -1;
+    case JS_TAG_FLOAT64:
+        {
+            double d = JS_VALUE_GET_FLOAT64(val);
+            if (isnan(d)) {
+                *pres = 0;
+            } else {
+                if (d < INT64_MIN)
+                    *pres = INT64_MIN;
+                else if (d > INT64_MAX)
+                    *pres = INT64_MAX;
+                else
+                    *pres = (int64_t)d;
+            }
+        }
+        return 0;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_FLOAT:
+    to_bf:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_get_int64(pres, &p->num, 0);
+            JS_FreeValue(ctx, val);
+        }
+        return 0;
+    case JS_TAG_BIG_INT:
+        if (is_bignum_mode(ctx))
+            goto to_bf;
+        /* fall thru */
+#endif
+    default:
+        val = JS_ToNumberFree(ctx, val);
+        if (JS_IsException(val)) {
+            *pres = 0;
+            return -1;
+        }
+        goto redo;
+    }
+}
+
+int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val)
+{
+    return JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val));
+}
+
+int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val,
+                    int64_t min, int64_t max, int64_t neg_offset)
+{
+    int res = JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val));
+    if (res == 0) {
+        if (*pres < 0)
+            *pres += neg_offset;
+        if (*pres < min)
+            *pres = min;
+        else if (*pres > max)
+            *pres = max;
+    }
+    return res;
+}
+
+/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0)
+   in case of exception */
+static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val)
+{
+    uint32_t tag;
+    int64_t ret;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        ret = JS_VALUE_GET_INT(val);
+        break;
+    case JS_TAG_FLOAT64:
+        {
+            JSFloat64Union u;
+            double d;
+            int e;
+            d = JS_VALUE_GET_FLOAT64(val);
+            u.d = d;
+            /* we avoid doing fmod(x, 2^64) */
+            e = (u.u64 >> 52) & 0x7ff;
+            if (likely(e <= (1023 + 62))) {
+                /* fast case */
+                ret = (int64_t)d;
+            } else if (e <= (1023 + 62 + 53)) {
+                uint64_t v;
+                /* remainder modulo 2^64 */
+                v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
+                ret = v << ((e - 1023) - 52);
+                /* take the sign into account */
+                if (u.u64 >> 63)
+                    ret = -ret;
+            } else {
+                ret = 0; /* also handles NaN and +inf */
+            }
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_FLOAT:
+    to_bf:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_get_int64(&ret, &p->num, BF_GET_INT_MOD);
+            JS_FreeValue(ctx, val);
+        }
+        break;
+    case JS_TAG_BIG_INT:
+        if (is_bignum_mode(ctx))
+            goto to_bf;
+        /* fall thru */
+#endif
+    default:
+        val = JS_ToNumberFree(ctx, val);
+        if (JS_IsException(val)) {
+            *pres = 0;
+            return -1;
+        }
+        goto redo;
+    }
+    *pres = ret;
+    return 0;
+}
+
+int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val)
+{
+    return JS_ToInt64Free(ctx, pres, JS_DupValue(ctx, val));
+}
+
+/* return (<0, 0) in case of exception */
+static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val)
+{
+    uint32_t tag;
+    int32_t ret;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        ret = JS_VALUE_GET_INT(val);
+        break;
+    case JS_TAG_FLOAT64:
+        {
+            JSFloat64Union u;
+            double d;
+            int e;
+            d = JS_VALUE_GET_FLOAT64(val);
+            u.d = d;
+            /* we avoid doing fmod(x, 2^32) */
+            e = (u.u64 >> 52) & 0x7ff;
+            if (likely(e <= (1023 + 30))) {
+                /* fast case */
+                ret = (int32_t)d;
+            } else if (e <= (1023 + 30 + 53)) {
+                uint64_t v;
+                /* remainder modulo 2^32 */
+                v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
+                v = v << ((e - 1023) - 52 + 32);
+                ret = v >> 32;
+                /* take the sign into account */
+                if (u.u64 >> 63)
+                    ret = -ret;
+            } else {
+                ret = 0; /* also handles NaN and +inf */
+            }
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_FLOAT:
+    to_bf:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_get_int32(&ret, &p->num, BF_GET_INT_MOD);
+            JS_FreeValue(ctx, val);
+        }
+        break;
+    case JS_TAG_BIG_INT:
+        if (is_bignum_mode(ctx))
+            goto to_bf;
+        /* fall thru */
+#endif
+    default:
+        val = JS_ToNumberFree(ctx, val);
+        if (JS_IsException(val)) {
+            *pres = 0;
+            return -1;
+        }
+        goto redo;
+    }
+    *pres = ret;
+    return 0;
+}
+
+int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val)
+{
+    return JS_ToInt32Free(ctx, pres, JS_DupValue(ctx, val));
+}
+
+static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val)
+{
+    return JS_ToInt32Free(ctx, (int32_t *)pres, val);
+}
+
+static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val)
+{
+    uint32_t tag;
+    int res;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        res = JS_VALUE_GET_INT(val);
+#ifdef CONFIG_BIGNUM
+    int_clamp:
+#endif
+        res = max_int(0, min_int(255, res));
+        break;
+    case JS_TAG_FLOAT64:
+        {
+            double d = JS_VALUE_GET_FLOAT64(val);
+            if (isnan(d)) {
+                res = 0;
+            } else {
+                if (d < 0)
+                    res = 0;
+                else if (d > 255)
+                    res = 255;
+                else
+                    res = lrint(d);
+            }
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            if (!is_bignum_mode(ctx))
+                goto to_number;
+            bf_get_int32(&res, &p->num, 0);
+            JS_FreeValue(ctx, val);
+        }
+        goto int_clamp;
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_t r_s, *r = &r_s;
+            bf_init(ctx->bf_ctx, r);
+            bf_set(r, &p->num);
+            bf_rint(r, BF_PREC_INF, BF_RNDN);
+            bf_get_int32(&res, r, 0);
+            bf_delete(r);
+            JS_FreeValue(ctx, val);
+        }
+        goto int_clamp;
+#endif
+    default:
+#ifdef CONFIG_BIGNUM
+    to_number:
+#endif
+        val = JS_ToNumberFree(ctx, val);
+        if (JS_IsException(val)) {
+            *pres = 0;
+            return -1;
+        }
+        goto redo;
+    }
+    *pres = res;
+    return 0;
+}
+
+static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen,
+                                            JSValue val)
+{
+    uint32_t tag, len;
+
+ redo:
+    tag = JS_VALUE_GET_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+        {
+            int v;
+            v = JS_VALUE_GET_INT(val);
+            if (v < 0)
+                goto fail;
+            len = v;
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_t a;
+            BOOL res;
+            bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD);
+            bf_init(ctx->bf_ctx, &a);
+            bf_set_ui(&a, len);
+            res = bf_cmp_eq(&a, &p->num);
+            bf_delete(&a);
+            JS_FreeValue(ctx, val);
+            if (!res)
+                goto fail;
+        }
+        break;
+#endif
+    default:
+        if (JS_TAG_IS_FLOAT64(tag)) {
+            double d;
+            d = JS_VALUE_GET_FLOAT64(val);
+            len = (uint32_t)d;
+            if (len != d) {
+            fail:
+                JS_ThrowRangeError(ctx, "invalid array length");
+                return -1;
+            }
+        } else {
+            val = JS_ToNumberFree(ctx, val);
+            if (JS_IsException(val))
+                return -1;
+            goto redo;
+        }
+        break;
+    }
+    *plen = len;
+    return 0;
+}
+
+#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1)
+
+int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val)
+{
+    int64_t v;
+    if (JS_ToInt64Sat(ctx, &v, val))
+        return -1;
+    if (v < 0 || v > MAX_SAFE_INTEGER) {
+        JS_ThrowRangeError(ctx, "invalid array index");
+        *plen = 0;
+        return -1;
+    }
+    *plen = v;
+    return 0;
+}
+
+/* convert a value to a length between 0 and MAX_SAFE_INTEGER.
+   return -1 for exception */
+static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen,
+                                       JSValue val)
+{
+    int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0);
+    JS_FreeValue(ctx, val);
+    return res;
+}
+
+/* Note: can return an exception */
+/* XXX: bignum case */
+static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val)
+{
+    double d;
+    if (!JS_IsNumber(val))
+        return FALSE;
+    if (unlikely(JS_ToFloat64(ctx, &d, val)))
+        return -1;
+    return isfinite(d) && floor(d) == d;
+}
+
+static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val)
+{
+    uint32_t tag;
+
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+        {
+            int v;
+            v = JS_VALUE_GET_INT(val);
+            return (v < 0);
+        }
+    case JS_TAG_FLOAT64:
+        {
+            JSFloat64Union u;
+            u.d = JS_VALUE_GET_FLOAT64(val);
+            return (u.u64 >> 63);
+        }
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            /* Note: integer zeros are not necessarily positive */
+            return p->num.sign && !bf_is_zero(&p->num);
+        }
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            return p->num.sign;
+        }
+        break;
+#endif
+    default:
+        return FALSE;
+    }
+}
+
+#ifdef CONFIG_BIGNUM
+
+static JSValue js_ftoa(JSContext *ctx, JSValueConst val1, int radix,
+                       limb_t prec, bf_flags_t flags)
+{
+    JSValue val, ret;
+    bf_t a_s, *a;
+    BOOL is_float;
+    char *str;
+    int saved_sign;
+
+    val = JS_ToNumeric(ctx, val1);
+    if (JS_IsException(val))
+        return val;
+    a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
+    saved_sign = a->sign;
+    if (a->expn == BF_EXP_ZERO)
+        a->sign = 0;
+    flags |= BF_FTOA_JS_QUIRKS;
+    if ((flags & BF_FTOA_FORMAT_MASK) == BF_FTOA_FORMAT_FREE_MIN) {
+        /* Note: for floating point numbers with a radix which is not
+           a power of two, the current precision is used to compute
+           the number of digits. For integers, the full precision is
+           always returned. */
+        if (is_float || (flags & BF_FTOA_FORCE_EXP)) {
+            if ((radix & (radix - 1)) != 0) {
+                bf_t r_s, *r = &r_s;
+                int prec, flags1;
+                /* must round first */
+                if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) {
+                    prec = ctx->fp_env.prec;
+                    flags1 = ctx->fp_env.flags &
+                        (BF_FLAG_SUBNORMAL | (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT));
+                } else {
+                    prec = 53;
+                    flags1 = bf_set_exp_bits(11) | BF_FLAG_SUBNORMAL;
+                }
+                bf_init(ctx->bf_ctx, r);
+                bf_set(r, a);
+                bf_round(r, prec, flags1 | BF_RNDN);
+                bf_ftoa(&str, r, radix, prec, flags1 | flags);
+                bf_delete(r);
+            } else {
+                bf_ftoa(&str, a, radix, BF_PREC_INF, flags);
+            }
+        } else {
+            bf_ftoa(&str, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC | BF_FTOA_JS_QUIRKS);
+        }
+    } else {
+        bf_ftoa(&str, a, radix, prec, flags);
+    }
+    a->sign = saved_sign;
+    if (a == &a_s)
+        bf_delete(a);
+    JS_FreeValue(ctx, val);
+    ret = JS_NewString(ctx, str);
+    bf_realloc(ctx->bf_ctx, str, 0);
+    return ret;
+}
+
+#else /* !CONFIG_BIGNUM */
+
+/* 2 <= base <= 36 */
+static char *i64toa(char *buf_end, int64_t n, unsigned int base)
+{
+    char *q = buf_end;
+    int digit, is_neg;
+
+    is_neg = 0;
+    if (n < 0) {
+        is_neg = 1;
+        n = -n;
+    }
+    *--q = '\0';
+    do {
+        digit = (uint64_t)n % base;
+        n = (uint64_t)n / base;
+        if (digit < 10)
+            digit += '0';
+        else
+            digit += 'a' - 10;
+        *--q = digit;
+    } while (n != 0);
+    if (is_neg)
+        *--q = '-';
+    return q;
+}
+
+/* buf1 contains the printf result */
+static void js_ecvt1(double d, int n_digits, int *decpt, int *sign, char *buf,
+                     int rounding_mode, char *buf1, int buf1_size)
+{
+    if (rounding_mode != FE_TONEAREST)
+        fesetround(rounding_mode);
+    snprintf(buf1, buf1_size, "%+.*e", n_digits - 1, d);
+    if (rounding_mode != FE_TONEAREST)
+        fesetround(FE_TONEAREST);
+    *sign = (buf1[0] == '-');
+    /* mantissa */
+    buf[0] = buf1[1];
+    if (n_digits > 1)
+        memcpy(buf + 1, buf1 + 3, n_digits - 1);
+    buf[n_digits] = '\0';
+    /* exponent */
+    *decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1;
+}
+
+/* maximum buffer size for js_dtoa */
+#define JS_DTOA_BUF_SIZE 128
+
+/* needed because ecvt usually limits the number of digits to
+   17. Return the number of digits. */
+static int js_ecvt(double d, int n_digits, int *decpt, int *sign, char *buf,
+                   BOOL is_fixed)
+{
+    int rounding_mode;
+    char buf_tmp[JS_DTOA_BUF_SIZE];
+
+    if (!is_fixed) {
+        unsigned int n_digits_min, n_digits_max;
+        /* find the minimum amount of digits (XXX: inefficient but simple) */
+        n_digits_min = 1;
+        n_digits_max = 17;
+        while (n_digits_min < n_digits_max) {
+            n_digits = (n_digits_min + n_digits_max) / 2;
+            js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST,
+                     buf_tmp, sizeof(buf_tmp));
+            if (strtod(buf_tmp, NULL) == d) {
+                /* no need to keep the trailing zeros */
+                while (n_digits >= 2 && buf[n_digits - 1] == '0')
+                    n_digits--;
+                n_digits_max = n_digits;
+            } else {
+                n_digits_min = n_digits + 1;
+            }
+        }
+        n_digits = n_digits_max;
+        rounding_mode = FE_TONEAREST;
+    } else {
+        rounding_mode = FE_TONEAREST;
+#ifdef CONFIG_PRINTF_RNDN
+        {
+            char buf1[JS_DTOA_BUF_SIZE], buf2[JS_DTOA_BUF_SIZE];
+            int decpt1, sign1, decpt2, sign2;
+            /* The JS rounding is specified as round to nearest ties away
+               from zero (RNDNA), but in printf the "ties" case is not
+               specified (for example it is RNDN for glibc, RNDNA for
+               Windows), so we must round manually. */
+            js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_TONEAREST,
+                     buf_tmp, sizeof(buf_tmp));
+            /* XXX: could use 2 digits to reduce the average running time */
+            if (buf1[n_digits] == '5') {
+                js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_DOWNWARD,
+                         buf_tmp, sizeof(buf_tmp));
+                js_ecvt1(d, n_digits + 1, &decpt2, &sign2, buf2, FE_UPWARD,
+                         buf_tmp, sizeof(buf_tmp));
+                if (memcmp(buf1, buf2, n_digits + 1) == 0 && decpt1 == decpt2) {
+                    /* exact result: round away from zero */
+                    if (sign1)
+                        rounding_mode = FE_DOWNWARD;
+                    else
+                        rounding_mode = FE_UPWARD;
+                }
+            }
+        }
+#endif /* CONFIG_PRINTF_RNDN */
+    }
+    js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode,
+             buf_tmp, sizeof(buf_tmp));
+    return n_digits;
+}
+
+static int js_fcvt1(char *buf, int buf_size, double d, int n_digits,
+                    int rounding_mode)
+{
+    int n;
+    if (rounding_mode != FE_TONEAREST)
+        fesetround(rounding_mode);
+    n = snprintf(buf, buf_size, "%.*f", n_digits, d);
+    if (rounding_mode != FE_TONEAREST)
+        fesetround(FE_TONEAREST);
+    assert(n < buf_size);
+    return n;
+}
+
+static void js_fcvt(char *buf, int buf_size, double d, int n_digits)
+{
+    int rounding_mode;
+    rounding_mode = FE_TONEAREST;
+#ifdef CONFIG_PRINTF_RNDN
+    {
+        int n1, n2;
+        char buf1[JS_DTOA_BUF_SIZE];
+        char buf2[JS_DTOA_BUF_SIZE];
+
+        /* The JS rounding is specified as round to nearest ties away from
+           zero (RNDNA), but in printf the "ties" case is not specified
+           (for example it is RNDN for glibc, RNDNA for Windows), so we
+           must round manually. */
+        n1 = js_fcvt1(buf1, sizeof(buf1), d, n_digits + 1, FE_TONEAREST);
+        rounding_mode = FE_TONEAREST;
+        /* XXX: could use 2 digits to reduce the average running time */
+        if (buf1[n1 - 1] == '5') {
+            n1 = js_fcvt1(buf1, sizeof(buf1), d, n_digits + 1, FE_DOWNWARD);
+            n2 = js_fcvt1(buf2, sizeof(buf2), d, n_digits + 1, FE_UPWARD);
+            if (n1 == n2 && memcmp(buf1, buf2, n1) == 0) {
+                /* exact result: round away from zero */
+                if (buf1[0] == '-')
+                    rounding_mode = FE_DOWNWARD;
+                else
+                    rounding_mode = FE_UPWARD;
+            }
+        }
+    }
+#endif /* CONFIG_PRINTF_RNDN */
+    js_fcvt1(buf, buf_size, d, n_digits, rounding_mode);
+}
+
+/* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */
+/* use as many digits as necessary */
+#define JS_DTOA_VAR_FORMAT   (0 << 0)
+/* use n_digits significant digits (1 <= n_digits <= 101) */
+#define JS_DTOA_FIXED_FORMAT (1 << 0)
+/* force fractional format: [-]dd.dd with n_digits fractional digits */
+#define JS_DTOA_FRAC_FORMAT  (2 << 0)
+/* force exponential notation either in fixed or variable format */
+#define JS_DTOA_FORCE_EXP    (1 << 2)
+
+/* XXX: slow and maybe not fully correct
+   XXX: radix != 10 is only supported for small integers
+*/
+static void js_dtoa1(char *buf, double d, int radix, int n_digits, int flags)
+{
+    char *q;
+
+    if (!isfinite(d)) {
+        if (isnan(d)) {
+            strcpy(buf, "NaN");
+        } else {
+            q = buf;
+            if (d < 0)
+                *q++ = '-';
+            strcpy(q, "Infinity");
+        }
+    } else if (flags == JS_DTOA_VAR_FORMAT) {
+        int64_t i64;
+        char buf1[70], *ptr;
+        i64 = (int64_t)d;
+        if (d != i64 || i64 > MAX_SAFE_INTEGER || i64 < -MAX_SAFE_INTEGER)
+            goto generic_conv;
+        /* fast path for integers */
+        ptr = i64toa(buf1 + sizeof(buf1), i64, radix);
+        strcpy(buf, ptr);
+    } else {
+        if (d == 0.0)
+            d = 0.0; /* convert -0 to 0 */
+        if (flags == JS_DTOA_FRAC_FORMAT) {
+            js_fcvt(buf, JS_DTOA_BUF_SIZE, d, n_digits);
+        } else {
+            char buf1[JS_DTOA_BUF_SIZE];
+            int sign, decpt, k, n, i, p, n_max;
+            BOOL is_fixed;
+        generic_conv:
+            is_fixed = ((flags & 3) == JS_DTOA_FIXED_FORMAT);
+            if (is_fixed) {
+                n_max = n_digits;
+            } else {
+                n_max = 21;
+            }
+            /* the number has k digits (k >= 1) */
+            k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed);
+            n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */
+            q = buf;
+            if (sign)
+                *q++ = '-';
+            if (flags & JS_DTOA_FORCE_EXP)
+                goto force_exp;
+            if (n >= 1 && n <= n_max) {
+                if (k <= n) {
+                    memcpy(q, buf1, k);
+                    q += k;
+                    for(i = 0; i < (n - k); i++)
+                        *q++ = '0';
+                    *q = '\0';
+                } else {
+                    /* k > n */
+                    memcpy(q, buf1, n);
+                    q += n;
+                    *q++ = '.';
+                    for(i = 0; i < (k - n); i++)
+                        *q++ = buf1[n + i];
+                    *q = '\0';
+                }
+            } else if (n >= -5 && n <= 0) {
+                *q++ = '0';
+                *q++ = '.';
+                for(i = 0; i < -n; i++)
+                    *q++ = '0';
+                memcpy(q, buf1, k);
+                q += k;
+                *q = '\0';
+            } else {
+            force_exp:
+                /* exponential notation */
+                *q++ = buf1[0];
+                if (k > 1) {
+                    *q++ = '.';
+                    for(i = 1; i < k; i++)
+                        *q++ = buf1[i];
+                }
+                *q++ = 'e';
+                p = n - 1;
+                if (p >= 0)
+                    *q++ = '+';
+                sprintf(q, "%d", p);
+            }
+        }
+    }
+}
+
+static JSValue js_dtoa(JSContext *ctx,
+                       double d, int radix, int n_digits, int flags)
+{
+    char buf[JS_DTOA_BUF_SIZE];
+    js_dtoa1(buf, d, radix, n_digits, flags);
+    return JS_NewString(ctx, buf);
+}
+
+#endif /* !CONFIG_BIGNUM */
+
+JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey)
+{
+    uint32_t tag;
+    const char *str;
+    char buf[32];
+
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_STRING:
+        return JS_DupValue(ctx, val);
+    case JS_TAG_INT:
+        snprintf(buf, sizeof(buf), "%d", JS_VALUE_GET_INT(val));
+        str = buf;
+        goto new_string;
+    case JS_TAG_BOOL:
+        return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ?
+                          JS_ATOM_true : JS_ATOM_false);
+    case JS_TAG_NULL:
+        return JS_AtomToString(ctx, JS_ATOM_null);
+    case JS_TAG_UNDEFINED:
+        return JS_AtomToString(ctx, JS_ATOM_undefined);
+    case JS_TAG_EXCEPTION:
+        return JS_EXCEPTION;
+    case JS_TAG_OBJECT:
+        {
+            JSValue val1, ret;
+            val1 = JS_ToPrimitive(ctx, val, HINT_STRING);
+            if (JS_IsException(val1))
+                return val1;
+            ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey);
+            JS_FreeValue(ctx, val1);
+            return ret;
+        }
+        break;
+    case JS_TAG_FUNCTION_BYTECODE:
+        str = "[function bytecode]";
+        goto new_string;
+    case JS_TAG_SYMBOL:
+        if (is_ToPropertyKey) {
+            return JS_DupValue(ctx, val);
+        } else {
+            return JS_ThrowTypeError(ctx, "cannot convert symbol to string");
+        }
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_FLOAT64:
+    case JS_TAG_BIG_FLOAT:
+    case JS_TAG_BIG_INT:
+        return js_ftoa(ctx, val, 10, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN);
+#else
+    case JS_TAG_FLOAT64:
+        return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0,
+                       JS_DTOA_VAR_FORMAT);
+#endif
+    default:
+        str = "[unsupported type]";
+    new_string:
+        return JS_NewString(ctx, str);
+    }
+}
+
+JSValue JS_ToString(JSContext *ctx, JSValueConst val)
+{
+    return JS_ToStringInternal(ctx, val, FALSE);
+}
+
+static JSValue JS_ToStringFree(JSContext *ctx, JSValue val)
+{
+    JSValue ret;
+    ret = JS_ToString(ctx, val);
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val)
+{
+    if (JS_IsUndefined(val) || JS_IsNull(val))
+        return JS_ToStringFree(ctx, val);
+    return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL);
+}
+
+JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val)
+{
+    return JS_ToStringInternal(ctx, val, TRUE);
+}
+
+static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val)
+{
+    uint32_t tag = JS_VALUE_GET_TAG(val);
+    if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)
+        return JS_ThrowTypeError(ctx, "null or undefined are forbidden");
+    return JS_ToString(ctx, val);
+}
+
+static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1)
+{
+    JSValue val;
+    JSString *p;
+    int i;
+    uint32_t c;
+    StringBuffer b_s, *b = &b_s;
+    char buf[16];
+
+    val = JS_ToStringCheckObject(ctx, val1);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_STRING(val);
+
+    if (string_buffer_init(ctx, b, p->len + 2))
+        goto fail;
+
+    if (string_buffer_putc8(b, '\"'))
+        goto fail;
+    for(i = 0; i < p->len; ) {
+        c = string_getc(p, &i);
+        switch(c) {
+        case '\t':
+            c = 't';
+            goto quote;
+        case '\r':
+            c = 'r';
+            goto quote;
+        case '\n':
+            c = 'n';
+            goto quote;
+        case '\b':
+            c = 'b';
+            goto quote;
+        case '\f':
+            c = 'f';
+            goto quote;
+        case '\"':
+        case '\\':
+        quote:
+            if (string_buffer_putc8(b, '\\'))
+                goto fail;
+            if (string_buffer_putc8(b, c))
+                goto fail;
+            break;
+        default:
+            if (c < 32 || (c >= 0xd800 && c < 0xe000)) {
+                snprintf(buf, sizeof(buf), "\\u%04x", c);
+                if (string_buffer_puts8(b, buf))
+                    goto fail;
+            } else {
+                if (string_buffer_putc(b, c))
+                    goto fail;
+            }
+            break;
+        }
+    }
+    if (string_buffer_putc8(b, '\"'))
+        goto fail;
+    JS_FreeValue(ctx, val);
+    return string_buffer_end(b);
+ fail:
+    JS_FreeValue(ctx, val);
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt)
+{
+    printf("%14s %4s %4s %14s %10s %s\n",
+           "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS");
+}
+
+/* for debug only: dump an object without side effect */
+static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p)
+{
+    uint32_t i;
+    char atom_buf[ATOM_GET_STR_BUF_SIZE];
+    JSShape *sh;
+    JSShapeProperty *prs;
+    JSProperty *pr;
+    BOOL is_first = TRUE;
+
+    /* XXX: should encode atoms with special characters */
+    sh = p->shape; /* the shape can be NULL while freeing an object */
+    printf("%14p %4d ",
+           (void *)p,
+           p->header.ref_count);
+    if (sh) {
+        printf("%3d%c %14p ",
+               sh->header.ref_count,
+               " *"[sh->is_hashed],
+               (void *)sh->proto);
+    } else {
+        printf("%3s  %14s ", "-", "-");
+    }
+    printf("%10s ",
+           JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name));
+    if (p->is_exotic && p->fast_array) {
+        printf("[ ");
+        for(i = 0; i < p->u.array.count; i++) {
+            if (i != 0)
+                printf(", ");
+            switch (p->class_id) {
+            case JS_CLASS_ARRAY:
+            case JS_CLASS_ARGUMENTS:
+                JS_DumpValueShort(rt, p->u.array.u.values[i]);
+                break;
+            case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_FLOAT64_ARRAY:
+                {
+                    int size = 1 << typed_array_size_log2(p->class_id);
+                    const uint8_t *b = p->u.array.u.uint8_ptr + i * size;
+                    while (size-- > 0)
+                        printf("%02X", *b++);
+                }
+                break;
+            }
+        }
+        printf(" ] ");
+    }
+
+    if (sh) {
+        printf("{ ");
+        for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
+            if (prs->atom != JS_ATOM_NULL) {
+                pr = &p->prop[i];
+                if (!is_first)
+                    printf(", ");
+                printf("%s: ",
+                       JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom));
+                if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
+                    printf("[getset %p %p]", (void *)pr->u.getset.getter,
+                           (void *)pr->u.getset.setter);
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
+                    printf("[varref %p]", (void *)pr->u.var_ref);
+                } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
+                    printf("[autoinit %p %p]", (void *)pr->u.init.init_func,
+                           (void *)pr->u.init.opaque);
+                } else {
+                    JS_DumpValueShort(rt, pr->u.value);
+                }
+                is_first = FALSE;
+            }
+        }
+        printf(" }");
+    }
+    
+    if (js_class_has_bytecode(p->class_id)) {
+        JSFunctionBytecode *b = p->u.func.function_bytecode;
+        JSVarRef **var_refs;
+        if (b->closure_var_count) {
+            var_refs = p->u.func.var_refs;
+            printf(" Closure:");
+            for(i = 0; i < b->closure_var_count; i++) {
+                printf(" ");
+                JS_DumpValueShort(rt, var_refs[i]->value);
+            }
+            if (p->u.func.home_object) {
+                printf(" HomeObject: ");
+                JS_DumpValueShort(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object));
+            }
+        }
+    }
+    printf("\n");
+}
+
+static __maybe_unused void JS_DumpValueShort(JSRuntime *rt,
+                                                      JSValueConst val)
+{
+    uint32_t tag = JS_VALUE_GET_NORM_TAG(val);
+    const char *str;
+
+    switch(tag) {
+    case JS_TAG_INT:
+        printf("%d", JS_VALUE_GET_INT(val));
+        break;
+    case JS_TAG_BOOL:
+        if (JS_VALUE_GET_BOOL(val))
+            str = "true";
+        else
+            str = "false";
+        goto print_str;
+    case JS_TAG_NULL:
+        str = "null";
+        goto print_str;
+    case JS_TAG_EXCEPTION:
+        str = "exception";
+        goto print_str;
+    case JS_TAG_UNINITIALIZED:
+        str = "uninitialized";
+        goto print_str;
+    case JS_TAG_UNDEFINED:
+        str = "undefined";
+    print_str:
+        printf("%s", str);
+        break;
+    case JS_TAG_FLOAT64:
+        printf("%.14g", JS_VALUE_GET_FLOAT64(val));
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            char *str;
+            bf_ftoa(&str, &p->num, 10, 0,
+                    BF_RNDZ | BF_FTOA_FORMAT_FRAC);
+            printf("%sn", str);
+            bf_realloc(&rt->bf_ctx, str, 0);
+        }
+        break;
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            char *str;
+            bf_ftoa(&str, &p->num, 16, BF_PREC_INF,
+                    BF_RNDZ | BF_FTOA_FORMAT_FREE | BF_FTOA_ADD_PREFIX);
+            printf("%sl", str);
+            bf_realloc(&rt->bf_ctx, str, 0);
+        }
+        break;
+#endif
+    case JS_TAG_STRING:
+        {
+            JSString *p;
+            p = JS_VALUE_GET_STRING(val);
+            JS_DumpString(rt, p);
+        }
+        break;
+    case JS_TAG_FUNCTION_BYTECODE:
+        {
+            JSFunctionBytecode *b = JS_VALUE_GET_PTR(val);
+            char buf[ATOM_GET_STR_BUF_SIZE];
+            printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name));
+        }
+        break;
+    case JS_TAG_OBJECT:
+        {
+            JSObject *p = JS_VALUE_GET_OBJ(val);
+            JSAtom atom = rt->class_array[p->class_id].class_name;
+            char atom_buf[ATOM_GET_STR_BUF_SIZE];
+            printf("[%s %p]",
+                   JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p);
+        }
+        break;
+    case JS_TAG_SYMBOL:
+        {
+            JSAtomStruct *p = JS_VALUE_GET_PTR(val);
+            char atom_buf[ATOM_GET_STR_BUF_SIZE];
+            printf("Symbol(%s)",
+                   JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p)));
+        }
+        break;
+    case JS_TAG_MODULE:
+        printf("[module]");
+        break;
+    case JS_TAG_VAR_REF:
+        printf("[var_ref]");
+        break;
+    default:
+        printf("[unknown tag %d]", tag);
+        break;
+    }
+}
+
+static __maybe_unused void JS_DumpValue(JSContext *ctx,
+                                                 JSValueConst val)
+{
+    JS_DumpValueShort(ctx->rt, val);
+}
+
+static __maybe_unused void JS_PrintValue(JSContext *ctx,
+                                                  const char *str,
+                                                  JSValueConst val)
+{
+    printf("%s=", str);
+    JS_DumpValueShort(ctx->rt, val);
+    printf("\n");
+}
+
+/* return -1 if exception (proxy case) or TRUE/FALSE */
+int JS_IsArray(JSContext *ctx, JSValueConst val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) {
+        p = JS_VALUE_GET_OBJ(val);
+        if (unlikely(p->class_id == JS_CLASS_PROXY))
+            return js_proxy_isArray(ctx, val);
+        else
+            return p->class_id == JS_CLASS_ARRAY;
+    } else {
+        return FALSE;
+    }
+}
+
+static double js_pow(double a, double b)
+{
+    if (unlikely(!isfinite(b)) && fabs(a) == 1) {
+        /* not compatible with IEEE 754 */
+        return JS_FLOAT64_NAN;
+    } else {
+        return pow(a, b);
+    }
+}
+
+#ifdef CONFIG_BIGNUM
+
+JSValue JS_NewBigInt64(JSContext *ctx, int64_t v)
+{
+    BOOL is_bignum = is_bignum_mode(ctx);
+    if (is_bignum && v == (int32_t)v) {
+        return JS_NewInt32(ctx, v);
+    } else {
+        bf_t a_s, *a = &a_s;
+        bf_init(ctx->bf_ctx, a);
+        bf_set_si(a, v);
+        return JS_NewBigInt2(ctx, a, TRUE);
+    }
+}
+
+JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v)
+{
+    BOOL is_bignum = is_bignum_mode(ctx);
+    if (is_bignum && v == (int32_t)v) {
+        return JS_NewInt32(ctx, v);
+    } else {
+        bf_t a_s, *a = &a_s;
+        bf_init(ctx->bf_ctx, a);
+        bf_set_ui(a, v);
+        return JS_NewBigInt2(ctx, a, TRUE);
+    }
+}
+
+/* if the returned bigfloat is allocated it is equal to
+   'buf'. Otherwise it is a pointer to the bigfloat in 'val'. */
+static bf_t *JS_ToBigFloat(JSContext *ctx, BOOL *pis_float, bf_t *buf,
+                           JSValueConst val)
+{
+    uint32_t tag;
+    bf_t *r;
+    BOOL is_float;
+    JSBigFloat *p;
+
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+        r = buf;
+        bf_init(ctx->bf_ctx, r);
+        bf_set_si(r, JS_VALUE_GET_INT(val));
+        is_float = FALSE;
+        break;
+    case JS_TAG_FLOAT64:
+        r = buf;
+        bf_init(ctx->bf_ctx, r);
+        bf_set_float64(r, JS_VALUE_GET_FLOAT64(val));
+        is_float = TRUE;
+        break;
+    case JS_TAG_BIG_INT:
+        is_float = FALSE;
+        goto get_ptr;
+    case JS_TAG_BIG_FLOAT:
+        is_float = TRUE;
+    get_ptr:
+        p = JS_VALUE_GET_PTR(val);
+        r = &p->num;
+        break;
+    case JS_TAG_UNDEFINED:
+    default:
+        r = buf;
+        bf_init(ctx->bf_ctx, r);
+        bf_set_nan(r);
+        is_float = TRUE;
+        break;
+    }
+    *pis_float = is_float;
+    return r;
+}
+
+/* return NaN if bad bigint literal */
+static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val)
+{
+    const char *str;
+    const char *p;
+    int flags, err;
+
+    str = JS_ToCString(ctx, val);
+    JS_FreeValue(ctx, val);
+    if (!str)
+        return JS_EXCEPTION;
+    flags = BF_ATOF_BIN_OCT | BF_ATOF_NO_PREFIX_AFTER_SIGN |
+        BF_ATOF_JS_QUIRKS | BF_ATOF_INT_ONLY | BF_ATOF_INT_PREC_INF;
+    if (!is_bignum_mode(ctx))
+        flags |= BF_ATOF_BIG_INT;
+    val = js_atof(ctx, str, &p, 0, flags);
+    p += skip_spaces(p);
+    err = (*p != '\0');
+    JS_FreeCString(ctx, str);
+    if (err) {
+        JS_FreeValue(ctx, val);
+        val = JS_NAN;
+    }
+    return val;
+}
+
+static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val)
+{
+    val = JS_StringToBigInt(ctx, val);
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT) {
+        JS_FreeValue(ctx, val);
+        return JS_ThrowSyntaxError(ctx, "invalid bigint literal");
+    }
+    return val;
+}
+
+/* if the returned bigfloat is allocated it is equal to
+   'buf'. Otherwise it is a pointer to the bigfloat in 'val'. */
+static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val)
+{
+    uint32_t tag;
+    bf_t *r;
+    JSBigFloat *p;
+
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        if (!is_bignum_mode(ctx))
+            goto fail;
+        /* fall tru */
+    case JS_TAG_BOOL:
+        r = buf;
+        bf_init(ctx->bf_ctx, r);
+        bf_set_si(r, JS_VALUE_GET_INT(val));
+        break;
+    case JS_TAG_FLOAT64:
+        {
+            double d = JS_VALUE_GET_FLOAT64(val);
+            if (!is_bignum_mode(ctx))
+                goto fail;
+            if (!isfinite(d))
+                goto fail;
+            r = buf;
+            bf_init(ctx->bf_ctx, r);
+            d = trunc(d);
+            bf_set_float64(r, d);
+        }
+        break;
+    case JS_TAG_BIG_INT:
+        p = JS_VALUE_GET_PTR(val);
+        r = &p->num;
+        break;
+    case JS_TAG_BIG_FLOAT:
+        if (!is_bignum_mode(ctx))
+            goto fail;
+        p = JS_VALUE_GET_PTR(val);
+        if (!bf_is_finite(&p->num))
+            goto fail;
+        r = buf;
+        bf_init(ctx->bf_ctx, r);
+        bf_set(r, &p->num);
+        bf_rint(r, BF_PREC_INF, BF_RNDZ);
+        JS_FreeValue(ctx, val);
+        break;
+    case JS_TAG_STRING:
+        val = JS_StringToBigIntErr(ctx, val);
+        if (JS_IsException(val))
+            return NULL;
+        goto redo;
+    case JS_TAG_OBJECT:
+        val = JS_ToPrimitiveFree(ctx, val,
+                                 is_bignum_mode(ctx) ? HINT_INTEGER : HINT_NUMBER);
+        if (JS_IsException(val))
+            return NULL;
+        goto redo;
+    default:
+    fail:
+        JS_FreeValue(ctx, val);
+        JS_ThrowTypeError(ctx, "cannot convert to bigint");
+        return NULL;
+    }
+    return r;
+}
+
+static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val)
+{
+    return JS_ToBigIntFree(ctx, buf, JS_DupValue(ctx, val));
+}
+
+static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val)
+{
+    if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) {
+        return val;
+    } else {
+        bf_t a_s, *a;
+        a = JS_ToBigIntFree(ctx, &a_s, val);
+        if (!a)
+            return JS_EXCEPTION;
+        return JS_NewBigInt2(ctx, a, TRUE);
+    }
+}
+
+/* free the bf_t allocated by JS_ToBigInt */
+static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf)
+{
+    if (a == buf) {
+        bf_delete(a);
+    } else {
+        JSBigFloat *p = (JSBigFloat *)((uint8_t *)a -
+                                       offsetof(JSBigFloat, num));
+        JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_FLOAT, p));
+    }
+}
+
+/* XXX: merge with JS_ToInt64Free with a specific flag */
+static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val)
+{
+    bf_t a_s, *a;
+
+    a = JS_ToBigIntFree(ctx, &a_s, val);
+    if (!a) {
+        *pres = 0;
+        return -1;
+    }
+    bf_get_int64(pres, a, BF_GET_INT_MOD);
+    JS_FreeBigInt(ctx, a, &a_s);
+    return 0;
+}
+
+int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val)
+{
+    return JS_ToBigInt64Free(ctx, pres, JS_DupValue(ctx, val));
+}
+
+static JSBigFloat *js_new_bf(JSContext *ctx)
+{
+    JSBigFloat *p;
+    p = js_mallocz(ctx, sizeof(*p));
+    if (!p)
+        return NULL;
+    p->header.ref_count = 1;
+    bf_init(ctx->bf_ctx, &p->num);
+    return p;
+}
+
+/* WARNING: 'a' is freed */
+static JSValue JS_NewBigFloat(JSContext *ctx, bf_t *a)
+{
+    JSValue ret;
+    JSBigFloat *p;
+
+    p = js_new_bf(ctx);
+    p->num = *a;
+    ret = JS_MKPTR(JS_TAG_BIG_FLOAT, p);
+    return ret;
+}
+
+/* WARNING: 'a' is freed */
+static JSValue JS_NewBigInt2(JSContext *ctx, bf_t *a, BOOL force_bigint)
+{
+    JSValue ret;
+    JSBigFloat *p;
+    int32_t v;
+
+    if (!force_bigint && bf_get_int32(&v, a, 0) == 0) {
+        /* can fit in an int32 */
+        ret = JS_NewInt32(ctx, v);
+        bf_delete(a);
+    } else {
+        p = js_new_bf(ctx);
+        p->num = *a;
+        /* normalize the zero representation */
+        if (bf_is_zero(&p->num))
+            p->num.sign = 0;
+        ret = JS_MKPTR(JS_TAG_BIG_INT, p);
+    }
+    return ret;
+}
+
+static JSValue JS_NewBigInt(JSContext *ctx, bf_t *a)
+{
+    return JS_NewBigInt2(ctx, a, FALSE);
+}
+
+/* return < 0 if exception, 0 if overloading method, 1 if overloading
+   operator called */
+static __exception int js_call_binary_op_fallback(JSContext *ctx,
+                                                   JSValue *pret,
+                                                   JSValueConst op1,
+                                                   JSValueConst op2,
+                                                   OPCodeEnum op)
+{
+    JSAtom op_name;
+    JSValue method, ret, c1, c2;
+    BOOL bool_result, swap_op;
+    JSValueConst args[2];
+
+    bool_result = FALSE;
+    swap_op = FALSE;
+    c1 = JS_UNDEFINED;
+    c2 = JS_UNDEFINED;
+    switch(op) {
+    case OP_add:
+        op_name = JS_ATOM_Symbol_operatorAdd;
+        break;
+    case OP_sub:
+        op_name = JS_ATOM_Symbol_operatorSub;
+        break;
+    case OP_mul:
+        op_name = JS_ATOM_Symbol_operatorMul;
+        break;
+    case OP_div:
+    case OP_math_div:
+        op_name = JS_ATOM_Symbol_operatorDiv;
+        break;
+    case OP_mod:
+        op_name = JS_ATOM_Symbol_operatorMod;
+        break;
+    case OP_pow:
+    case OP_math_pow:
+        op_name = JS_ATOM_Symbol_operatorPow;
+        break;
+    case OP_math_mod:
+        op_name = JS_ATOM_Symbol_operatorMathMod;
+        break;
+    case OP_shl:
+        op_name = JS_ATOM_Symbol_operatorShl;
+        break;
+    case OP_sar:
+        op_name = JS_ATOM_Symbol_operatorShr;
+        break;
+    case OP_and:
+        op_name = JS_ATOM_Symbol_operatorAnd;
+        break;
+    case OP_or:
+        op_name = JS_ATOM_Symbol_operatorOr;
+        break;
+    case OP_xor:
+        op_name = JS_ATOM_Symbol_operatorXor;
+        break;
+    case OP_lt:
+        op_name = JS_ATOM_Symbol_operatorCmpLT;
+        bool_result = TRUE;
+        break;
+    case OP_lte:
+        op_name = JS_ATOM_Symbol_operatorCmpLE;
+        bool_result = TRUE;
+        break;
+    case OP_gt:
+        op_name = JS_ATOM_Symbol_operatorCmpLT;
+        bool_result = TRUE;
+        swap_op = TRUE;
+        break;
+    case OP_gte:
+        op_name = JS_ATOM_Symbol_operatorCmpLE;
+        bool_result = TRUE;
+        swap_op = TRUE;
+        break;
+    case OP_eq:
+    case OP_neq:
+        op_name = JS_ATOM_Symbol_operatorCmpEQ;
+        bool_result = TRUE;
+        break;
+    default:
+        goto fail;
+    }
+    c1 = JS_GetProperty(ctx, op1, JS_ATOM_constructor);
+    if (JS_IsException(c1))
+        goto exception;
+    c2 = JS_GetProperty(ctx, op2, JS_ATOM_constructor);
+    if (JS_IsException(c2))
+        goto exception;
+    if (JS_VALUE_GET_TAG(c1) != JS_TAG_OBJECT ||
+        JS_VALUE_GET_TAG(c2) != JS_TAG_OBJECT)
+        goto fail;
+    if (JS_VALUE_GET_OBJ(c1) == JS_VALUE_GET_OBJ(c2)) {
+        /* if same constructor, there is no ambiguity */
+        method = JS_GetProperty(ctx, c1, op_name);
+    } else {
+        JSValue val;
+        int order1, order2;
+
+        /* different constructors: we use a user-defined ordering */
+        val = JS_GetProperty(ctx, c1, JS_ATOM_Symbol_operatorOrder);
+        if (JS_IsException(val))
+            goto exception;
+        if (JS_IsUndefined(val))
+            goto undef_order;
+        if (JS_ToInt32Free(ctx, &order1, val))
+            goto exception;
+        val = JS_GetProperty(ctx, c2, JS_ATOM_Symbol_operatorOrder);
+        if (JS_IsException(val))
+            goto exception;
+        if (JS_IsUndefined(val)) {
+        undef_order:
+            JS_FreeValue(ctx, c1);
+            JS_FreeValue(ctx, c2);
+            *pret = JS_UNDEFINED;
+            return 0;
+        }
+        if (JS_ToInt32Free(ctx, &order2, val))
+            goto exception;
+        /* ambiguous priority: error */
+        if (order1 == order2) {
+            JS_ThrowTypeError(ctx, "operator_order is identical in both constructors");
+            goto exception;
+        }
+        if (order1 > order2) {
+            val = c1;
+        } else {
+            val = c2;
+        }
+        method = JS_GetProperty(ctx, val, op_name);
+    }
+    JS_FreeValue(ctx, c1);
+    JS_FreeValue(ctx, c2);
+    c1 = JS_UNDEFINED;
+    c2 = JS_UNDEFINED;
+    if (JS_IsException(method))
+        goto exception;
+    if (JS_IsUndefined(method) || JS_IsNull(method)) {
+        *pret = JS_UNDEFINED;
+        return 0;
+    }
+    if (swap_op) {
+        args[0] = op2;
+        args[1] = op1;
+    } else {
+        args[0] = op1;
+        args[1] = op2;
+    }
+    ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args);
+    if (JS_IsException(ret))
+        goto exception;
+    if (bool_result) {
+        BOOL res = JS_ToBoolFree(ctx, ret);
+        if (op == OP_neq)
+            res ^= 1;
+        ret = JS_NewBool(ctx, res);
+    }
+    *pret = ret;
+    return 1;
+ fail:
+    JS_ThrowTypeError(ctx, "invalid types for binary operator");
+ exception:
+    JS_FreeValue(ctx, c1);
+    JS_FreeValue(ctx, c2);
+    *pret = JS_UNDEFINED;
+    return -1;
+}
+
+static JSValue throw_bf_exception(JSContext *ctx, int status)
+{
+    const char *str;
+    if (status & BF_ST_DIVIDE_ZERO) {
+        str = "division by zero";
+    } else if (status & BF_ST_INVALID_OP) {
+        str = "invalid operation";
+    } else {
+        str = "integer overflow";
+    }
+    return JS_ThrowRangeError(ctx, "%s", str);
+}
+
+static no_inline __exception int js_unary_arith_slow(JSContext *ctx,
+                                                     JSValue *sp,
+                                                     OPCodeEnum op)
+{
+    JSValue op1, val, method;
+    bf_t a_s, r_s, *r = &r_s, *a;
+    BOOL is_float, is_legacy;
+    JSAtom op_name;
+    int ret, v;
+    uint32_t tag;
+
+    op1 = sp[-1];
+    /* fast path for float64 */
+    if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)))
+        goto handle_float64;
+    if (JS_IsObject(op1)) {
+        switch(op) {
+        case OP_plus:
+            op_name = JS_ATOM_Symbol_operatorPlus;
+            break;
+        case OP_neg:
+            op_name = JS_ATOM_Symbol_operatorNeg;
+            break;
+        case OP_inc:
+            op_name = JS_ATOM_Symbol_operatorInc;
+            break;
+        case OP_dec:
+            op_name = JS_ATOM_Symbol_operatorDec;
+            break;
+        default:
+            abort();
+        }
+        method = JS_GetProperty(ctx, op1, op_name);
+        if (JS_IsException(method))
+            return -1;
+        if (JS_IsUndefined(method) || JS_IsNull(method))
+            goto to_number;
+        val = JS_CallFree(ctx, method, op1, 0, NULL);
+        if (JS_IsException(val))
+            return -1;
+        JS_FreeValue(ctx, op1);
+        sp[-1] = val;
+    } else {
+    to_number:
+        op1 = JS_ToNumericFree(ctx, op1);
+        if (JS_IsException(op1))
+            goto exception;
+        is_legacy = is_bignum_mode(ctx) ^ 1;
+        tag = JS_VALUE_GET_TAG(op1);
+        switch(tag) {
+        case JS_TAG_INT:
+            {
+                int64_t v64;
+                v64 = JS_VALUE_GET_INT(op1);
+                switch(op) {
+                case OP_inc:
+                case OP_dec:
+                    v = 2 * (op - OP_dec) - 1;
+                    v64 += v;
+                    break;
+                case OP_plus:
+                    break;
+                case OP_neg:
+                    if (v64 == 0 && is_legacy) {
+                        sp[-1] = __JS_NewFloat64(ctx, -0.0);
+                        return 0;
+                    } else {
+                        v64 = -v64;
+                    }
+                    break;
+                default:
+                    abort();
+                }
+                sp[-1] = JS_NewInt64(ctx, v64);
+            }
+            break;
+        case JS_TAG_BIG_INT:
+            if (is_legacy && op == OP_plus) {
+                JS_ThrowTypeError(ctx, "bigint argument with unary +");
+                JS_FreeValue(ctx, op1);
+                goto exception;
+            }
+            a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+            bf_init(ctx->bf_ctx, r);
+            ret = 0;
+            switch(op) {
+            case OP_inc:
+            case OP_dec:
+                v = 2 * (op - OP_dec) - 1;
+                ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ) & BF_ST_OVERFLOW;
+                break;
+            case OP_plus:
+                bf_set(r, a);
+                break;
+            case OP_neg:
+                bf_set(r, a);
+                bf_neg(r);
+                break;
+            default:
+                abort();
+            }
+            if (a == &a_s)
+                bf_delete(a);
+            JS_FreeValue(ctx, op1);
+            if (unlikely(ret)) {
+                bf_delete(r);
+                throw_bf_exception(ctx, ret);
+                goto exception;
+            }
+            sp[-1] = JS_NewBigInt2(ctx, r, is_legacy);
+            break;
+        case JS_TAG_BIG_FLOAT:
+            a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+            bf_init(ctx->bf_ctx, r);
+            ret = 0;
+            switch(op) {
+            case OP_inc:
+            case OP_dec:
+                v = 2 * (op - OP_dec) - 1;
+                bf_add_si(r, a, v, ctx->fp_env.prec, ctx->fp_env.flags);
+                break;
+            case OP_plus:
+                bf_set(r, a);
+                break;
+            case OP_neg:
+                bf_set(r, a);
+                bf_neg(r);
+                break;
+            default:
+                abort();
+            }
+            if (a == &a_s)
+                bf_delete(a);
+            JS_FreeValue(ctx, op1);
+            if (unlikely(ret)) {
+                bf_delete(r);
+                throw_bf_exception(ctx, ret);
+                goto exception;
+            }
+            sp[-1] = JS_NewBigFloat(ctx, r);
+            break;
+        default:
+        handle_float64:
+            {
+                double d;
+                d = JS_VALUE_GET_FLOAT64(op1);
+                switch(op) {
+                case OP_inc:
+                case OP_dec:
+                    v = 2 * (op - OP_dec) - 1;
+                    d += v;
+                    break;
+                case OP_plus:
+                    break;
+                case OP_neg:
+                    d = -d;
+                    break;
+                default:
+                    abort();
+                }
+                sp[-1] = __JS_NewFloat64(ctx, d);
+            }
+            break;
+        }
+    }
+    return 0;
+ exception:
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static __exception int js_post_inc_slow(JSContext *ctx,
+                                        JSValue *sp, OPCodeEnum op)
+{
+    JSValue op1;
+
+    /* XXX: allow custom operators */
+    op1 = sp[-1];
+    op1 = JS_ToNumericFree(ctx, op1);
+    if (JS_IsException(op1)) {
+        sp[-1] = JS_UNDEFINED;
+        return -1;
+    }
+    sp[-1] = op1;
+    sp[0] = JS_DupValue(ctx, op1);
+    return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec);
+}
+
+static no_inline int js_not_slow(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, method, val;
+    bf_t a_s, r_s, *r = &r_s, *a;
+    int ret;
+    BOOL is_legacy;
+
+    op1 = sp[-1];
+    if (JS_IsObject(op1)) {
+        method = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorNot);
+        if (JS_IsException(method))
+            return -1;
+        if (JS_IsUndefined(method) || JS_IsNull(method))
+            goto to_number;
+        val = JS_CallFree(ctx, method, op1, 0, NULL);
+        if (JS_IsException(val))
+            return -1;
+        JS_FreeValue(ctx, op1);
+        sp[-1] = val;
+    } else {
+        if (JS_IsString(op1)) {
+        to_number:
+            op1 = JS_ToNumberHintFree(ctx, op1, TON_FLAG_INTEGER);
+            if (JS_IsException(op1))
+                goto exception;
+        }
+        is_legacy = is_bignum_mode(ctx) ^ 1;
+        if (!is_legacy || JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) {
+            a = JS_ToBigIntFree(ctx, &a_s, op1);
+            bf_init(ctx->bf_ctx, r);
+            ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ) & BF_ST_OVERFLOW;
+            bf_neg(r);
+            JS_FreeBigInt(ctx, a, &a_s);
+            if (unlikely(ret)) {
+                bf_delete(r);
+                throw_bf_exception(ctx, ret);
+                goto exception;
+            }
+            sp[-1] = JS_NewBigInt2(ctx, r, is_legacy);
+        } else {
+            int32_t v1;
+            if (unlikely(JS_ToInt32Free(ctx, &v1, op1)))
+                goto exception;
+            sp[-1] = JS_NewInt32(ctx, ~v1);
+        }
+    }
+    return 0;
+ exception:
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp,
+                                                      OPCodeEnum op)
+{
+    JSValue op1, op2, res;
+    BOOL is_float, is_legacy;
+    uint32_t tag1, tag2;
+    int ret, rnd_mode;
+    double d1, d2;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    /* fast path for float operations */
+    if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) {
+        d1 = JS_VALUE_GET_FLOAT64(op1);
+        d2 = JS_VALUE_GET_FLOAT64(op2);
+        goto handle_float64;
+    }
+
+    /* try to call an overloaded operator */
+    if ((tag1 == JS_TAG_OBJECT &&
+         (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) ||
+        (tag2 == JS_TAG_OBJECT &&
+         (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) {
+        ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op);
+        if (ret != 0) {
+            JS_FreeValue(ctx, op1);
+            JS_FreeValue(ctx, op2);
+            if (ret < 0) {
+                goto exception;
+            } else {
+                sp[-2] = res;
+                return 0;
+            }
+        }
+    }
+
+    op1 = JS_ToNumericFree(ctx, op1);
+    if (JS_IsException(op1)) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    op2 = JS_ToNumericFree(ctx, op2);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        goto exception;
+    }
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+
+    is_legacy = is_bignum_mode(ctx) ^ 1;
+    if (is_legacy && (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) &&
+        tag1 != tag2) {
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        JS_ThrowTypeError(ctx, "both operands must be bigint");
+        goto exception;
+    }
+
+    if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
+        int32_t v1, v2;
+        int64_t v;
+        v1 = JS_VALUE_GET_INT(op1);
+        v2 = JS_VALUE_GET_INT(op2);
+        switch(op) {
+        case OP_sub:
+            v = (int64_t)v1 - (int64_t)v2;
+            break;
+        case OP_mul:
+            v = (int64_t)v1 * (int64_t)v2;
+            if (is_legacy && v == 0 && (v1 | v2) < 0) {
+                sp[-2] = __JS_NewFloat64(ctx, -0.0);
+                return 0;
+            }
+            break;
+        case OP_math_div:
+            goto op_fallback2;
+        case OP_div:
+            sp[-2] = __JS_NewFloat64(ctx, (double)v1 / (double)v2);
+            return 0;
+        case OP_math_mod:
+            if (unlikely(v2 == 0)) {
+                throw_bf_exception(ctx, BF_ST_DIVIDE_ZERO);
+                goto exception;
+            }
+            v = (int64_t)v1 % (int64_t)v2;
+            if (v < 0) {
+                if (v2 < 0)
+                    v -= v2;
+                else
+                    v += v2;
+            }
+            break;
+        case OP_mod:
+            if (is_legacy && (v1 < 0 || v2 <= 0)) {
+                sp[-2] = JS_NewFloat64(ctx, fmod(v1, v2));
+                return 0;
+            } else {
+                if (unlikely(v2 == 0)) {
+                    throw_bf_exception(ctx, BF_ST_DIVIDE_ZERO);
+                    goto exception;
+                }
+                v = (int64_t)v1 % (int64_t)v2;
+            }
+            break;
+        case OP_pow:
+        case OP_math_pow:
+            if (is_legacy) {
+                sp[-2] = JS_NewFloat64(ctx, js_pow(v1, v2));
+                return 0;
+            } else {
+                goto handle_bigint;
+            }
+            break;
+        default:
+            abort();
+        }
+        sp[-2] = JS_NewInt64(ctx, v);
+    } else if ((tag1 == JS_TAG_BIG_INT && (tag2 == JS_TAG_INT ||
+                                           tag2 == JS_TAG_BIG_INT)) ||
+               (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_INT)) {
+        /* big int result */
+        bf_t a_s, b_s, r_s, *r, *a, *b;
+    handle_bigint:
+        a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+        b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+        r = &r_s;
+        bf_init(ctx->bf_ctx, r);
+        ret = 0;
+        switch(op) {
+        case OP_sub:
+            ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ) & BF_ST_OVERFLOW;
+            break;
+        case OP_mul:
+            ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ) & BF_ST_OVERFLOW;
+            break;
+        case OP_math_div:
+            goto op_fallback;
+        case OP_div:
+            if (is_legacy) {
+                bf_t rem_s, *rem = &rem_s;
+                bf_init(ctx->bf_ctx, rem);
+                ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ,
+                                BF_RNDZ) & BF_ST_INVALID_OP;
+                bf_delete(rem);
+            } else {
+                bf_div(r, a, b, 53, bf_set_exp_bits(11) |
+                       BF_RNDN | BF_FLAG_SUBNORMAL);
+                goto float64_result;
+            }
+            break;
+        case OP_math_mod:
+            /* Euclidian remainder */
+            rnd_mode = BF_DIVREM_EUCLIDIAN;
+            goto do_int_mod;
+        case OP_mod:
+            rnd_mode = BF_RNDZ;
+        do_int_mod:
+            {
+                bf_t q_s, *q = &q_s;
+                bf_init(ctx->bf_ctx, q);
+                ret = bf_divrem(q, r, a, b, BF_PREC_INF, BF_RNDZ,
+                                rnd_mode) & BF_ST_INVALID_OP;
+                bf_delete(q);
+            }
+            break;
+        case OP_pow:
+        case OP_math_pow:
+            if (b->sign) {
+                if (is_legacy) {
+                    ret = BF_ST_INVALID_OP;
+                } else if (op == OP_math_pow) {
+                op_fallback:
+                    bf_delete(r);
+                    if (a == &a_s)
+                        bf_delete(a);
+                    if (b == &b_s)
+                        bf_delete(b);
+                op_fallback2:
+                    ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op);
+                    JS_FreeValue(ctx, op1);
+                    JS_FreeValue(ctx, op2);
+                    if (ret < 0) {
+                        goto exception;
+                    } else if (ret == 0) {
+                        JS_ThrowTypeError(ctx, "operator must be defined for exact division or power");
+                        goto exception;
+                    }
+                    sp[-2] = res;
+                    return 0;
+                } else {
+                    double dr;
+                    bf_pow(r, a, b, 53, bf_set_exp_bits(11) |
+                           BF_RNDN | BF_FLAG_SUBNORMAL);
+                float64_result:
+                    bf_get_float64(r, &dr, BF_RNDN);
+                    bf_delete(r);
+                    if (a == &a_s)
+                        bf_delete(a);
+                    if (b == &b_s)
+                        bf_delete(b);
+                    JS_FreeValue(ctx, op1);
+                    JS_FreeValue(ctx, op2);
+                    sp[-2] = __JS_NewFloat64(ctx, dr);
+                    return 0;
+                }
+            } else {
+                ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUICKS) &
+                    BF_ST_OVERFLOW;
+            }
+            break;
+        default:
+            abort();
+        }
+        if (a == &a_s)
+            bf_delete(a);
+        if (b == &b_s)
+            bf_delete(b);
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        if (unlikely(ret)) {
+            bf_delete(r);
+            throw_bf_exception(ctx, ret);
+            goto exception;
+        }
+        sp[-2] = JS_NewBigInt2(ctx, r, is_legacy);
+    } else if ((tag1 == JS_TAG_FLOAT64 && (tag2 == JS_TAG_FLOAT64 ||
+                                           tag2 == JS_TAG_INT ||
+                                           tag2 == JS_TAG_BIG_INT)) ||
+               (tag2 == JS_TAG_FLOAT64 && (tag1 == JS_TAG_INT ||
+                                           tag1 == JS_TAG_BIG_INT))) {
+        double dr;
+        /* float64 result */
+        JS_ToFloat64Free(ctx, &d1, op1);
+        JS_ToFloat64Free(ctx, &d2, op2);
+    handle_float64:
+        switch(op) {
+        case OP_sub:
+            dr = d1 - d2;
+            break;
+        case OP_mul:
+            dr = d1 * d2;
+            break;
+        case OP_div:
+        case OP_math_div:
+            dr = d1 / d2;
+            break;
+        case OP_mod:
+            dr = fmod(d1, d2);
+            break;
+        case OP_math_mod:
+            if (d1 >= 0 && d2 >= 0) {
+                dr = fmod(d1, d2);
+            } else {
+                /* XXX: slow */
+                bf_t a, b, r, q;
+                bf_init(ctx->bf_ctx, &a);
+                bf_init(ctx->bf_ctx, &b);
+                bf_init(ctx->bf_ctx, &r);
+                bf_set_float64(&a, d1);
+                bf_set_float64(&b, d2);
+                bf_divrem(&q, &r, &a, &b,
+                          53, bf_set_exp_bits(11) | BF_RNDN | BF_FLAG_SUBNORMAL,
+                          BF_DIVREM_EUCLIDIAN);
+                bf_get_float64(&q, &dr, BF_RNDN);
+                bf_delete(&a);
+                bf_delete(&b);
+                bf_delete(&q);
+                bf_delete(&r);
+            }
+            break;
+        case OP_pow:
+        case OP_math_pow:
+            dr = js_pow(d1, d2);
+            break;
+        default:
+            abort();
+        }
+        sp[-2] = __JS_NewFloat64(ctx, dr);
+    } else {
+        bf_t a_s, b_s, r_s, *r, *a, *b;
+        /* big float result */
+        a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+        b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+        r = &r_s;
+        bf_init(ctx->bf_ctx, r);
+        ret = 0;
+        switch(op) {
+        case OP_sub:
+            bf_sub(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags);
+            break;
+        case OP_mul:
+            bf_mul(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags);
+            break;
+        case OP_math_div:
+        case OP_div:
+            bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags);
+            break;
+        case OP_math_mod:
+            /* Euclidian remainder */
+            rnd_mode = BF_DIVREM_EUCLIDIAN;
+            goto do_mod;
+        case OP_mod:
+            rnd_mode = BF_RNDZ;
+        do_mod:
+            {
+                bf_t q_s, *q = &q_s;
+                bf_init(ctx->bf_ctx, q);
+                bf_divrem(q, r, a, b, ctx->fp_env.prec, ctx->fp_env.flags,
+                          rnd_mode);
+                bf_delete(q);
+            }
+            break;
+        case OP_pow:
+        case OP_math_pow:
+            bf_pow(r, a, b, ctx->fp_env.prec,
+                   ctx->fp_env.flags | BF_POW_JS_QUICKS);
+            break;
+        default:
+            abort();
+        }
+        if (a == &a_s)
+            bf_delete(a);
+        if (b == &b_s)
+            bf_delete(b);
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        if (unlikely(ret)) {
+            bf_delete(r);
+            throw_bf_exception(ctx, ret);
+            goto exception;
+        }
+        sp[-2] = JS_NewBigFloat(ctx, r);
+    }
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2, res;
+    uint32_t tag1, tag2;
+    BOOL is_float, is_legacy;
+    int ret;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    /* fast path for float64 */
+    if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) {
+        double d1, d2;
+        d1 = JS_VALUE_GET_FLOAT64(op1);
+        d2 = JS_VALUE_GET_FLOAT64(op2);
+        sp[-2] = __JS_NewFloat64(ctx, d1 + d2);
+        return 0;
+    }
+
+    if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) {
+        /* try to call an overloaded operator */
+        if ((tag1 == JS_TAG_OBJECT &&
+             (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) ||
+            (tag2 == JS_TAG_OBJECT &&
+             (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) {
+            ret = js_call_binary_op_fallback(ctx, &res, op1, op2, OP_add);
+            if (ret != 0) {
+                JS_FreeValue(ctx, op1);
+                JS_FreeValue(ctx, op2);
+                if (ret < 0) {
+                    goto exception;
+                } else {
+                    sp[-2] = res;
+                    return 0;
+                }
+            }
+        }
+
+        op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
+        if (JS_IsException(op1)) {
+            JS_FreeValue(ctx, op2);
+            goto exception;
+        }
+
+        op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
+        if (JS_IsException(op2)) {
+            JS_FreeValue(ctx, op1);
+            goto exception;
+        }
+        tag1 = JS_VALUE_GET_NORM_TAG(op1);
+        tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    }
+
+    if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) {
+        sp[-2] = JS_ConcatString(ctx, op1, op2);
+        if (JS_IsException(sp[-2]))
+            goto exception;
+        return 0;
+    }
+
+    op1 = JS_ToNumericFree(ctx, op1);
+    if (JS_IsException(op1)) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    op2 = JS_ToNumericFree(ctx, op2);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        goto exception;
+    }
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+
+    is_legacy = is_bignum_mode(ctx) ^ 1;
+    if (is_legacy && (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) &&
+        tag1 != tag2) {
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        JS_ThrowTypeError(ctx, "both operands must be bigint");
+        goto exception;
+    }
+
+    if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
+        int32_t v1, v2;
+        int64_t v;
+        v1 = JS_VALUE_GET_INT(op1);
+        v2 = JS_VALUE_GET_INT(op2);
+        v = (int64_t)v1 + (int64_t)v2;
+        sp[-2] = JS_NewInt64(ctx, v);
+    } else if ((tag1 == JS_TAG_BIG_INT && (tag2 == JS_TAG_INT ||
+                                           tag2 == JS_TAG_BIG_INT)) ||
+               (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_INT)) {
+        bf_t a_s, b_s, r_s, *r, *a, *b;
+        /* big int result */
+        a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+        b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+        r = &r_s;
+        bf_init(ctx->bf_ctx, r);
+        ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ) & BF_ST_OVERFLOW;
+        if (a == &a_s)
+            bf_delete(a);
+        if (b == &b_s)
+            bf_delete(b);
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        if (unlikely(ret)) {
+            bf_delete(r);
+            throw_bf_exception(ctx, ret);
+            goto exception;
+        }
+        sp[-2] = JS_NewBigInt2(ctx, r, is_legacy);
+    } else if ((tag1 == JS_TAG_FLOAT64 && (tag2 == JS_TAG_FLOAT64 ||
+                                           tag2 == JS_TAG_INT ||
+                                           tag2 == JS_TAG_BIG_INT)) ||
+               (tag2 == JS_TAG_FLOAT64 && (tag1 == JS_TAG_INT ||
+                                           tag1 == JS_TAG_BIG_INT))) {
+        double d1, d2;
+        /* float64 result */
+        JS_ToFloat64Free(ctx, &d1, op1);
+        JS_ToFloat64Free(ctx, &d2, op2);
+        sp[-2] = __JS_NewFloat64(ctx, d1 + d2);
+    } else {
+        bf_t a_s, b_s, r_s, *r, *a, *b;
+        /* big float result */
+        a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+        b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+
+        r = &r_s;
+        bf_init(ctx->bf_ctx, r);
+        bf_add(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags);
+        if (a == &a_s)
+            bf_delete(a);
+        if (b == &b_s)
+            bf_delete(b);
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        sp[-2] = JS_NewBigFloat(ctx, r);
+    }
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline __exception int js_binary_logic_slow(JSContext *ctx,
+                                                      JSValue *sp,
+                                                      OPCodeEnum op)
+{
+    JSValue op1, op2, res;
+    bf_t a_s, b_s, r_s, *r, *a, *b;
+    int ret;
+    uint32_t tag1, tag2;
+    BOOL is_legacy;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+
+    /* try to call an overloaded operator */
+    if ((tag1 == JS_TAG_OBJECT &&
+         (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) ||
+        (tag2 == JS_TAG_OBJECT &&
+         (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) {
+        ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op);
+        if (ret != 0) {
+            JS_FreeValue(ctx, op1);
+            JS_FreeValue(ctx, op2);
+            if (ret < 0) {
+                goto exception;
+            } else {
+                sp[-2] = res;
+                return 0;
+            }
+        }
+    }
+
+    op1 = JS_ToNumberHintFree(ctx, op1, TON_FLAG_INTEGER);
+    if (JS_IsException(op1)) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    op2 = JS_ToNumberHintFree(ctx, op2, TON_FLAG_INTEGER);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        goto exception;
+    }
+
+    is_legacy = is_bignum_mode(ctx) ^ 1;
+    if (is_legacy) {
+        uint32_t v1, v2, r;
+
+        tag1 = JS_VALUE_GET_TAG(op1);
+        tag2 = JS_VALUE_GET_TAG(op2);
+        if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) {
+            if (tag1 != tag2) {
+                JS_FreeValue(ctx, op1);
+                JS_FreeValue(ctx, op2);
+                JS_ThrowTypeError(ctx, "both operands must be bigint");
+                goto exception;
+            }
+        } else {
+            if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) {
+                JS_FreeValue(ctx, op2);
+                goto exception;
+            }
+            if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2)))
+                goto exception;
+            switch(op) {
+            case OP_shl:
+                r = v1 << (v2 & 0x1f);
+                break;
+            case OP_sar:
+                r = (int)v1 >> (v2 & 0x1f);
+                break;
+            case OP_and:
+                r = v1 & v2;
+                break;
+            case OP_or:
+                r = v1 | v2;
+                break;
+            case OP_xor:
+                r = v1 ^ v2;
+                break;
+            default:
+                abort();
+            }
+            sp[-2] = JS_NewInt32(ctx, r);
+            return 0;
+        }
+    }
+
+    a = JS_ToBigIntFree(ctx, &a_s, op1);
+    b = JS_ToBigIntFree(ctx, &b_s, op2);
+
+    r = &r_s;
+    bf_init(ctx->bf_ctx, r);
+    ret = 0;
+    switch(op) {
+    case OP_shl:
+    case OP_sar:
+        {
+            slimb_t v2;
+#if LIMB_BITS == 32
+            bf_get_int32(&v2, b, 0);
+            if (v2 == INT32_MIN)
+                v2 = INT32_MIN + 1;
+#else
+            bf_get_int64(&v2, b, 0);
+            if (v2 == INT64_MIN)
+                v2 = INT64_MIN + 1;
+#endif
+            if (op == OP_sar)
+                v2 = -v2;
+            bf_set(r, a);
+            ret = bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ);
+            if (v2 < 0) {
+                ret |= bf_rint(r, BF_PREC_INF, BF_RNDD);
+            }
+            ret &= BF_ST_OVERFLOW;
+        }
+        break;
+    case OP_and:
+        bf_logic_and(r, a, b);
+        break;
+    case OP_or:
+        bf_logic_or(r, a, b);
+        break;
+    case OP_xor:
+        bf_logic_xor(r, a, b);
+        break;
+    default:
+        abort();
+    }
+    JS_FreeBigInt(ctx, a, &a_s);
+    JS_FreeBigInt(ctx, b, &b_s);
+    if (unlikely(ret)) {
+        bf_delete(r);
+        throw_bf_exception(ctx, ret);
+        goto exception;
+    }
+    sp[-2] = JS_NewBigInt2(ctx, r, is_legacy);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp,
+                                        OPCodeEnum op)
+{
+    JSValue op1, op2, ret;
+    int res;
+    uint32_t tag1, tag2;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    /* try to call an overloaded operator */
+    if ((tag1 == JS_TAG_OBJECT &&
+         (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) ||
+        (tag2 == JS_TAG_OBJECT &&
+         (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) {
+        res = js_call_binary_op_fallback(ctx, &ret, op1, op2, op);
+        if (res != 0) {
+            JS_FreeValue(ctx, op1);
+            JS_FreeValue(ctx, op2);
+            if (res < 0) {
+                goto exception;
+            } else {
+                sp[-2] = ret;
+                return 0;
+            }
+        }
+    }
+    op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER);
+    if (JS_IsException(op1)) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        goto exception;
+    }
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+
+    if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) {
+        JSString *p1, *p2;
+        p1 = JS_VALUE_GET_STRING(op1);
+        p2 = JS_VALUE_GET_STRING(op2);
+        res = js_string_compare(ctx, p1, p2);
+        switch(op) {
+        case OP_lt:
+            res = (res < 0);
+            break;
+        case OP_lte:
+            res = (res <= 0);
+            break;
+        case OP_gt:
+            res = (res > 0);
+            break;
+        default:
+        case OP_gte:
+            res = (res >= 0);
+            break;
+        }
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+    } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) &&
+               (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) {
+        /* can use floating point comparison */
+        double d1, d2;
+        if (tag1 == JS_TAG_FLOAT64) {
+            d1 = JS_VALUE_GET_FLOAT64(op1);
+        } else {
+            d1 = JS_VALUE_GET_INT(op1);
+        }
+        if (tag2 == JS_TAG_FLOAT64) {
+            d2 = JS_VALUE_GET_FLOAT64(op2);
+        } else {
+            d2 = JS_VALUE_GET_INT(op2);
+        }
+        switch(op) {
+        case OP_lt:
+            res = (d1 < d2); /* if NaN return false */
+            break;
+        case OP_lte:
+            res = (d1 <= d2); /* if NaN return false */
+            break;
+        case OP_gt:
+            res = (d1 > d2); /* if NaN return false */
+            break;
+        default:
+        case OP_gte:
+            res = (d1 >= d2); /* if NaN return false */
+            break;
+        }
+    } else {
+        bf_t a_s, b_s, *a, *b;
+        BOOL is_float;
+
+        if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) ||
+             (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING)) &&
+            !is_bignum_mode(ctx)) {
+            if (tag1 == JS_TAG_STRING) {
+                op1 = JS_StringToBigInt(ctx, op1);
+                if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT)
+                    goto invalid_bigint_string;
+            }
+            if (tag2 == JS_TAG_STRING) {
+                op2 = JS_StringToBigInt(ctx, op2);
+                if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) {
+                invalid_bigint_string:
+                    JS_FreeValue(ctx, op1);
+                    JS_FreeValue(ctx, op2);
+                    res = FALSE;
+                    goto done;
+                }
+            }
+        } else {
+            op1 = JS_ToNumericFree(ctx, op1);
+            if (JS_IsException(op1)) {
+                JS_FreeValue(ctx, op2);
+                goto exception;
+            }
+            op2 = JS_ToNumericFree(ctx, op2);
+            if (JS_IsException(op2)) {
+                JS_FreeValue(ctx, op1);
+                goto exception;
+            }
+        }
+
+        a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+        b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+
+        switch(op) {
+        case OP_lt:
+            res = bf_cmp_lt(a, b); /* if NaN return false */
+            break;
+        case OP_lte:
+            res = bf_cmp_le(a, b); /* if NaN return false */
+            break;
+        case OP_gt:
+            res = bf_cmp_lt(b, a); /* if NaN return false */
+            break;
+        default:
+        case OP_gte:
+            res = bf_cmp_le(b, a); /* if NaN return false */
+            break;
+        }
+        if (a == &a_s)
+            bf_delete(a);
+        if (b == &b_s)
+            bf_delete(b);
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+    }
+ done:
+    sp[-2] = JS_NewBool(ctx, res);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static BOOL tag_is_number(uint32_t tag)
+{
+    return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT ||
+            tag == JS_TAG_FLOAT64 || tag == JS_TAG_BIG_FLOAT);
+}
+
+static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp,
+                                            BOOL is_neq)
+{
+    JSValue op1, op2, ret;
+    int res;
+    uint32_t tag1, tag2;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+ redo:
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    if (tag_is_number(tag1) && tag_is_number(tag2)) {
+        if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
+            res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
+        } else if ((tag1 == JS_TAG_FLOAT64 &&
+                    (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) ||
+                   (tag2 == JS_TAG_FLOAT64 &&
+                    (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) {
+            double d1, d2;
+            if (tag1 == JS_TAG_FLOAT64) {
+                d1 = JS_VALUE_GET_FLOAT64(op1);
+            } else {
+                d1 = JS_VALUE_GET_INT(op1);
+            }
+            if (tag2 == JS_TAG_FLOAT64) {
+                d2 = JS_VALUE_GET_FLOAT64(op2);
+            } else {
+                d2 = JS_VALUE_GET_INT(op2);
+            }
+            res = (d1 == d2);
+        } else {
+            bf_t a_s, b_s, *a, *b;
+            BOOL is_float;
+            a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+            b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+            res = bf_cmp_eq(a, b);
+            if (a == &a_s)
+                bf_delete(a);
+            if (b == &b_s)
+                bf_delete(b);
+            JS_FreeValue(ctx, op1);
+            JS_FreeValue(ctx, op2);
+        }
+    } else if (tag1 == tag2) {
+        if (tag1 == JS_TAG_OBJECT) {
+            /* try the fallback operator */
+            res = js_call_binary_op_fallback(ctx, &ret, op1, op2,
+                                              is_neq ? OP_neq : OP_eq);
+            if (res != 0) {
+                JS_FreeValue(ctx, op1);
+                JS_FreeValue(ctx, op2);
+                if (res < 0) {
+                    goto exception;
+                } else {
+                    sp[-2] = ret;
+                    return 0;
+                }
+            }
+        }
+        res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
+    } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
+               (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
+        res = TRUE;
+    } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) ||
+               (tag2 == JS_TAG_STRING && tag_is_number(tag1))) {
+
+        if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) &&
+            !is_bignum_mode(ctx)) {
+            if (tag1 == JS_TAG_STRING) {
+                op1 = JS_StringToBigInt(ctx, op1);
+                if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT)
+                    goto invalid_bigint_string;
+            }
+            if (tag2 == JS_TAG_STRING) {
+                op2 = JS_StringToBigInt(ctx, op2);
+                if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) {
+                invalid_bigint_string:
+                    JS_FreeValue(ctx, op1);
+                    JS_FreeValue(ctx, op2);
+                    res = FALSE;
+                    goto done;
+                }
+            }
+        } else {
+            op1 = JS_ToNumericFree(ctx, op1);
+            if (JS_IsException(op1)) {
+                JS_FreeValue(ctx, op2);
+                goto exception;
+            }
+            op2 = JS_ToNumericFree(ctx, op2);
+            if (JS_IsException(op2)) {
+                JS_FreeValue(ctx, op1);
+                goto exception;
+            }
+        }
+        res = js_strict_eq(ctx, op1, op2);
+    } else if (tag1 == JS_TAG_BOOL) {
+        op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1));
+        goto redo;
+    } else if (tag2 == JS_TAG_BOOL) {
+        op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2));
+        goto redo;
+    } else if ((tag1 == JS_TAG_OBJECT &&
+                (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) ||
+               (tag2 == JS_TAG_OBJECT &&
+                (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) {
+
+        /* try the fallback operator */
+        res = js_call_binary_op_fallback(ctx, &ret, op1, op2,
+                                          is_neq ? OP_neq : OP_eq);
+        if (res != 0) {
+            JS_FreeValue(ctx, op1);
+            JS_FreeValue(ctx, op2);
+            if (res < 0) {
+                goto exception;
+            } else {
+                sp[-2] = ret;
+                return 0;
+            }
+        }
+
+        op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
+        if (JS_IsException(op1)) {
+            JS_FreeValue(ctx, op2);
+            goto exception;
+        }
+        op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
+        if (JS_IsException(op2)) {
+            JS_FreeValue(ctx, op1);
+            goto exception;
+        }
+        goto redo;
+    } else {
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        res = FALSE;
+    }
+ done:
+    sp[-2] = JS_NewBool(ctx, res ^ is_neq);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2;
+    uint32_t v1, v2, r;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    op1 = JS_ToNumericFree(ctx, op1);
+    if (JS_IsException(op1)) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    op2 = JS_ToNumericFree(ctx, op2);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        goto exception;
+    }
+    /* XXX: could forbid >>> in bignum mode */
+    if (!is_bignum_mode(ctx) &&
+        (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT ||
+         JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) {
+        JS_ThrowTypeError(ctx, "bigint operands are forbidden for >>>");
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    /* cannot give an exception */
+    JS_ToUint32Free(ctx, &v1, op1);
+    JS_ToUint32Free(ctx, &v2, op2);
+    r = v1 >> (v2 & 0x1f);
+    sp[-2] = JS_NewUint32(ctx, r);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static double bf_mul_pow10_to_float64(JSContext *ctx, const bf_t *a,
+                                      int64_t exponent)
+{
+    bf_t r_s, *r = &r_s;
+    double d;
+    /* always convert to Float64 */
+    bf_init(ctx->bf_ctx, r);
+    bf_mul_pow_radix(r, a, 10, exponent,
+                     53, bf_set_exp_bits(11) | BF_RNDN | BF_FLAG_SUBNORMAL);
+    bf_get_float64(r, &d, BF_RNDN);
+    bf_delete(r);
+    return d;
+}
+
+static no_inline int js_mul_pow10(JSContext *ctx, JSValue *sp)
+{
+    bf_t a_s, *a, r_s, *r = &r_s;
+    BOOL is_float;
+    JSValue op1, op2;
+    slimb_t e;
+    int ret;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+    if (!a)
+        return -1;
+#if LIMB_BITS == 32
+    ret = JS_ToInt32(ctx, &e, op2);
+#else
+    ret = JS_ToInt64(ctx, &e, op2);
+#endif
+    if (ret) {
+        if (a == &a_s)
+            bf_delete(a);
+        return -1;
+    }
+
+    bf_init(ctx->bf_ctx, r);
+    bf_mul_pow_radix(r, a, 10, e, ctx->fp_env.prec, ctx->fp_env.flags);
+    if (a == &a_s)
+        bf_delete(a);
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+    sp[-2] = JS_NewBigFloat(ctx, r);
+    return 0;
+}
+
+#else /* !CONFIG_BIGNUM */
+
+static no_inline __exception int js_unary_arith_slow(JSContext *ctx,
+                                                     JSValue *sp,
+                                                     OPCodeEnum op)
+{
+    JSValue op1;
+    double d;
+
+    op1 = sp[-1];
+    if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) {
+        sp[-1] = JS_UNDEFINED;
+        return -1;
+    }
+    switch(op) {
+    case OP_inc:
+        d++;
+        break;
+    case OP_dec:
+        d--;
+        break;
+    case OP_plus:
+        break;
+    case OP_neg:
+        d = -d;
+        break;
+    default:
+        abort();
+    }
+    sp[-1] = JS_NewFloat64(ctx, d);
+    return 0;
+}
+
+/* specific case necessary for correct return value semantics */
+static __exception int js_post_inc_slow(JSContext *ctx,
+                                        JSValue *sp, OPCodeEnum op)
+{
+    JSValue op1;
+    double d, r;
+
+    op1 = sp[-1];
+    if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) {
+        sp[-1] = JS_UNDEFINED;
+        return -1;
+    }
+    r = d + 2 * (op - OP_post_dec) - 1;
+    sp[0] = JS_NewFloat64(ctx, r);
+    sp[-1] = JS_NewFloat64(ctx, d);
+    return 0;
+}
+
+static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp,
+                                                      OPCodeEnum op)
+{
+    JSValue op1, op2;
+    double d1, d2, r;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    if (unlikely(JS_ToFloat64Free(ctx, &d1, op1))) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    if (unlikely(JS_ToFloat64Free(ctx, &d2, op2))) {
+        goto exception;
+    }
+    switch(op) {
+    case OP_sub:
+        r = d1 - d2;
+        break;
+    case OP_mul:
+        r = d1 * d2;
+        break;
+    case OP_div:
+        r = d1 / d2;
+        break;
+    case OP_mod:
+        r = fmod(d1, d2);
+        break;
+    case OP_pow:
+        r = js_pow(d1, d2);
+        break;
+    default:
+        abort();
+    }
+    sp[-2] = JS_NewFloat64(ctx, r);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2;
+    uint32_t tag1, tag2;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    tag1 = JS_VALUE_GET_TAG(op1);
+    tag2 = JS_VALUE_GET_TAG(op2);
+    if ((tag1 == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag1)) &&
+        (tag2 == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag2))) {
+        goto add_numbers;
+    } else {
+        op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
+        if (JS_IsException(op1)) {
+            JS_FreeValue(ctx, op2);
+            goto exception;
+        }
+        op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
+        if (JS_IsException(op2)) {
+            JS_FreeValue(ctx, op1);
+            goto exception;
+        }
+        tag1 = JS_VALUE_GET_TAG(op1);
+        tag2 = JS_VALUE_GET_TAG(op2);
+        if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) {
+            sp[-2] = JS_ConcatString(ctx, op1, op2);
+            if (JS_IsException(sp[-2]))
+                goto exception;
+        } else {
+            double d1, d2;
+        add_numbers:
+            if (JS_ToFloat64Free(ctx, &d1, op1)) {
+                JS_FreeValue(ctx, op2);
+                goto exception;
+            }
+            if (JS_ToFloat64Free(ctx, &d2, op2))
+                goto exception;
+            sp[-2] = JS_NewFloat64(ctx, d1 + d2);
+        }
+    }
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline __exception int js_binary_logic_slow(JSContext *ctx,
+                                                      JSValue *sp,
+                                                      OPCodeEnum op)
+{
+    JSValue op1, op2;
+    uint32_t v1, v2, r;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2)))
+        goto exception;
+    switch(op) {
+    case OP_shl:
+        r = v1 << (v2 & 0x1f);
+        break;
+    case OP_sar:
+        r = (int)v1 >> (v2 & 0x1f);
+        break;
+    case OP_and:
+        r = v1 & v2;
+        break;
+    case OP_or:
+        r = v1 | v2;
+        break;
+    case OP_xor:
+        r = v1 ^ v2;
+        break;
+    default:
+        abort();
+    }
+    sp[-2] = JS_NewInt32(ctx, r);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline int js_not_slow(JSContext *ctx, JSValue *sp)
+{
+    int32_t v1;
+
+    if (unlikely(JS_ToInt32Free(ctx, &v1, sp[-1]))) {
+        sp[-1] = JS_UNDEFINED;
+        return -1;
+    }
+    sp[-1] = JS_NewInt32(ctx, ~v1);
+    return 0;
+}
+
+static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp,
+                                        OPCodeEnum op)
+{
+    JSValue op1, op2;
+    int res;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER);
+    if (JS_IsException(op1)) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        goto exception;
+    }
+    if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING &&
+        JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) {
+        JSString *p1, *p2;
+        p1 = JS_VALUE_GET_STRING(op1);
+        p2 = JS_VALUE_GET_STRING(op2);
+        res = js_string_compare(ctx, p1, p2);
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        switch(op) {
+        case OP_lt:
+            res = (res < 0);
+            break;
+        case OP_lte:
+            res = (res <= 0);
+            break;
+        case OP_gt:
+            res = (res > 0);
+            break;
+        default:
+        case OP_gte:
+            res = (res >= 0);
+            break;
+        }
+    } else {
+        double d1, d2;
+        if (JS_ToFloat64Free(ctx, &d1, op1)) {
+            JS_FreeValue(ctx, op2);
+            goto exception;
+        }
+        if (JS_ToFloat64Free(ctx, &d2, op2))
+            goto exception;
+        switch(op) {
+        case OP_lt:
+            res = (d1 < d2); /* if NaN return false */
+            break;
+        case OP_lte:
+            res = (d1 <= d2); /* if NaN return false */
+            break;
+        case OP_gt:
+            res = (d1 > d2); /* if NaN return false */
+            break;
+        default:
+        case OP_gte:
+            res = (d1 >= d2); /* if NaN return false */
+            break;
+        }
+    }
+    sp[-2] = JS_NewBool(ctx, res);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp,
+                                            BOOL is_neq)
+{
+    JSValue op1, op2;
+    int tag1, tag2;
+    BOOL res;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+ redo:
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    if (tag1 == tag2 ||
+        (tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) ||
+        (tag2 == JS_TAG_INT && tag1 == JS_TAG_FLOAT64)) {
+        res = js_strict_eq(ctx, op1, op2);
+    } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
+               (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
+        res = TRUE;
+    } else if ((tag1 == JS_TAG_STRING && (tag2 == JS_TAG_INT ||
+                                   tag2 == JS_TAG_FLOAT64)) ||
+        (tag2 == JS_TAG_STRING && (tag1 == JS_TAG_INT ||
+                                   tag1 == JS_TAG_FLOAT64))) {
+        double d1;
+        double d2;
+        if (JS_ToFloat64Free(ctx, &d1, op1)) {
+            JS_FreeValue(ctx, op2);
+            goto exception;
+        }
+        if (JS_ToFloat64Free(ctx, &d2, op2))
+            goto exception;
+        res = (d1 == d2);
+    } else if (tag1 == JS_TAG_BOOL) {
+        op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1));
+        goto redo;
+    } else if (tag2 == JS_TAG_BOOL) {
+        op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2));
+        goto redo;
+    } else if (tag1 == JS_TAG_OBJECT &&
+               (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64 || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) {
+        op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
+        if (JS_IsException(op1)) {
+            JS_FreeValue(ctx, op2);
+            goto exception;
+        }
+        goto redo;
+    } else if (tag2 == JS_TAG_OBJECT &&
+               (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64 || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL)) {
+        op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
+        if (JS_IsException(op2)) {
+            JS_FreeValue(ctx, op1);
+            goto exception;
+        }
+        goto redo;
+    } else {
+        JS_FreeValue(ctx, op1);
+        JS_FreeValue(ctx, op2);
+        res = FALSE;
+    }
+    sp[-2] = JS_NewBool(ctx, res ^ is_neq);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2;
+    uint32_t v1, v2, r;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    if (unlikely(JS_ToUint32Free(ctx, &v1, op1))) {
+        JS_FreeValue(ctx, op2);
+        goto exception;
+    }
+    if (unlikely(JS_ToUint32Free(ctx, &v2, op2)))
+        goto exception;
+    r = v1 >> (v2 & 0x1f);
+    sp[-2] = JS_NewUint32(ctx, r);
+    return 0;
+ exception:
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+#endif /* !CONFIG_BIGNUM */
+
+/* XXX: Should take JSValueConst arguments */
+static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
+                          JSStrictEqModeEnum eq_mode)
+{
+    BOOL res;
+    int tag1, tag2;
+    double d1, d2;
+
+    tag1 = JS_VALUE_GET_NORM_TAG(op1);
+    tag2 = JS_VALUE_GET_NORM_TAG(op2);
+    switch(tag1) {
+    case JS_TAG_BOOL:
+        if (tag1 != tag2) {
+            res = FALSE;
+        } else {
+            res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
+            goto done_no_free;
+        }
+        break;
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        res = (tag1 == tag2);
+        break;
+    case JS_TAG_STRING:
+        {
+            JSString *p1, *p2;
+            if (tag1 != tag2) {
+                res = FALSE;
+            } else {
+                p1 = JS_VALUE_GET_STRING(op1);
+                p2 = JS_VALUE_GET_STRING(op2);
+                res = (js_string_compare(ctx, p1, p2) == 0);
+            }
+        }
+        break;
+    case JS_TAG_SYMBOL:
+        {
+            JSAtomStruct *p1, *p2;
+            if (tag1 != tag2) {
+                res = FALSE;
+            } else {
+                p1 = JS_VALUE_GET_PTR(op1);
+                p2 = JS_VALUE_GET_PTR(op2);
+                res = (p1 == p2);
+            }
+        }
+        break;
+    case JS_TAG_OBJECT:
+        if (tag1 != tag2)
+            res = FALSE;
+        else
+            res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2);
+        break;
+    case JS_TAG_INT:
+        d1 = JS_VALUE_GET_INT(op1);
+        if (tag2 == JS_TAG_INT) {
+            d2 = JS_VALUE_GET_INT(op2);
+            goto number_test;
+        } else if (tag2 == JS_TAG_FLOAT64) {
+#ifdef CONFIG_BIGNUM
+            if (is_bignum_mode(ctx)) {
+                res = FALSE;
+            } else
+#endif
+            {
+                d2 = JS_VALUE_GET_FLOAT64(op2);
+                goto number_test;
+            }
+        } else
+#ifdef CONFIG_BIGNUM
+        if (tag2 == JS_TAG_BIG_INT && is_bignum_mode(ctx)) {
+            goto bigint_test;
+        } else
+#endif
+        {
+            res = FALSE;
+        }
+        break;
+    case JS_TAG_FLOAT64:
+        d1 = JS_VALUE_GET_FLOAT64(op1);
+        if (tag2 == JS_TAG_FLOAT64) {
+            d2 = JS_VALUE_GET_FLOAT64(op2);
+        } else if (tag2 == JS_TAG_INT
+#ifdef CONFIG_BIGNUM
+                   && !is_bignum_mode(ctx)
+#endif
+                   ) {
+            d2 = JS_VALUE_GET_INT(op2);
+        } else {
+            res = FALSE;
+            break;
+        }
+    number_test:
+        if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) {
+            JSFloat64Union u1, u2;
+            /* NaN is not always normalized, so this test is necessary */
+            if (isnan(d1) || isnan(d2)) {
+                res = isnan(d1) == isnan(d2);
+            } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) {
+                res = (d1 == d2); /* +0 == -0 */
+            } else {
+                u1.d = d1;
+                u2.d = d2;
+                res = (u1.u64 == u2.u64); /* +0 != -0 */
+            }
+        } else {
+            res = (d1 == d2); /* if NaN return false and +0 == -0 */
+        }
+        goto done_no_free;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        {
+            bf_t a_s, *a, b_s, *b;
+            BOOL is_float;
+            if (tag1 == tag2) {
+                /* OK */
+            } else if (tag2 == JS_TAG_INT && is_bignum_mode(ctx)) {
+                /* OK */
+            } else {
+                res = FALSE;
+                break;
+            }
+        bigint_test:
+            a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+            b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+            res = bf_cmp_eq(a, b);
+            if (a == &a_s)
+                bf_delete(a);
+            if (b == &b_s)
+                bf_delete(a);
+        }
+        break;
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p1, *p2;
+            const bf_t *a, *b;
+            if (tag1 != tag2) {
+                res = FALSE;
+                break;
+            }
+            p1 = JS_VALUE_GET_PTR(op1);
+            p2 = JS_VALUE_GET_PTR(op2);
+            a = &p1->num;
+            b = &p2->num;
+            if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) {
+                if (eq_mode == JS_EQ_SAME_VALUE_ZERO &&
+                           a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) {
+                    res = TRUE;
+                } else {
+                    res = (bf_cmp_full(a, b) == 0);
+                }
+            } else {
+                res = bf_cmp_eq(a, b);
+            }
+        }
+        break;
+#endif
+    default:
+        res = FALSE;
+        break;
+    }
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+ done_no_free:
+    return res;
+}
+
+static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2)
+{
+    return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
+}
+
+static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2)
+{
+    return js_strict_eq2(ctx,
+                         JS_DupValue(ctx, op1), JS_DupValue(ctx, op2),
+                         JS_EQ_SAME_VALUE);
+}
+
+static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2)
+{
+    return js_strict_eq2(ctx,
+                         JS_DupValue(ctx, op1), JS_DupValue(ctx, op2),
+                         JS_EQ_SAME_VALUE_ZERO);
+}
+
+static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp,
+                                       BOOL is_neq)
+{
+    BOOL res;
+    res = js_strict_eq(ctx, sp[-2], sp[-1]);
+    sp[-2] = JS_NewBool(ctx, res ^ is_neq);
+    return 0;
+}
+
+static __exception int js_operator_in(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2;
+    JSAtom atom;
+    int ret;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+
+    if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) {
+        JS_ThrowTypeError(ctx, "invalid 'in' operand");
+        return -1;
+    }
+    atom = JS_ValueToAtom(ctx, op1);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return -1;
+    ret = JS_HasProperty(ctx, op2, atom);
+    JS_FreeAtom(ctx, atom);
+    if (ret < 0)
+        return -1;
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+    sp[-2] = JS_NewBool(ctx, ret);
+    return 0;
+}
+
+static __exception int js_has_unscopable(JSContext *ctx, JSValueConst obj,
+                                         JSAtom atom)
+{
+    JSValue arr, val;
+    int ret;
+
+    arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables);
+    if (JS_IsException(arr))
+        return -1;
+    ret = 0;
+    if (JS_IsObject(arr)) {
+        val = JS_GetProperty(ctx, arr, atom);
+        ret = JS_ToBoolFree(ctx, val);
+    }
+    JS_FreeValue(ctx, arr);
+    return ret;
+}
+
+static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2;
+    BOOL ret;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    ret = JS_IsInstanceOf(ctx, op1, op2);
+    if (ret < 0)
+        return ret;
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+    sp[-2] = JS_NewBool(ctx, ret);
+    return 0;
+}
+
+static __exception int js_operator_typeof(JSContext *ctx, JSValue op1)
+{
+    JSAtom atom;
+    uint32_t tag;
+
+    tag = JS_VALUE_GET_NORM_TAG(op1);
+    switch(tag) {
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_INT:
+        if (is_bignum_mode(ctx))
+            atom = JS_ATOM_bigint;
+        else
+            atom = JS_ATOM_number;
+        break;
+    case JS_TAG_BIG_INT:
+        atom = JS_ATOM_bigint;
+        break;
+    case JS_TAG_FLOAT64:
+        atom = JS_ATOM_number;
+        break;
+    case JS_TAG_BIG_FLOAT:
+        atom = JS_ATOM_bigfloat;
+        break;
+#else
+    case JS_TAG_INT:
+    case JS_TAG_FLOAT64:
+        atom = JS_ATOM_number;
+        break;
+#endif
+    case JS_TAG_UNDEFINED:
+        atom = JS_ATOM_undefined;
+        break;
+    case JS_TAG_BOOL:
+        atom = JS_ATOM_boolean;
+        break;
+    case JS_TAG_STRING:
+        atom = JS_ATOM_string;
+        break;
+    case JS_TAG_OBJECT:
+        if (JS_IsFunction(ctx, op1))
+            atom = JS_ATOM_function;
+        else
+            goto obj_type;
+        break;
+    case JS_TAG_NULL:
+    obj_type:
+        atom = JS_ATOM_object;
+        break;
+    case JS_TAG_SYMBOL:
+        atom = JS_ATOM_symbol;
+        break;
+    default:
+        atom = JS_ATOM_unknown;
+        break;
+    }
+    return atom;
+}
+
+static __exception int js_operator_delete(JSContext *ctx, JSValue *sp)
+{
+    JSValue op1, op2;
+    JSAtom atom;
+    int ret;
+
+    op1 = sp[-2];
+    op2 = sp[-1];
+    atom = JS_ValueToAtom(ctx, op2);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return -1;
+    ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT);
+    JS_FreeAtom(ctx, atom);
+    if (unlikely(ret < 0))
+        return -1;
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+    sp[-2] = JS_NewBool(ctx, ret);
+    return 0;
+}
+
+static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    return JS_ThrowTypeError(ctx, "invalid property access");
+}
+
+/* XXX: not 100% compatible, but mozilla seems to use a similar
+   implementation to ensure that caller in non strict mode does not
+   throw (ES5 compatibility) */
+static JSValue js_function_proto_caller(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
+    if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype) {
+        return js_throw_type_error(ctx, this_val, 0, NULL);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_function_proto_fileName(JSContext *ctx,
+                                          JSValueConst this_val)
+{
+    JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
+    if (b && b->has_debug) {
+        return JS_AtomToString(ctx, b->debug.filename);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_function_proto_lineNumber(JSContext *ctx,
+                                            JSValueConst this_val)
+{
+    JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
+    if (b && b->has_debug) {
+        return JS_NewInt32(ctx, b->debug.line_num);
+    }
+    return JS_UNDEFINED;
+}
+
+static int js_arguments_define_own_property(JSContext *ctx,
+                                            JSValueConst this_obj,
+                                            JSAtom prop, JSValueConst val,
+                                            JSValueConst getter, JSValueConst setter, int flags)
+{
+    JSObject *p;
+    uint32_t idx;
+    p = JS_VALUE_GET_OBJ(this_obj);
+    /* convert to normal array when redefining an existing numeric field */
+    if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) &&
+        idx < p->u.array.count) {
+        if (convert_fast_array_to_array(ctx, p))
+            return -1;
+    }
+    /* run the default define own property */
+    return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter,
+                             flags | JS_PROP_NO_EXOTIC);
+}
+
+static const JSClassExoticMethods js_arguments_exotic_methods = {
+    .define_own_property = js_arguments_define_own_property,
+};
+
+static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv)
+{
+    JSValue val, *tab;
+    JSProperty *pr;
+    JSObject *p;
+    int i;
+
+    val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                                 JS_CLASS_ARGUMENTS);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_OBJ(val);
+
+    /* add the length field (cannot fail) */
+    pr = add_property(ctx, p, JS_ATOM_length,
+                      JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    pr->u.value = JS_NewInt32(ctx, argc);
+
+    /* initialize the fast array part */
+    tab = NULL;
+    if (argc > 0) {
+        tab = js_malloc(ctx, sizeof(tab[0]) * argc);
+        if (!tab) {
+            JS_FreeValue(ctx, val);
+            return JS_EXCEPTION;
+        }
+        for(i = 0; i < argc; i++) {
+            tab[i] = JS_DupValue(ctx, argv[i]);
+        }
+    }
+    p->u.array.u.values = tab;
+    p->u.array.count = argc;
+
+    JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator,
+                           JS_DupValue(ctx, ctx->array_proto_values),
+                           JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
+    /* add callee property to throw a TypeError in strict mode */
+    JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED,
+                      ctx->throw_type_error, ctx->throw_type_error,
+                      JS_PROP_HAS_GET | JS_PROP_HAS_SET);
+    return val;
+}
+
+#define GLOBAL_VAR_OFFSET 0x40000000
+#define ARGUMENT_VAR_OFFSET 0x20000000
+
+/* legacy arguments object: add references to the function arguments */
+static JSValue js_build_mapped_arguments(JSContext *ctx, int argc,
+                                         JSValueConst *argv,
+                                         JSStackFrame *sf, int arg_count)
+{
+    JSValue val;
+    JSProperty *pr;
+    JSObject *p;
+    int i;
+
+    val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                                 JS_CLASS_MAPPED_ARGUMENTS);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_OBJ(val);
+
+    /* add the length field (cannot fail) */
+    pr = add_property(ctx, p, JS_ATOM_length,
+                      JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    pr->u.value = JS_NewInt32(ctx, argc);
+
+    for(i = 0; i < arg_count; i++) {
+        JSVarRef *var_ref;
+        var_ref = get_var_ref(ctx, sf, i, TRUE);
+        if (!var_ref)
+            goto fail;
+        pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF);
+        if (!pr) {
+            free_var_ref(ctx->rt, var_ref);
+            goto fail;
+        }
+        pr->u.var_ref = var_ref;
+    }
+
+    /* the arguments not mapped to the arguments of the function can
+       be normal properties */
+    for(i = arg_count; i < argc; i++) {
+        if (JS_DefinePropertyValueUint32(ctx, val, i,
+                                         JS_DupValue(ctx, argv[i]),
+                                         JS_PROP_C_W_E) < 0)
+            goto fail;
+    }
+
+    JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator,
+                           JS_DupValue(ctx, ctx->array_proto_values),
+                           JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
+    /* callee returns this function in non strict mode */
+    JS_DefinePropertyValue(ctx, val, JS_ATOM_callee,
+                           JS_DupValue(ctx, ctx->current_stack_frame->cur_func),
+                           JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
+    return val;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValueConst *argv)
+{
+    JSValue val;
+    int i, ret;
+
+    val = JS_NewArray(ctx);
+    if (JS_IsException(val))
+        return val;
+    for (i = first; i < argc; i++) {
+        ret = JS_DefinePropertyValueUint32(ctx, val, i - first,
+                                           JS_DupValue(ctx, argv[i]),
+                                           JS_PROP_C_W_E);
+        if (ret < 0) {
+            JS_FreeValue(ctx, val);
+            return JS_EXCEPTION;
+        }
+    }
+    return val;
+}
+
+static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj)
+{
+    JSObject *p, *p1;
+    JSPropertyEnum *tab_atom;
+    int i;
+    JSValue enum_obj;
+    JSForInIterator *it;
+    uint32_t tag, tab_atom_count;
+
+    tag = JS_VALUE_GET_TAG(obj);
+    if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) {
+        obj = JS_ToObjectFree(ctx, obj);
+    }
+
+    it = js_malloc(ctx, sizeof(*it));
+    if (!it) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR);
+    if (JS_IsException(enum_obj)) {
+        js_free(ctx, it);
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    it->is_array = FALSE;
+    it->obj = obj;
+    it->idx = 0;
+    p = JS_VALUE_GET_OBJ(enum_obj);
+    p->u.for_in_iterator = it;
+
+    if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)
+        return enum_obj;
+
+    p = JS_VALUE_GET_OBJ(obj);
+
+    /* fast path: assume no enumerable properties in the prototype chain */
+    p1 = p->shape->proto;
+    while (p1 != NULL) {
+        if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p1,
+                                   JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY))
+            goto fail;
+        js_free_prop_enum(ctx, tab_atom, tab_atom_count);
+        if (tab_atom_count != 0) {
+            goto slow_path;
+        }
+        p1 = p1->shape->proto;
+    }
+    if (p->fast_array) {
+        JSShape *sh;
+        JSShapeProperty *prs;
+        /* check that there are no enumerable normal fields */
+        sh = p->shape;
+        for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
+            if (prs->flags & JS_PROP_ENUMERABLE)
+                goto normal_case;
+        }
+        /* the implicit GetOwnProperty raises an exception if the
+           typed array is detached */
+        if ((p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+             p->class_id <= JS_CLASS_FLOAT64_ARRAY) &&
+            typed_array_is_detached(ctx, p) &&
+            typed_array_get_length(ctx, p) != 0) {
+            JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+            goto fail;
+        }
+        /* for fast arrays, we only store the number of elements */
+        it->is_array = TRUE;
+        it->array_length = p->u.array.count;
+    } else {
+    normal_case:
+        if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
+                                   JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY))
+            goto fail;
+        for(i = 0; i < tab_atom_count; i++) {
+            JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0);
+        }
+        js_free_prop_enum(ctx, tab_atom, tab_atom_count);
+    }
+    return enum_obj;
+
+ slow_path:
+    /* non enumerable properties hide the enumerables ones in the
+       prototype chain */
+    while (p != NULL) {
+        if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
+                                   JS_GPN_STRING_MASK | JS_GPN_SET_ENUM))
+            goto fail;
+        for(i = 0; i < tab_atom_count; i++) {
+            JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL,
+                                   (tab_atom[i].is_enumerable ?
+                                    JS_PROP_ENUMERABLE : 0));
+        }
+        js_free_prop_enum(ctx, tab_atom, tab_atom_count);
+        p = p->shape->proto;
+    }
+    return enum_obj;
+
+ fail:
+    JS_FreeValue(ctx, enum_obj);
+    return JS_EXCEPTION;
+}
+
+/* obj -> enum_obj */
+static __exception int js_for_in_start(JSContext *ctx, JSValue *sp)
+{
+    sp[-1] = build_for_in_iterator(ctx, sp[-1]);
+    if (JS_IsException(sp[-1]))
+        return -1;
+    return 0;
+}
+
+/* enum_obj -> enum_obj value done */
+static __exception int js_for_in_next(JSContext *ctx, JSValue *sp)
+{
+    JSValueConst enum_obj;
+    JSObject *p;
+    JSAtom prop;
+    JSForInIterator *it;
+    int ret;
+
+    enum_obj = sp[-1];
+    /* fail safe */
+    if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT)
+        goto done;
+    p = JS_VALUE_GET_OBJ(enum_obj);
+    if (p->class_id != JS_CLASS_FOR_IN_ITERATOR)
+        goto done;
+    it = p->u.for_in_iterator;
+
+    for(;;) {
+        if (it->is_array) {
+            if (it->idx >= it->array_length)
+                goto done;
+            prop = __JS_AtomFromUInt32(it->idx);
+            it->idx++;
+        } else {
+            JSShape *sh = p->shape;
+            JSShapeProperty *prs;
+            if (it->idx >= sh->prop_count)
+                goto done;
+            prs = get_shape_prop(sh) + it->idx;
+            prop = prs->atom;
+            it->idx++;
+            if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE))
+                continue;
+        }
+        /* check if the property was deleted */
+        ret = JS_HasProperty(ctx, it->obj, prop);
+        if (ret < 0)
+            return ret;
+        if (ret)
+            break;
+    }
+    /* return the property */
+    sp[0] = JS_AtomToValue(ctx, prop);
+    sp[1] = JS_FALSE;
+    return 0;
+ done:
+    /* return the end */
+    sp[0] = JS_UNDEFINED;
+    sp[1] = JS_TRUE;
+    return 0;
+}
+
+static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj,
+                               JSValueConst method)
+{
+    JSValue enum_obj;
+
+    enum_obj = JS_Call(ctx, method, obj, 0, NULL);
+    if (JS_IsException(enum_obj))
+        return enum_obj;
+    if (!JS_IsObject(enum_obj)) {
+        JS_FreeValue(ctx, enum_obj);
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    }
+    return enum_obj;
+}
+
+static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, BOOL is_async)
+{
+    JSValue method, ret, sync_iter;
+
+    if (is_async) {
+        method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator);
+        if (JS_IsException(method))
+            return method;
+        if (JS_IsUndefined(method) || JS_IsNull(method)) {
+            method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator);
+            if (JS_IsException(method))
+                return method;
+            sync_iter = JS_GetIterator2(ctx, obj, method);
+            JS_FreeValue(ctx, method);
+            if (JS_IsException(sync_iter))
+                return sync_iter;
+            ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter);
+            JS_FreeValue(ctx, sync_iter);
+            return ret;
+        }
+    } else {
+        method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator);
+        if (JS_IsException(method))
+            return method;
+    }
+    if (!JS_IsFunction(ctx, method)) {
+        JS_FreeValue(ctx, method);
+        return JS_ThrowTypeError(ctx, "value is not iterable");
+    }
+    ret = JS_GetIterator2(ctx, obj, method);
+    JS_FreeValue(ctx, method);
+    return ret;
+}
+
+/* return *pdone = 2 if the iterator object is not parsed */
+static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj,
+                                JSValueConst method,
+                                int argc, JSValueConst *argv, int *pdone)
+{
+    JSValue obj;
+
+    /* fast path for the built-in iterators (avoid creating the
+       intermediate result object) */
+    if (JS_IsObject(method)) {
+        JSObject *p = JS_VALUE_GET_OBJ(method);
+        if (p->class_id == JS_CLASS_C_FUNCTION &&
+            p->u.cfunc.cproto == JS_CFUNC_iterator_next) {
+            JSCFunctionType func;
+            JSValueConst args[1];
+
+            /* in case the function expects one argument */
+            if (argc == 0) {
+                args[0] = JS_UNDEFINED;
+                argv = args;
+            }
+            func = p->u.cfunc.c_function;
+            return func.iterator_next(ctx, enum_obj, argc, argv,
+                                      pdone, p->u.cfunc.magic);
+        }
+    }
+    obj = JS_Call(ctx, method, enum_obj, argc, argv);
+    if (JS_IsException(obj))
+        goto fail;
+    if (!JS_IsObject(obj)) {
+        JS_FreeValue(ctx, obj);
+        JS_ThrowTypeError(ctx, "iterator must return an object");
+        goto fail;
+    }
+    *pdone = 2;
+    return obj;
+ fail:
+    *pdone = FALSE;
+    return JS_EXCEPTION;
+}
+
+static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj,
+                               JSValueConst method,
+                               int argc, JSValueConst *argv, BOOL *pdone)
+{
+    JSValue obj, value, done_val;
+    int done;
+
+    obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done);
+    if (JS_IsException(obj))
+        goto fail;
+    if (done != 2) {
+        *pdone = done;
+        return obj;
+    } else {
+        done_val = JS_GetProperty(ctx, obj, JS_ATOM_done);
+        if (JS_IsException(done_val))
+            goto fail;
+        *pdone = JS_ToBoolFree(ctx, done_val);
+        value = JS_UNDEFINED;
+        if (!*pdone) {
+            value = JS_GetProperty(ctx, obj, JS_ATOM_value);
+        }
+        JS_FreeValue(ctx, obj);
+        return value;
+    }
+ fail:
+    JS_FreeValue(ctx, obj);
+    *pdone = FALSE;
+    return JS_EXCEPTION;
+}
+
+/* return < 0 in case of exception */
+static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj,
+                            BOOL is_exception_pending)
+{
+    JSValue method, ret, ex_obj;
+    int res;
+
+    if (is_exception_pending) {
+        ex_obj = ctx->current_exception;
+        ctx->current_exception = JS_NULL;
+        res = -1;
+    } else {
+        ex_obj = JS_UNDEFINED;
+        res = 0;
+    }
+    method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return);
+    if (JS_IsException(method)) {
+        res = -1;
+        goto done;
+    }
+    if (JS_IsUndefined(method) || JS_IsNull(method)) {
+        goto done;
+    }
+    ret = JS_CallFree(ctx, method, enum_obj, 0, NULL);
+    if (!is_exception_pending) {
+        if (JS_IsException(ret)) {
+            res = -1;
+        } else if (!JS_IsObject(ret)) {
+            JS_ThrowTypeErrorNotAnObject(ctx);
+            res = -1;
+        }
+    }
+    JS_FreeValue(ctx, ret);
+ done:
+    if (is_exception_pending) {
+        JS_Throw(ctx, ex_obj);
+    }
+    return res;
+}
+
+/* obj -> enum_rec (3 slots) */
+static __exception int js_for_of_start(JSContext *ctx, JSValue *sp,
+                                       BOOL is_async)
+{
+    JSValue op1, obj, method;
+    op1 = sp[-1];
+    obj = JS_GetIterator(ctx, op1, is_async);
+    if (JS_IsException(obj))
+        return -1;
+    JS_FreeValue(ctx, op1);
+    sp[-1] = obj;
+    method = JS_GetProperty(ctx, obj, JS_ATOM_next);
+    if (JS_IsException(method))
+        return -1;
+    sp[0] = method;
+    return 0;
+}
+
+/* enum_rec -> enum_rec value done */
+static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset)
+{
+    JSValue value = JS_UNDEFINED;
+    int done = 1;
+
+    if (likely(!JS_IsUndefined(sp[offset]))) {
+        value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done);
+        if (JS_IsException(value))
+            done = -1;
+        if (done) {
+            /* value is JS_UNDEFINED or JS_EXCEPTION */
+            /* replace the iteration object with undefined */
+            JS_FreeValue(ctx, sp[offset]);
+            sp[offset] = JS_UNDEFINED;
+            if (done < 0)
+                return -1;
+        }
+    }
+    sp[0] = value;
+    sp[1] = JS_NewBool(ctx, done);
+    return 0;
+}
+
+static __exception int js_for_await_of_next(JSContext *ctx, JSValue *sp)
+{
+    JSValue result;
+    result = JS_Call(ctx, sp[-2], sp[-3], 0, NULL);
+    if (JS_IsException(result))
+        return -1;
+    sp[0] = result;
+    return 0;
+}
+
+static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValueConst obj,
+                                           BOOL *pdone)
+{
+    JSValue done_val, value;
+    BOOL done;
+    done_val = JS_GetProperty(ctx, obj, JS_ATOM_done);
+    if (JS_IsException(done_val))
+        goto fail;
+    done = JS_ToBoolFree(ctx, done_val);
+    value = JS_GetProperty(ctx, obj, JS_ATOM_value);
+    if (JS_IsException(value))
+        goto fail;
+    *pdone = done;
+    return value;
+ fail:
+    *pdone = FALSE;
+    return JS_EXCEPTION;
+}
+
+static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp)
+{
+    JSValue obj, value;
+    BOOL done;
+    obj = sp[-1];
+    if (!JS_IsObject(obj)) {
+        JS_ThrowTypeError(ctx, "iterator must return an object");
+        return -1;
+    }
+    value = JS_IteratorGetCompleteValue(ctx, obj, &done);
+    if (JS_IsException(value))
+        return -1;
+    JS_FreeValue(ctx, obj);
+    sp[-1] = value;
+    sp[0] = JS_NewBool(ctx, done);
+    return 0;
+}
+
+static JSValue js_create_iterator_result(JSContext *ctx,
+                                         JSValue val,
+                                         BOOL done)
+{
+    JSValue obj;
+    obj = JS_NewObject(ctx);
+    if (JS_IsException(obj)) {
+        JS_FreeValue(ctx, val);
+        return obj;
+    }
+    if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value,
+                               val, JS_PROP_C_W_E) < 0) {
+        goto fail;
+    }
+    if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done,
+                               JS_NewBool(ctx, done), JS_PROP_C_W_E) < 0) {
+    fail:
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    return obj;
+}
+
+static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv,
+                                      BOOL *pdone, int magic);
+
+static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv, int magic);
+
+static BOOL js_is_fast_array(JSContext *ctx, JSValueConst obj)
+{
+    /* Try and handle fast arrays explicitly */
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(obj);
+        if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/* Access an Array's internal JSValue array if available */
+static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj,
+                              JSValue **arrpp, uint32_t *countp)
+{
+    /* Try and handle fast arrays explicitly */
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(obj);
+        if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
+            *countp = p->u.array.count;
+            *arrpp = p->u.array.u.values;
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp)
+{
+    JSValue iterator, enumobj, method, value;
+    int pos, is_array_iterator;
+    JSValue *arrp;
+    uint32_t i, count32;
+
+    if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) {
+        JS_ThrowInternalError(ctx, "invalid index for append");
+        return -1;
+    }
+
+    pos = JS_VALUE_GET_INT(sp[-2]);
+
+    /* XXX: further optimisations:
+       - use ctx->array_proto_values?
+       - check if array_iterator_prototype next method is built-in and
+         avoid constructing actual iterator object?
+       - build this into js_for_of_start and use in all `for (x of o)` loops
+     */
+    iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator);
+    if (JS_IsException(iterator))
+        return -1;
+    is_array_iterator = JS_IsCFunction(ctx, iterator,
+                                       (JSCFunction *)js_create_array_iterator,
+                                       JS_ITERATOR_KIND_VALUE);
+    JS_FreeValue(ctx, iterator);
+
+    enumobj = JS_GetIterator(ctx, sp[-1], FALSE);
+    if (JS_IsException(enumobj))
+        return -1;
+    method = JS_GetProperty(ctx, enumobj, JS_ATOM_next);
+    if (JS_IsException(method)) {
+        JS_FreeValue(ctx, enumobj);
+        return -1;
+    }
+    if (is_array_iterator
+    &&  JS_IsCFunction(ctx, method, (JSCFunction *)js_array_iterator_next, 0)
+    &&  js_get_fast_array(ctx, sp[-1], &arrp, &count32)) {
+        int64_t len;
+        /* Handle fast arrays explicitly */
+        if (js_get_length64(ctx, &len, sp[-1]))
+            goto exception;
+        for (i = 0; i < count32; i++) {
+            if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++,
+                                             JS_DupValue(ctx, arrp[i]), JS_PROP_C_W_E) < 0)
+                goto exception;
+        }
+        if (len > count32) {
+            /* This is not strictly correct because the trailing elements are
+               empty instead of undefined. Append undefined entries instead.
+             */
+            pos += len - count32;
+            if (JS_SetProperty(ctx, sp[-3], JS_ATOM_length, JS_NewUint32(ctx, pos)) < 0)
+                goto exception;
+        }
+    } else {
+        for (;;) {
+            BOOL done;
+            value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done);
+            if (JS_IsException(value))
+                goto exception;
+            if (done) {
+                /* value is JS_UNDEFINED */
+                break;
+            }
+            if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0)
+                goto exception;
+        }
+    }
+    sp[-2] = JS_NewInt32(ctx, pos);
+    JS_FreeValue(ctx, enumobj);
+    JS_FreeValue(ctx, method);
+    return 0;
+
+exception:
+    JS_IteratorClose(ctx, enumobj, TRUE);
+    JS_FreeValue(ctx, enumobj);
+    JS_FreeValue(ctx, method);
+    return -1;
+}
+
+static __exception int JS_CopyDataProperties(JSContext *ctx,
+                                             JSValueConst target,
+                                             JSValueConst source,
+                                             JSValueConst excluded,
+                                             BOOL setprop)
+{
+    JSPropertyEnum *tab_atom;
+    JSValue val;
+    uint32_t i, tab_atom_count;
+    JSObject *p;
+    JSObject *pexcl = NULL;
+    int ret = 0, flags;
+
+    if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT)
+        return 0;
+
+    if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT)
+        pexcl = JS_VALUE_GET_OBJ(excluded);
+
+    p = JS_VALUE_GET_OBJ(source);
+    if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
+                               JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK |
+                               JS_GPN_ENUM_ONLY))
+        return -1;
+
+    flags = JS_PROP_C_W_E;
+
+    for (i = 0; i < tab_atom_count; i++) {
+        if (pexcl) {
+            ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom);
+            if (ret) {
+                if (ret < 0)
+                    break;
+                ret = 0;
+                continue;
+            }
+        }
+        ret = -1;
+        val = JS_GetProperty(ctx, source, tab_atom[i].atom);
+        if (JS_IsException(val))
+            break;
+        if (setprop)
+            ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val);
+        else
+            ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, flags);
+        if (ret < 0)
+            break;
+        ret = 0;
+    }
+    js_free_prop_enum(ctx, tab_atom, tab_atom_count);
+    return ret;
+}
+
+/* only valid inside C functions */
+static JSValueConst JS_GetActiveFunction(JSContext *ctx)
+{
+    return ctx->current_stack_frame->cur_func;
+}
+
+static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf,
+                             int var_idx, BOOL is_arg)
+{
+    JSVarRef *var_ref;
+    struct list_head *el;
+
+    list_for_each(el, &sf->var_ref_list) {
+        var_ref = list_entry(el, JSVarRef, link);
+        if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) {
+            var_ref->header.ref_count++;
+            return var_ref;
+        }
+    }
+    /* create a new one */
+    var_ref = js_malloc(ctx, sizeof(JSVarRef));
+    if (!var_ref)
+        return NULL;
+    var_ref->header.ref_count = 1;
+    var_ref->gc_header.mark = 0;
+    var_ref->is_arg = is_arg;
+    var_ref->var_idx = var_idx;
+    list_add_tail(&var_ref->link, &sf->var_ref_list);
+    if (is_arg)
+        var_ref->pvalue = &sf->arg_buf[var_idx];
+    else
+        var_ref->pvalue = &sf->var_buf[var_idx];
+    var_ref->value = JS_UNDEFINED;
+    return var_ref;
+}
+
+static JSValue js_closure2(JSContext *ctx, JSValue func_obj,
+                           JSFunctionBytecode *b,
+                           JSVarRef **cur_var_refs,
+                           JSStackFrame *sf)
+{
+    JSObject *p;
+    JSVarRef **var_refs;
+    int i;
+
+    p = JS_VALUE_GET_OBJ(func_obj);
+    p->u.func.function_bytecode = b;
+    p->u.func.home_object = NULL;
+    p->u.func.var_refs = NULL;
+    if (b->closure_var_count) {
+        var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count);
+        if (!var_refs)
+            goto fail;
+        p->u.func.var_refs = var_refs;
+        for(i = 0; i < b->closure_var_count; i++) {
+            JSClosureVar *cv = &b->closure_var[i];
+            JSVarRef *var_ref;
+            if (cv->is_local) {
+                /* reuse the existing variable reference if it already exists */
+                var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg);
+                if (!var_ref)
+                    goto fail;
+            } else {
+                var_ref = cur_var_refs[cv->var_idx];
+                var_ref->header.ref_count++;
+            }
+            var_refs[i] = var_ref;
+        }
+    }
+    return func_obj;
+ fail:
+    /* bfunc is freed when func_obj is freed */
+    JS_FreeValue(ctx, func_obj);
+    return JS_EXCEPTION;
+}
+
+static int js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque)
+{
+    JSValue obj, this_val;
+    int ret;
+
+    this_val = JS_MKPTR(JS_TAG_OBJECT, p);
+    obj = JS_NewObject(ctx);
+    if (JS_IsException(obj))
+        return -1;
+    set_cycle_flag(ctx, obj);
+    set_cycle_flag(ctx, this_val);
+    ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor,
+                                 JS_DupValue(ctx, this_val),
+                                 JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    if (JS_DefinePropertyValue(ctx, this_val, atom, obj, JS_PROP_WRITABLE) < 0 || ret < 0)
+        return -1;
+    return 0;
+}
+
+static JSValue js_closure(JSContext *ctx, JSValue bfunc,
+                          JSVarRef **cur_var_refs,
+                          JSStackFrame *sf)
+{
+    JSFunctionBytecode *b;
+    JSValue func_obj;
+    JSAtom name_atom;
+    static const uint16_t func_kind_to_class_id[] = {
+        [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION,
+        [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION,
+        [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION,
+        [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION,
+    };
+
+    b = JS_VALUE_GET_PTR(bfunc);
+    func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]);
+    if (JS_IsException(func_obj)) {
+        JS_FreeValue(ctx, bfunc);
+        return JS_EXCEPTION;
+    }
+    func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf);
+    if (JS_IsException(func_obj)) {
+        /* bfunc has been freed */
+        goto fail;
+    }
+    name_atom = b->func_name;
+    if (name_atom == JS_ATOM_NULL)
+        name_atom = JS_ATOM_empty_string;
+    js_function_set_properties(ctx, func_obj, name_atom,
+                               b->defined_arg_count);
+
+    if (b->func_kind & JS_FUNC_GENERATOR) {
+        JSValue proto;
+        int proto_class_id;
+        /* generators have a prototype field which is used as
+           prototype for the generator object */
+        if (b->func_kind == JS_FUNC_ASYNC_GENERATOR)
+            proto_class_id = JS_CLASS_ASYNC_GENERATOR;
+        else
+            proto_class_id = JS_CLASS_GENERATOR;
+        proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]);
+        if (JS_IsException(proto))
+            goto fail;
+        JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto,
+                               JS_PROP_WRITABLE);
+    } else if (b->has_prototype) {
+        /* add the 'prototype' property: delay instantiation to avoid
+           creating cycles for every javascript function. The prototype
+           object is created on the fly when first accessed */
+        JS_SetConstructorBit(ctx, func_obj, TRUE);
+        JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype,
+                                  js_instantiate_prototype, NULL,
+                                  JS_PROP_WRITABLE);
+    }
+    return func_obj;
+ fail:
+    /* bfunc is freed when func_obj is freed */
+    JS_FreeValue(ctx, func_obj);
+    return JS_EXCEPTION;
+}
+
+#define JS_DEFINE_CLASS_HAS_HERITAGE     (1 << 0)
+
+static int js_op_define_class(JSContext *ctx, JSValue *sp,
+                              JSAtom class_name, int class_flags,
+                              JSVarRef **cur_var_refs,
+                              JSStackFrame *sf, BOOL is_computed_name)
+{
+    JSValue bfunc, parent_class, proto = JS_UNDEFINED;
+    JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED;
+    JSFunctionBytecode *b;
+
+    parent_class = sp[-2];
+    bfunc = sp[-1];
+
+    if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) {
+        if (JS_IsNull(parent_class)) {
+            parent_proto = JS_NULL;
+            parent_class = JS_DupValue(ctx, ctx->function_proto);
+        } else {
+            if (!JS_IsConstructor(ctx, parent_class)) {
+                JS_ThrowTypeError(ctx, "parent class must be constructor");
+                goto fail;
+            }
+            parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype);
+            if (JS_IsException(parent_proto))
+                goto fail;
+            if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) {
+                JS_ThrowTypeError(ctx, "parent prototype must be an object or null");
+                goto fail;
+            }
+        }
+    } else {
+        /* parent_class is JS_UNDEFINED in this case */
+        parent_proto = JS_DupValue(ctx, ctx->class_proto[JS_CLASS_OBJECT]);
+        parent_class = JS_DupValue(ctx, ctx->function_proto);
+    }
+    proto = JS_NewObjectProto(ctx, parent_proto);
+    if (JS_IsException(proto))
+        goto fail;
+
+    b = JS_VALUE_GET_PTR(bfunc);
+    assert(b->func_kind == JS_FUNC_NORMAL);
+    ctor = JS_NewObjectProtoClass(ctx, parent_class,
+                                  JS_CLASS_BYTECODE_FUNCTION);
+    if (JS_IsException(ctor))
+        goto fail;
+    ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf);
+    bfunc = JS_UNDEFINED;
+    if (JS_IsException(ctor))
+        goto fail;
+    js_method_set_home_object(ctx, ctor, proto);
+    JS_SetConstructorBit(ctx, ctor, TRUE);
+
+    JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length,
+                           JS_NewInt32(ctx, b->defined_arg_count),
+                           JS_PROP_CONFIGURABLE);
+
+    if (is_computed_name) {
+        if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3],
+                                        JS_PROP_CONFIGURABLE) < 0)
+            goto fail;
+    } else {
+        if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0)
+            goto fail;
+    }
+
+    /* the constructor property must be first. It can be overriden by
+       computed property names */
+    if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor,
+                               JS_DupValue(ctx, ctor),
+                               JS_PROP_CONFIGURABLE |
+                               JS_PROP_WRITABLE | JS_PROP_THROW) < 0)
+        goto fail;
+    /* set the prototype property */
+    if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype,
+                               JS_DupValue(ctx, proto), JS_PROP_THROW) < 0)
+        goto fail;
+    set_cycle_flag(ctx, ctor);
+    set_cycle_flag(ctx, proto);
+
+    JS_FreeValue(ctx, parent_proto);
+    JS_FreeValue(ctx, parent_class);
+
+    sp[-2] = ctor;
+    sp[-1] = proto;
+    return 0;
+ fail:
+    JS_FreeValue(ctx, parent_class);
+    JS_FreeValue(ctx, parent_proto);
+    JS_FreeValue(ctx, bfunc);
+    JS_FreeValue(ctx, proto);
+    JS_FreeValue(ctx, ctor);
+    sp[-2] = JS_UNDEFINED;
+    sp[-1] = JS_UNDEFINED;
+    return -1;
+}
+
+static void close_var_refs(JSRuntime *rt, JSStackFrame *sf)
+{
+    struct list_head *el, *el1;
+    JSVarRef *var_ref;
+    int var_idx;
+
+    list_for_each_safe(el, el1, &sf->var_ref_list) {
+        var_ref = list_entry(el, JSVarRef, link);
+        var_idx = var_ref->var_idx;
+        if (var_ref->is_arg)
+            var_ref->value = JS_DupValueRT(rt, sf->arg_buf[var_idx]);
+        else
+            var_ref->value = JS_DupValueRT(rt, sf->var_buf[var_idx]);
+        var_ref->pvalue = &var_ref->value;
+        var_ref->link.prev = NULL; /* the reference is no longer to a
+                                      local variable */
+    }
+}
+
+static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int idx, int is_arg)
+{
+    struct list_head *el, *el1;
+    JSVarRef *var_ref;
+    int var_idx = idx;
+
+    list_for_each_safe(el, el1, &sf->var_ref_list) {
+        var_ref = list_entry(el, JSVarRef, link);
+        if (var_idx == var_ref->var_idx && var_ref->is_arg == is_arg) {
+            var_ref->value = JS_DupValue(ctx, sf->var_buf[var_idx]);
+            var_ref->pvalue = &var_ref->value;
+            list_del(&var_ref->link);
+            var_ref->link.prev = NULL; /* the reference is no longer to a
+                                          local variable */
+        }
+    }
+}
+
+#define CALL_FLAG_CONSTRUCTOR (1 << 0)
+#define CALL_FLAG_COPY_ARGV   (1 << 1)
+#define CALL_FLAG_GENERATOR   (1 << 2)
+
+static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj,
+                                  JSValueConst this_obj,
+                                  int argc, JSValueConst *argv, int flags)
+{
+    JSCFunctionType func;
+    JSObject *p;
+    JSStackFrame sf_s, *sf = &sf_s, *prev_sf;
+    JSValue ret_val;
+    JSValueConst *arg_buf;
+    int arg_count, i;
+    JSCFunctionEnum cproto;
+
+    p = JS_VALUE_GET_OBJ(func_obj);
+    cproto = p->u.cfunc.cproto;
+    arg_count = p->u.cfunc.length;
+
+    /* better to always check stack overflow */
+    if (js_check_stack_overflow(ctx, sizeof(arg_buf[0]) * arg_count))
+        return JS_ThrowStackOverflow(ctx);
+
+    prev_sf = ctx->current_stack_frame;
+    sf->prev_frame = prev_sf;
+    ctx->current_stack_frame = sf;
+#ifdef CONFIG_BIGNUM
+    /* we only propagate the bignum mode as some runtime functions
+       test it */
+    if (prev_sf)
+        sf->js_mode = prev_sf->js_mode & JS_MODE_BIGINT;
+    else
+        sf->js_mode = 0;
+#else
+    sf->js_mode = 0;
+#endif
+    sf->cur_func = (JSValue)func_obj;
+    sf->arg_count = argc;
+    arg_buf = argv;
+
+    if (unlikely(argc < arg_count)) {
+        /* ensure that at least argc_count arguments are readable */
+        arg_buf = alloca(sizeof(arg_buf[0]) * arg_count);
+        for(i = 0; i < argc; i++)
+            arg_buf[i] = argv[i];
+        for(i = argc; i < arg_count; i++)
+            arg_buf[i] = JS_UNDEFINED;
+        sf->arg_count = arg_count;
+    }
+    sf->arg_buf = (JSValue*)arg_buf;
+
+    func = p->u.cfunc.c_function;
+    switch(cproto) {
+    case JS_CFUNC_constructor:
+    case JS_CFUNC_constructor_or_func:
+        if (!(flags & CALL_FLAG_CONSTRUCTOR)) {
+            if (cproto == JS_CFUNC_constructor) {
+            not_a_constructor:
+                ret_val = JS_ThrowTypeError(ctx, "must be called with new");
+                break;
+            } else {
+                this_obj = JS_UNDEFINED;
+            }
+        }
+        /* here this_obj is new_target */
+        /* fall thru */
+    case JS_CFUNC_generic:
+        ret_val = func.generic(ctx, this_obj, argc, arg_buf);
+        break;
+    case JS_CFUNC_constructor_magic:
+    case JS_CFUNC_constructor_or_func_magic:
+        if (!(flags & CALL_FLAG_CONSTRUCTOR)) {
+            if (cproto == JS_CFUNC_constructor_magic) {
+                goto not_a_constructor;
+            } else {
+                this_obj = JS_UNDEFINED;
+            }
+        }
+        /* fall thru */
+    case JS_CFUNC_generic_magic:
+        ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf,
+                                     p->u.cfunc.magic);
+        break;
+    case JS_CFUNC_getter:
+        ret_val = func.getter(ctx, this_obj);
+        break;
+    case JS_CFUNC_setter:
+        ret_val = func.setter(ctx, this_obj, arg_buf[0]);
+        break;
+    case JS_CFUNC_getter_magic:
+        ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic);
+        break;
+    case JS_CFUNC_setter_magic:
+        ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic);
+        break;
+    case JS_CFUNC_f_f:
+        {
+            double d1;
+
+            if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) {
+                ret_val = JS_EXCEPTION;
+                break;
+            }
+            ret_val = JS_NewFloat64(ctx, func.f_f(d1));
+        }
+        break;
+    case JS_CFUNC_f_f_f:
+        {
+            double d1, d2;
+
+            if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) {
+                ret_val = JS_EXCEPTION;
+                break;
+            }
+            if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) {
+                ret_val = JS_EXCEPTION;
+                break;
+            }
+            ret_val = JS_NewFloat64(ctx, func.f_f_f(d1, d2));
+        }
+        break;
+    case JS_CFUNC_iterator_next:
+        {
+            int done;
+            ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf,
+                                         &done, p->u.cfunc.magic);
+            if (!JS_IsException(ret_val) && done != 2) {
+                ret_val = js_create_iterator_result(ctx, ret_val, done);
+            }
+        }
+        break;
+    default:
+        abort();
+    }
+
+    ctx->current_stack_frame = sf->prev_frame;
+    return ret_val;
+}
+
+static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj,
+                                      JSValueConst this_obj,
+                                      int argc, JSValueConst *argv, int flags)
+{
+    JSObject *p;
+    JSBoundFunction *bf;
+    JSValueConst *arg_buf, new_target;
+    int arg_count, i;
+
+    p = JS_VALUE_GET_OBJ(func_obj);
+    bf = p->u.bound_function;
+    arg_count = bf->argc + argc;
+    if (js_check_stack_overflow(ctx, sizeof(JSValue) * arg_count))
+        return JS_ThrowStackOverflow(ctx);
+    arg_buf = alloca(sizeof(JSValue) * arg_count);
+    for(i = 0; i < bf->argc; i++) {
+        arg_buf[i] = bf->argv[i];
+    }
+    for(i = 0; i < argc; i++) {
+        arg_buf[bf->argc + i] = argv[i];
+    }
+    if (flags & CALL_FLAG_CONSTRUCTOR) {
+        new_target = this_obj;
+        if (js_same_value(ctx, func_obj, new_target))
+            new_target = bf->func_obj;
+        return JS_CallConstructor2(ctx, bf->func_obj, new_target,
+                                   arg_count, arg_buf);
+    } else {
+        return JS_Call(ctx, bf->func_obj, bf->this_val,
+                       arg_count, arg_buf);
+    }
+}
+
+static no_inline __exception int __js_poll_interrupts(JSContext *ctx)
+{
+    JSRuntime *rt = ctx->rt;
+    ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
+    if (rt->interrupt_handler) {
+        if (rt->interrupt_handler(rt, rt->interrupt_opaque)) {
+            /* XXX: should set a specific flag to avoid catching */
+            JS_ThrowInternalError(ctx, "interrupted");
+            JS_SetUncatchableError(ctx, ctx->current_exception, TRUE);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static inline __exception int js_poll_interrupts(JSContext *ctx)
+{
+    if (unlikely(--ctx->interrupt_counter <= 0)) {
+        return __js_poll_interrupts(ctx);
+    } else {
+        return 0;
+    }
+}
+
+/* argument of OP_special_object */
+typedef enum {
+    OP_SPECIAL_OBJECT_ARGUMENTS,
+    OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS,
+    OP_SPECIAL_OBJECT_THIS_FUNC,
+    OP_SPECIAL_OBJECT_NEW_TARGET,
+    OP_SPECIAL_OBJECT_HOME_OBJECT,
+    OP_SPECIAL_OBJECT_VAR_OBJECT,
+    OP_SPECIAL_OBJECT_IMPORT_META,
+} OPSpecialObjectEnum;
+
+#define FUNC_RET_AWAIT      0
+#define FUNC_RET_YIELD      1
+#define FUNC_RET_YIELD_STAR 2
+
+/* argv[] is modified if (flags & CALL_FLAG_COPY_ARGV) = 0. */
+static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj,
+                               JSValueConst this_obj, JSValueConst new_target,
+                               int argc, JSValue *argv, int flags)
+{
+    JSObject *p;
+    JSFunctionBytecode *b;
+    JSStackFrame sf_s, *sf = &sf_s;
+    const uint8_t *pc;
+    int opcode, arg_allocated_size, i;
+    JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval;
+    JSVarRef **var_refs;
+    size_t alloca_size;
+
+#if !DIRECT_DISPATCH
+#define SWITCH(pc)      switch (opcode = *pc++)
+#define CASE(op)        case op
+#define DEFAULT         default
+#define BREAK           break
+#else
+    static const void * const dispatch_table[256] = {
+#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id,
+#if SHORT_OPCODES
+#define def(id, size, n_pop, n_push, f)
+#else                                                     
+#define def(id, size, n_pop, n_push, f) && case_default,
+#endif
+#include "quickjs-opcode.h"
+        [ OP_COUNT ... 255 ] = &&case_default
+    };
+#define SWITCH(pc)      goto *dispatch_table[opcode = *pc++];
+#define CASE(op)        case_ ## op
+#define DEFAULT         case_default
+#define BREAK           SWITCH(pc)
+#endif
+
+    if (js_poll_interrupts(ctx))
+        return JS_EXCEPTION;
+    if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) {
+        if (flags & CALL_FLAG_GENERATOR) {
+            JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj);
+            /* func_obj get contains a pointer to JSFuncAsyncState */
+            /* the stack frame is already allocated */
+            sf = &s->frame;
+            p = JS_VALUE_GET_OBJ(sf->cur_func);
+            b = p->u.func.function_bytecode;
+            var_refs = p->u.func.var_refs;
+            local_buf = arg_buf = sf->arg_buf;
+            var_buf = sf->var_buf;
+            stack_buf = sf->var_buf + b->var_count;
+            sp = sf->cur_sp;
+            sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */
+            pc = sf->cur_pc;
+            sf->prev_frame = ctx->current_stack_frame;
+            ctx->current_stack_frame = sf;
+            if (s->throw_flag)
+                goto exception;
+            else
+                goto restart;
+        } else {
+            goto not_a_function;
+        }
+    }
+    p = JS_VALUE_GET_OBJ(func_obj);
+    if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
+        switch(p->class_id) {
+        case JS_CLASS_C_FUNCTION:
+            return js_call_c_function(ctx, func_obj, this_obj, argc,
+                                      (JSValueConst *)argv, flags);
+        case JS_CLASS_BOUND_FUNCTION:
+            return js_call_bound_function(ctx, func_obj, this_obj, argc,
+                                          (JSValueConst *)argv, flags);
+        default:
+            {
+                JSClassCall *call_func;
+                call_func = ctx->rt->class_array[p->class_id].call;
+                if (!call_func) {
+                not_a_function:
+                    return JS_ThrowTypeError(ctx, "not a function");
+                }
+                return call_func(ctx, func_obj, this_obj, argc,
+                                 (JSValueConst *)argv);
+            }
+        }
+    }
+    b = p->u.func.function_bytecode;
+
+    if (unlikely(argc < b->arg_count || (flags & CALL_FLAG_COPY_ARGV))) {
+        arg_allocated_size = b->arg_count;
+    } else {
+        arg_allocated_size = 0;
+    }
+
+    alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count +
+                                     b->stack_size);
+    if (js_check_stack_overflow(ctx, alloca_size))
+        return JS_ThrowStackOverflow(ctx);
+
+    sf->js_mode = b->js_mode;
+    arg_buf = argv;
+    sf->arg_count = argc;
+    sf->cur_func = (JSValue)func_obj;
+    init_list_head(&sf->var_ref_list);
+    var_refs = p->u.func.var_refs;
+
+    local_buf = alloca(alloca_size);
+    if (unlikely(arg_allocated_size)) {
+        int n = min_int(argc, b->arg_count);
+        arg_buf = local_buf;
+        for(i = 0; i < n; i++)
+            arg_buf[i] = JS_DupValue(ctx, argv[i]);
+        for(; i < b->arg_count; i++)
+            arg_buf[i] = JS_UNDEFINED;
+        sf->arg_count = b->arg_count;
+    }
+    var_buf = local_buf + arg_allocated_size;
+    sf->var_buf = var_buf;
+    sf->arg_buf = arg_buf;
+
+    for(i = 0; i < b->var_count; i++)
+        var_buf[i] = JS_UNDEFINED;
+
+    stack_buf = var_buf + b->var_count;
+    sp = stack_buf;
+    pc = b->byte_code_buf;
+    sf->prev_frame = ctx->current_stack_frame;
+    ctx->current_stack_frame = sf;
+ restart:
+    for(;;) {
+        int call_argc;
+        JSValue *call_argv;
+
+        SWITCH(pc) {
+        CASE(OP_push_i32):
+            *sp++ = JS_NewInt32(ctx, get_u32(pc));
+            pc += 4;
+            BREAK;
+        CASE(OP_push_const):
+            *sp++ = JS_DupValue(ctx, b->cpool[get_u32(pc)]);
+            pc += 4;
+            BREAK;
+#if SHORT_OPCODES
+        CASE(OP_push_minus1):
+        CASE(OP_push_0):
+        CASE(OP_push_1):
+        CASE(OP_push_2):
+        CASE(OP_push_3):
+        CASE(OP_push_4):
+        CASE(OP_push_5):
+        CASE(OP_push_6):
+        CASE(OP_push_7):
+            *sp++ = JS_NewInt32(ctx, opcode - OP_push_0);
+            BREAK;
+        CASE(OP_push_i8):
+            *sp++ = JS_NewInt32(ctx, get_i8(pc));
+            pc += 1;
+            BREAK;
+        CASE(OP_push_i16):
+            *sp++ = JS_NewInt32(ctx, get_i16(pc));
+            pc += 2;
+            BREAK;
+        CASE(OP_push_const8):
+            *sp++ = JS_DupValue(ctx, b->cpool[*pc++]);
+            BREAK;
+        CASE(OP_fclosure8):
+            *sp++ = js_closure(ctx, JS_DupValue(ctx, b->cpool[*pc++]), var_refs, sf);
+            if (unlikely(JS_IsException(sp[-1])))
+                goto exception;
+            BREAK;
+        CASE(OP_push_empty_string):
+            *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string);
+            BREAK;
+        CASE(OP_get_length):
+            {
+                JSValue val;
+
+                val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length);
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = val;
+            }
+            BREAK;
+#endif
+        CASE(OP_push_atom_value):
+            *sp++ = JS_AtomToValue(ctx, get_u32(pc));
+            pc += 4;
+            BREAK;
+        CASE(OP_undefined):
+            *sp++ = JS_UNDEFINED;
+            BREAK;
+        CASE(OP_null):
+            *sp++ = JS_NULL;
+            BREAK;
+        CASE(OP_push_this):
+            /* OP_push_this is only called at the start of a function */
+            {
+                JSValue val;
+                if (!(b->js_mode & JS_MODE_STRICT)) {
+                    uint32_t tag = JS_VALUE_GET_TAG(this_obj);
+                    if (likely(tag == JS_TAG_OBJECT))
+                        goto normal_this;
+                    if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) {
+                        val = JS_DupValue(ctx, ctx->global_obj);
+                    } else {
+                        val = JS_ToObject(ctx, this_obj);
+                        if (JS_IsException(val))
+                            goto exception;
+                    }
+                } else {
+                normal_this:
+                    val = JS_DupValue(ctx, this_obj);
+                }
+                *sp++ = val;
+            }
+            BREAK;
+        CASE(OP_push_false):
+            *sp++ = JS_FALSE;
+            BREAK;
+        CASE(OP_push_true):
+            *sp++ = JS_TRUE;
+            BREAK;
+        CASE(OP_object):
+            *sp++ = JS_NewObject(ctx);
+            if (unlikely(JS_IsException(sp[-1])))
+                goto exception;
+            BREAK;
+        CASE(OP_special_object):
+            {
+                int arg = *pc++;
+                switch(arg) {
+                case OP_SPECIAL_OBJECT_ARGUMENTS:
+                    *sp++ = js_build_arguments(ctx, argc, (JSValueConst *)argv);
+                    if (unlikely(JS_IsException(sp[-1])))
+                        goto exception;
+                    break;
+                case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS:
+                    *sp++ = js_build_mapped_arguments(ctx, argc, (JSValueConst *)argv,
+                                                      sf, min_int(argc, b->arg_count));
+                    if (unlikely(JS_IsException(sp[-1])))
+                        goto exception;
+                    break;
+                case OP_SPECIAL_OBJECT_THIS_FUNC:
+                    *sp++ = JS_DupValue(ctx, sf->cur_func);
+                    break;
+                case OP_SPECIAL_OBJECT_NEW_TARGET:
+                    *sp++ = JS_DupValue(ctx, new_target);
+                    break;
+                case OP_SPECIAL_OBJECT_HOME_OBJECT:
+                    {
+                        JSObject *p1;
+                        p1 = p->u.func.home_object;
+                        if (unlikely(!p1))
+                            *sp++ = JS_UNDEFINED;
+                        else
+                            *sp++ = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1));
+                    }
+                    break;
+                case OP_SPECIAL_OBJECT_VAR_OBJECT:
+                    *sp++ = JS_NewObjectProto(ctx, JS_NULL);
+                    if (unlikely(JS_IsException(sp[-1])))
+                        goto exception;
+                    break;
+                case OP_SPECIAL_OBJECT_IMPORT_META:
+                    *sp++ = js_import_meta(ctx);
+                    if (unlikely(JS_IsException(sp[-1])))
+                        goto exception;
+                    break;
+                default:
+                    abort();
+                }
+            }
+            BREAK;
+        CASE(OP_rest):
+            {
+                int first = get_u16(pc);
+                pc += 2;
+                *sp++ = js_build_rest(ctx, first, argc, (JSValueConst *)argv);
+                if (unlikely(JS_IsException(sp[-1])))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_drop):
+            JS_FreeValue(ctx, sp[-1]);
+            sp--;
+            BREAK;
+        CASE(OP_nip):
+            JS_FreeValue(ctx, sp[-2]);
+            sp[-2] = sp[-1];
+            sp--;
+            BREAK;
+        CASE(OP_nip1): /* a b c -> b c */
+            JS_FreeValue(ctx, sp[-3]);
+            sp[-3] = sp[-2];
+            sp[-2] = sp[-1];
+            sp--;
+            BREAK;
+        CASE(OP_dup):
+            sp[0] = JS_DupValue(ctx, sp[-1]);
+            sp++;
+            BREAK;
+        CASE(OP_dup2): /* a b -> a b a b */
+            sp[0] = JS_DupValue(ctx, sp[-2]);
+            sp[1] = JS_DupValue(ctx, sp[-1]);
+            sp += 2;
+            BREAK;
+        CASE(OP_dup3): /* a b c -> a b c a b c */
+            sp[0] = JS_DupValue(ctx, sp[-3]);
+            sp[1] = JS_DupValue(ctx, sp[-2]);
+            sp[2] = JS_DupValue(ctx, sp[-1]);
+            sp += 3;
+            BREAK;
+        CASE(OP_dup1): /* a b -> a a b */
+            sp[0] = sp[-1];
+            sp[-1] = JS_DupValue(ctx, sp[-2]);
+            sp++;
+            BREAK;
+        CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */
+            sp[0] = sp[-1];
+            sp[-1] = sp[-2];
+            sp[-2] = JS_DupValue(ctx, sp[0]);
+            sp++;
+            BREAK;
+        CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */
+            sp[0] = sp[-1];
+            sp[-1] = sp[-2];
+            sp[-2] = sp[-3];
+            sp[-3] = JS_DupValue(ctx, sp[0]);
+            sp++;
+            BREAK;
+        CASE(OP_insert4): /* this obj prop a -> a this obj prop a */
+            sp[0] = sp[-1];
+            sp[-1] = sp[-2];
+            sp[-2] = sp[-3];
+            sp[-3] = sp[-4];
+            sp[-4] = JS_DupValue(ctx, sp[0]);
+            sp++;
+            BREAK;
+        CASE(OP_perm3): /* obj a b -> a obj b (213) */
+            {
+                JSValue tmp;
+                tmp = sp[-2];
+                sp[-2] = sp[-3];
+                sp[-3] = tmp;
+            }
+            BREAK;
+        CASE(OP_rot3l): /* x a b -> a b x (231) */
+            {
+                JSValue tmp;
+                tmp = sp[-3];
+                sp[-3] = sp[-2];
+                sp[-2] = sp[-1];
+                sp[-1] = tmp;
+            }
+            BREAK;
+        CASE(OP_rot4l): /* x a b c -> a b c x */
+            {
+                JSValue tmp;
+                tmp = sp[-4];
+                sp[-4] = sp[-3];
+                sp[-3] = sp[-2];
+                sp[-2] = sp[-1];
+                sp[-1] = tmp;
+            }
+            BREAK;
+        CASE(OP_rot5l): /* x a b c d -> a b c d x */
+            {
+                JSValue tmp;
+                tmp = sp[-5];
+                sp[-5] = sp[-4];
+                sp[-4] = sp[-3];
+                sp[-3] = sp[-2];
+                sp[-2] = sp[-1];
+                sp[-1] = tmp;
+            }
+            BREAK;
+        CASE(OP_rot3r): /* a b x -> x a b (312) */
+            {
+                JSValue tmp;
+                tmp = sp[-1];
+                sp[-1] = sp[-2];
+                sp[-2] = sp[-3];
+                sp[-3] = tmp;
+            }
+            BREAK;
+        CASE(OP_perm4): /* obj prop a b -> a obj prop b */
+            {
+                JSValue tmp;
+                tmp = sp[-2];
+                sp[-2] = sp[-3];
+                sp[-3] = sp[-4];
+                sp[-4] = tmp;
+            }
+            BREAK;
+        CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */
+            {
+                JSValue tmp;
+                tmp = sp[-2];
+                sp[-2] = sp[-3];
+                sp[-3] = sp[-4];
+                sp[-4] = sp[-5];
+                sp[-5] = tmp;
+            }
+            BREAK;
+        CASE(OP_swap): /* a b -> b a */
+            {
+                JSValue tmp;
+                tmp = sp[-2];
+                sp[-2] = sp[-1];
+                sp[-1] = tmp;
+            }
+            BREAK;
+        CASE(OP_swap2): /* a b c d -> c d a b */
+            {
+                JSValue tmp1, tmp2;
+                tmp1 = sp[-4];
+                tmp2 = sp[-3];
+                sp[-4] = sp[-2];
+                sp[-3] = sp[-1];
+                sp[-2] = tmp1;
+                sp[-1] = tmp2;
+            }
+            BREAK;
+
+        CASE(OP_fclosure):
+            {
+                JSValue bfunc = JS_DupValue(ctx, b->cpool[get_u32(pc)]);
+                pc += 4;
+                *sp++ = js_closure(ctx, bfunc, var_refs, sf);
+                if (unlikely(JS_IsException(sp[-1])))
+                    goto exception;
+            }
+            BREAK;
+#if SHORT_OPCODES
+        CASE(OP_call0):
+        CASE(OP_call1):
+        CASE(OP_call2):
+        CASE(OP_call3):
+            call_argc = opcode - OP_call0;
+            goto has_call_argc;
+#endif
+        CASE(OP_call):
+        CASE(OP_tail_call):
+            {
+                call_argc = get_u16(pc);
+                pc += 2;
+                goto has_call_argc;
+            has_call_argc:
+                call_argv = sp - call_argc;
+                sf->cur_pc = pc;
+                ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED,
+                                          JS_UNDEFINED, call_argc, call_argv, 0);
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                if (opcode == OP_tail_call)
+                    goto done;
+                for(i = -1; i < call_argc; i++)
+                    JS_FreeValue(ctx, call_argv[i]);
+                sp -= call_argc + 1;
+                *sp++ = ret_val;
+            }
+            BREAK;
+        CASE(OP_call_constructor):
+            {
+                call_argc = get_u16(pc);
+                pc += 2;
+                call_argv = sp - call_argc;
+                sf->cur_pc = pc;
+                ret_val = JS_CallConstructorInternal(ctx, call_argv[-2],
+                                                     call_argv[-1],
+                                                     call_argc, call_argv, 0);
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                for(i = -2; i < call_argc; i++)
+                    JS_FreeValue(ctx, call_argv[i]);
+                sp -= call_argc + 2;
+                *sp++ = ret_val;
+            }
+            BREAK;
+        CASE(OP_call_method):
+        CASE(OP_tail_call_method):
+            {
+                call_argc = get_u16(pc);
+                pc += 2;
+                call_argv = sp - call_argc;
+                sf->cur_pc = pc;
+                ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2],
+                                          JS_UNDEFINED, call_argc, call_argv, 0);
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                if (opcode == OP_tail_call_method)
+                    goto done;
+                for(i = -2; i < call_argc; i++)
+                    JS_FreeValue(ctx, call_argv[i]);
+                sp -= call_argc + 2;
+                *sp++ = ret_val;
+            }
+            BREAK;
+        CASE(OP_array_from):
+            {
+                int i, ret;
+
+                call_argc = get_u16(pc);
+                pc += 2;
+                ret_val = JS_NewArray(ctx);
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                call_argv = sp - call_argc;
+                for(i = 0; i < call_argc; i++) {
+                    ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i],
+                                                 JS_PROP_C_W_E | JS_PROP_THROW);
+                    call_argv[i] = JS_UNDEFINED;
+                    if (ret < 0) {
+                        JS_FreeValue(ctx, ret_val);
+                        goto exception;
+                    }
+                }
+                sp -= call_argc;
+                *sp++ = ret_val;
+            }
+            BREAK;
+
+        CASE(OP_apply):
+            {
+                int magic;
+                magic = get_u16(pc);
+                pc += 2;
+
+                ret_val = js_function_apply(ctx, sp[-3], 2, (JSValueConst *)&sp[-2], magic);
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-3]);
+                JS_FreeValue(ctx, sp[-2]);
+                JS_FreeValue(ctx, sp[-1]);
+                sp -= 3;
+                *sp++ = ret_val;
+            }
+            BREAK;
+        CASE(OP_return):
+            ret_val = *--sp;
+            goto done;
+        CASE(OP_return_undef):
+            ret_val = JS_UNDEFINED;
+            goto done;
+
+        CASE(OP_check_ctor_return):
+            /* return TRUE if 'this' should be returned */
+            if (!JS_IsObject(sp[-1])) {
+                if (!JS_IsUndefined(sp[-1])) {
+                    JS_ThrowTypeError(ctx, "derived class constructor must return an object or undefined");
+                    goto exception;
+                }
+                sp[0] = JS_TRUE;
+            } else {
+                sp[0] = JS_FALSE;
+            }
+            sp++;
+            BREAK;
+        CASE(OP_check_ctor):
+            if (JS_IsUndefined(new_target)) {
+                JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'");
+                goto exception;
+            }
+            BREAK;
+        CASE(OP_check_brand):
+            if (JS_CheckBrand(ctx, sp[-2], sp[-1]) < 0)
+                goto exception;
+            BREAK;
+        CASE(OP_add_brand):
+            if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0)
+                goto exception;
+            JS_FreeValue(ctx, sp[-2]);
+            JS_FreeValue(ctx, sp[-1]);
+            sp -= 2;
+            BREAK;
+            
+        CASE(OP_throw):
+            JS_Throw(ctx, *--sp);
+            goto exception;
+
+        CASE(OP_throw_var):
+#define JS_THROW_VAR_RO             0
+#define JS_THROW_VAR_REDECL         1
+#define JS_THROW_VAR_UNINITIALIZED  2
+#define JS_THROW_VAR_DELETE_SUPER   3
+            {
+                JSAtom atom;
+                int type;
+                atom = get_u32(pc);
+                type = pc[4];
+                pc += 5;
+                if (type == JS_THROW_VAR_RO)
+                    JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom);
+                else
+                if (type == JS_THROW_VAR_REDECL)
+                    JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom);
+                else
+                if (type == JS_THROW_VAR_UNINITIALIZED)
+                    JS_ThrowReferenceErrorUninitialized(ctx, atom);
+                else
+                if (type == JS_THROW_VAR_DELETE_SUPER)
+                    JS_ThrowReferenceError(ctx, "unsupported reference to 'super'");
+                else
+                    JS_ThrowInternalError(ctx, "invalid throw var type %d", type);
+            }
+            goto exception;
+
+        CASE(OP_eval):
+            {
+                JSValueConst obj;
+                int scope_idx;
+                call_argc = get_u16(pc);
+                scope_idx = get_u16(pc + 2) - 1;
+                pc += 4;
+                call_argv = sp - call_argc;
+                sf->cur_pc = pc;
+                if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) {
+                    if (call_argc >= 1)
+                        obj = call_argv[0];
+                    else
+                        obj = JS_UNDEFINED;
+                    ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj,
+                                            JS_EVAL_TYPE_DIRECT, scope_idx);
+                } else {
+                    ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED,
+                                              JS_UNDEFINED, call_argc, call_argv, 0);
+                }
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                for(i = -1; i < call_argc; i++)
+                    JS_FreeValue(ctx, call_argv[i]);
+                sp -= call_argc + 1;
+                *sp++ = ret_val;
+            }
+            BREAK;
+            /* could merge with OP_apply */
+        CASE(OP_apply_eval):
+            {
+                int scope_idx;
+                uint32_t len;
+                JSValue *tab;
+                JSValueConst obj;
+
+                scope_idx = get_u16(pc) - 1;
+                pc += 2;
+                tab = build_arg_list(ctx, &len, sp[-1]);
+                if (!tab)
+                    goto exception;
+                if (js_same_value(ctx, sp[-2], ctx->eval_obj)) {
+                    if (len >= 1)
+                        obj = tab[0];
+                    else
+                        obj = JS_UNDEFINED;
+                    ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj,
+                                            JS_EVAL_TYPE_DIRECT, scope_idx);
+                } else {
+                    ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len,
+                                      (JSValueConst *)tab);
+                }
+                free_arg_list(ctx, tab, len);
+                if (unlikely(JS_IsException(ret_val)))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-2]);
+                JS_FreeValue(ctx, sp[-1]);
+                sp -= 2;
+                *sp++ = ret_val;
+            }
+            BREAK;
+
+        CASE(OP_regexp):
+            {
+                sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED,
+                                                        sp[-2], sp[-1]);
+                sp--;
+            }
+            BREAK;
+
+        CASE(OP_get_super_ctor):
+            {
+                JSValue proto;
+                proto = JS_DupValue(ctx, JS_GetPrototype(ctx, sp[-1]));
+                if (JS_IsException(proto))
+                    goto exception;
+                if (!JS_IsConstructor(ctx, proto)) {
+                    JS_FreeValue(ctx, proto);
+                    JS_ThrowTypeError(ctx, "not a constructor");
+                    goto exception;
+                }
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = proto;
+            }
+            BREAK;
+
+        CASE(OP_get_super):
+            {
+                JSValue proto;
+                proto = JS_DupValue(ctx, JS_GetPrototype(ctx, sp[-1]));
+                if (JS_IsException(proto))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = proto;
+            }
+            BREAK;
+
+        CASE(OP_import):
+            {
+                JSValue val;
+                val = js_dynamic_import(ctx, sp[-1]);
+                if (JS_IsException(val))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = val;
+            }
+            BREAK;
+
+        CASE(OP_check_var):
+            {
+                int ret;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                ret = JS_CheckGlobalVar(ctx, atom);
+                if (ret < 0)
+                    goto exception;
+                *sp++ = JS_NewBool(ctx, ret);
+            }
+            BREAK;
+
+        CASE(OP_get_var_undef):
+        CASE(OP_get_var):
+            {
+                JSValue val;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef);
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+                *sp++ = val;
+            }
+            BREAK;
+
+        CASE(OP_put_var):
+        CASE(OP_put_var_init):
+            {
+                int ret;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var);
+                sp--;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_put_var_strict):
+            {
+                int ret;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                /* sp[-2] is JS_TRUE or JS_FALSE */
+                if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) {
+                    JS_ThrowReferenceErrorNotDefined(ctx, atom);
+                    goto exception;
+                }
+                ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2);
+                sp -= 2;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_check_define_var):
+            {
+                JSAtom atom;
+                int flags;
+                atom = get_u32(pc);
+                flags = pc[4];
+                pc += 5;
+                if (JS_CheckDefineGlobalVar(ctx, atom, flags))
+                    goto exception;
+            }
+            BREAK;
+        CASE(OP_define_var):
+            {
+                JSAtom atom;
+                int flags;
+                atom = get_u32(pc);
+                flags = pc[4];
+                pc += 5;
+                if (JS_DefineGlobalVar(ctx, atom, flags))
+                    goto exception;
+            }
+            BREAK;
+        CASE(OP_define_func):
+            {
+                JSAtom atom;
+                int flags;
+                atom = get_u32(pc);
+                flags = pc[4];
+                pc += 5;
+                if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp--;
+            }
+            BREAK;
+
+        CASE(OP_get_loc):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                sp[0] = JS_DupValue(ctx, var_buf[idx]);
+                sp++;
+            }
+            BREAK;
+        CASE(OP_put_loc):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, &var_buf[idx], sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_set_loc):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, &var_buf[idx], JS_DupValue(ctx, sp[-1]));
+            }
+            BREAK;
+        CASE(OP_get_arg):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                sp[0] = JS_DupValue(ctx, arg_buf[idx]);
+                sp++;
+            }
+            BREAK;
+        CASE(OP_put_arg):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, &arg_buf[idx], sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_set_arg):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, &arg_buf[idx], JS_DupValue(ctx, sp[-1]));
+            }
+            BREAK;
+
+#if SHORT_OPCODES
+        CASE(OP_get_loc8): *sp++ = JS_DupValue(ctx, var_buf[*pc++]); BREAK;
+        CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK;
+        CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], JS_DupValue(ctx, sp[-1])); BREAK;
+
+        CASE(OP_get_loc0): *sp++ = JS_DupValue(ctx, var_buf[0]); BREAK;
+        CASE(OP_get_loc1): *sp++ = JS_DupValue(ctx, var_buf[1]); BREAK;
+        CASE(OP_get_loc2): *sp++ = JS_DupValue(ctx, var_buf[2]); BREAK;
+        CASE(OP_get_loc3): *sp++ = JS_DupValue(ctx, var_buf[3]); BREAK;
+        CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK;
+        CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK;
+        CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK;
+        CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK;
+        CASE(OP_set_loc0): set_value(ctx, &var_buf[0], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_loc1): set_value(ctx, &var_buf[1], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_loc2): set_value(ctx, &var_buf[2], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_loc3): set_value(ctx, &var_buf[3], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_get_arg0): *sp++ = JS_DupValue(ctx, arg_buf[0]); BREAK;
+        CASE(OP_get_arg1): *sp++ = JS_DupValue(ctx, arg_buf[1]); BREAK;
+        CASE(OP_get_arg2): *sp++ = JS_DupValue(ctx, arg_buf[2]); BREAK;
+        CASE(OP_get_arg3): *sp++ = JS_DupValue(ctx, arg_buf[3]); BREAK;
+        CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK;
+        CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK;
+        CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK;
+        CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK;
+        CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_get_var_ref0): *sp++ = JS_DupValue(ctx, *var_refs[0]->pvalue); BREAK;
+        CASE(OP_get_var_ref1): *sp++ = JS_DupValue(ctx, *var_refs[1]->pvalue); BREAK;
+        CASE(OP_get_var_ref2): *sp++ = JS_DupValue(ctx, *var_refs[2]->pvalue); BREAK;
+        CASE(OP_get_var_ref3): *sp++ = JS_DupValue(ctx, *var_refs[3]->pvalue); BREAK;
+        CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK;
+        CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK;
+        CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK;
+        CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK;
+        CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK;
+        CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK;
+#endif
+
+        CASE(OP_get_var_ref):
+            {
+                int idx;
+                JSValue val;
+                idx = get_u16(pc);
+                pc += 2;
+                val = *var_refs[idx]->pvalue;
+                sp[0] = JS_DupValue(ctx, val);
+                sp++;
+            }
+            BREAK;
+        CASE(OP_put_var_ref):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, var_refs[idx]->pvalue, sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_set_var_ref):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, var_refs[idx]->pvalue, JS_DupValue(ctx, sp[-1]));
+            }
+            BREAK;
+        CASE(OP_get_var_ref_check):
+            {
+                int idx;
+                JSValue val;
+                idx = get_u16(pc);
+                pc += 2;
+                val = *var_refs[idx]->pvalue;
+                if (unlikely(JS_IsUninitialized(val))) {
+                    JS_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL);
+                    goto exception;
+                }
+                sp[0] = JS_DupValue(ctx, val);
+                sp++;
+            }
+            BREAK;
+        CASE(OP_put_var_ref_check):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) {
+                    JS_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL);
+                    goto exception;
+                }
+                set_value(ctx, var_refs[idx]->pvalue, sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_put_var_ref_check_init):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) {
+                    JS_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL);
+                    goto exception;
+                }
+                set_value(ctx, var_refs[idx]->pvalue, sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_set_loc_uninitialized):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                set_value(ctx, &var_buf[idx], JS_UNINITIALIZED);
+            }
+            BREAK;
+        CASE(OP_get_loc_check):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                if (unlikely(JS_IsUninitialized(var_buf[idx]))) {
+                    JS_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL);
+                    goto exception;
+                }
+                sp[0] = JS_DupValue(ctx, var_buf[idx]);
+                sp++;
+            }
+            BREAK;
+        CASE(OP_put_loc_check):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                if (unlikely(JS_IsUninitialized(var_buf[idx]))) {
+                    JS_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL);
+                    goto exception;
+                }
+                set_value(ctx, &var_buf[idx], sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_put_loc_check_init):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                if (unlikely(!JS_IsUninitialized(var_buf[idx]))) {
+                    JS_ThrowReferenceError(ctx, "'this' can be initialized only once");
+                    goto exception;
+                }
+                set_value(ctx, &var_buf[idx], sp[-1]);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_close_loc):
+            {
+                int idx;
+                idx = get_u16(pc);
+                pc += 2;
+                close_lexical_var(ctx, sf, idx, FALSE);
+            }
+            BREAK;
+
+        CASE(OP_make_loc_ref):
+        CASE(OP_make_arg_ref):
+        CASE(OP_make_var_ref_ref):
+            {
+                JSVarRef *var_ref;
+                JSProperty *pr;
+                JSAtom atom;
+                int idx;
+                atom = get_u32(pc);
+                idx = get_u16(pc + 4);
+                pc += 6;
+                *sp++ = JS_NewObjectProto(ctx, JS_NULL);
+                if (unlikely(JS_IsException(sp[-1])))
+                    goto exception;
+                if (opcode == OP_make_var_ref_ref) {
+                    var_ref = var_refs[idx];
+                    var_ref->header.ref_count++;
+                } else {
+                    var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref);
+                    if (!var_ref)
+                        goto exception;
+                }
+                pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom,
+                                  JS_PROP_WRITABLE | JS_PROP_VARREF);
+                if (!pr) {
+                    free_var_ref(ctx->rt, var_ref);
+                    goto exception;
+                }
+                pr->u.var_ref = var_ref;
+                *sp++ = JS_AtomToValue(ctx, atom);
+            }
+            BREAK;
+        CASE(OP_make_var_ref):
+            {
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                if (JS_GetGlobalVarRef(ctx, atom, sp))
+                    goto exception;
+                sp += 2;
+            }
+            BREAK;
+
+        CASE(OP_goto):
+            pc += (int32_t)get_u32(pc);
+            if (unlikely(js_poll_interrupts(ctx)))
+                goto exception;
+            BREAK;
+#if SHORT_OPCODES
+        CASE(OP_goto16):
+            pc += (int16_t)get_u16(pc);
+            if (unlikely(js_poll_interrupts(ctx)))
+                goto exception;
+            BREAK;
+        CASE(OP_goto8):
+            pc += (int8_t)pc[0];
+            if (unlikely(js_poll_interrupts(ctx)))
+                goto exception;
+            BREAK;
+#endif
+        CASE(OP_if_true):
+            {
+                int res;
+                JSValue op1;
+
+                op1 = sp[-1];
+                pc += 4;
+                if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
+                    res = JS_VALUE_GET_INT(op1);
+                } else {
+                    res = JS_ToBoolFree(ctx, op1);
+                }
+                sp--;
+                if (res) {
+                    pc += (int32_t)get_u32(pc - 4) - 4;
+                }
+                if (unlikely(js_poll_interrupts(ctx)))
+                    goto exception;
+            }
+            BREAK;
+        CASE(OP_if_false):
+            {
+                int res;
+                JSValue op1;
+
+                op1 = sp[-1];
+                pc += 4;
+                if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
+                    res = JS_VALUE_GET_INT(op1);
+                } else {
+                    res = JS_ToBoolFree(ctx, op1);
+                }
+                sp--;
+                if (!res) {
+                    pc += (int32_t)get_u32(pc - 4) - 4;
+                }
+                if (unlikely(js_poll_interrupts(ctx)))
+                    goto exception;
+            }
+            BREAK;
+#if SHORT_OPCODES
+        CASE(OP_if_true8):
+            {
+                int res;
+                JSValue op1;
+
+                op1 = sp[-1];
+                pc += 1;
+                if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
+                    res = JS_VALUE_GET_INT(op1);
+                } else {
+                    res = JS_ToBoolFree(ctx, op1);
+                }
+                sp--;
+                if (res) {
+                    pc += (int8_t)pc[-1] - 1;
+                }
+                if (unlikely(js_poll_interrupts(ctx)))
+                    goto exception;
+            }
+            BREAK;
+        CASE(OP_if_false8):
+            {
+                int res;
+                JSValue op1;
+
+                op1 = sp[-1];
+                pc += 1;
+                if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
+                    res = JS_VALUE_GET_INT(op1);
+                } else {
+                    res = JS_ToBoolFree(ctx, op1);
+                }
+                sp--;
+                if (!res) {
+                    pc += (int8_t)pc[-1] - 1;
+                }
+                if (unlikely(js_poll_interrupts(ctx)))
+                    goto exception;
+            }
+            BREAK;
+#endif
+        CASE(OP_catch):
+            {
+                int32_t diff;
+                diff = get_u32(pc);
+                sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf);
+                sp++;
+                pc += 4;
+            }
+            BREAK;
+        CASE(OP_gosub):
+            {
+                int32_t diff;
+                diff = get_u32(pc);
+                /* XXX: should have a different tag to avoid security flaw */
+                sp[0] = JS_NewInt32(ctx, pc + 4 - b->byte_code_buf);
+                sp++;
+                pc += diff;
+            }
+            BREAK;
+        CASE(OP_ret):
+            {
+                JSValue op1;
+                uint32_t pos;
+                op1 = sp[-1];
+                if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT))
+                    goto ret_fail;
+                pos = JS_VALUE_GET_INT(op1);
+                if (unlikely(pos >= b->byte_code_len)) {
+                ret_fail:
+                    JS_ThrowInternalError(ctx, "invalid ret value");
+                    goto exception;
+                }
+                sp--;
+                pc = b->byte_code_buf + pos;
+            }
+            BREAK;
+
+        CASE(OP_for_in_start):
+            if (js_for_in_start(ctx, sp))
+                goto exception;
+            BREAK;
+        CASE(OP_for_in_next):
+            if (js_for_in_next(ctx, sp))
+                goto exception;
+            sp += 2;
+            BREAK;
+        CASE(OP_for_of_start):
+            if (js_for_of_start(ctx, sp, FALSE))
+                goto exception;
+            sp += 1;
+            *sp++ = JS_NewCatchOffset(ctx, 0);
+            BREAK;
+        CASE(OP_for_of_next):
+            {
+                int offset = -3 - pc[0];
+                pc += 1;
+                if (js_for_of_next(ctx, sp, offset))
+                    goto exception;
+                sp += 2;
+            }
+            BREAK;
+        CASE(OP_for_await_of_start):
+            if (js_for_of_start(ctx, sp, TRUE))
+                goto exception;
+            sp += 1;
+            *sp++ = JS_NewCatchOffset(ctx, 0);
+            BREAK;
+        CASE(OP_for_await_of_next):
+            if (js_for_await_of_next(ctx, sp))
+                goto exception;
+            sp += 1;
+            BREAK;
+        CASE(OP_iterator_get_value_done):
+            if (js_iterator_get_value_done(ctx, sp))
+                goto exception;
+            sp += 1;
+            BREAK;
+
+        CASE(OP_iterator_close):
+            sp--; /* drop the catch offset to avoid getting caught by exception */
+            JS_FreeValue(ctx, sp[-1]); /* drop the next method */
+            sp--;
+            if (!JS_IsUndefined(sp[-1])) {
+                if (JS_IteratorClose(ctx, sp[-1], FALSE))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+            }
+            sp--;
+            BREAK;
+        CASE(OP_iterator_close_return):
+            {
+                JSValue ret_val;
+                /* iter_obj next catch_offset ... ret_val ->
+                   ret_eval iter_obj next catch_offset */
+                ret_val = *--sp;
+                while (sp > stack_buf &&
+                       JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) {
+                    JS_FreeValue(ctx, *--sp);
+                }
+                if (unlikely(sp < stack_buf + 3)) {
+                    JS_ThrowInternalError(ctx, "iterator_close_return");
+                    JS_FreeValue(ctx, ret_val);
+                    goto exception;
+                }
+                sp[0] = sp[-1];
+                sp[-1] = sp[-2];
+                sp[-2] = sp[-3];
+                sp[-3] = ret_val;
+                sp++;
+            }
+            BREAK;
+
+        CASE(OP_async_iterator_close):
+            /* iter_obj next catch_offset -> value flag */
+            {
+                JSValue ret, method;
+                int ret_flag;
+                method = JS_GetProperty(ctx, sp[-3], JS_ATOM_return);
+                if (JS_IsException(method))
+                    goto exception;
+                if (JS_IsUndefined(method) || JS_IsNull(method)) {
+                    ret = JS_UNDEFINED;
+                    ret_flag = TRUE;
+                } else {
+                    ret = JS_CallFree(ctx, method, sp[-3], 0, NULL);
+                    if (JS_IsException(ret))
+                        goto exception;
+                    ret_flag = FALSE;
+                }
+                JS_FreeValue(ctx, sp[-3]);
+                JS_FreeValue(ctx, sp[-2]);
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-3] = ret;
+                sp[-2] = JS_NewBool(ctx, ret_flag);
+                sp -= 1;
+            }
+            BREAK;
+
+        CASE(OP_async_iterator_next):
+            /* stack: iter_obj next catch_offset val */
+            {
+                JSValue ret;
+                ret = JS_Call(ctx, sp[-3], sp[-4],
+                              1, (JSValueConst *)(sp - 1));
+                if (JS_IsException(ret))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = ret;
+            }
+            BREAK;
+
+        CASE(OP_async_iterator_get):
+            /* stack: iter_obj next catch_offset val */
+            {
+                JSValue method, ret;
+                BOOL ret_flag;
+                int flags;
+                flags = *pc++;
+                if (flags == 2) {
+                    JS_ThrowTypeError(ctx, "iterator does not have a throw method");
+                    goto exception;
+                }
+                method = JS_GetProperty(ctx, sp[-4], flags ? JS_ATOM_throw : JS_ATOM_return);
+                if (JS_IsException(method))
+                    goto exception;
+                if (JS_IsUndefined(method) || JS_IsNull(method)) {
+                    ret_flag = TRUE;
+                } else {
+                    ret = JS_CallFree(ctx, method, sp[-4],
+                                      1, (JSValueConst *)(sp - 1));
+                    if (JS_IsException(ret))
+                        goto exception;
+                    JS_FreeValue(ctx, sp[-1]);
+                    sp[-1] = ret;
+                    ret_flag = FALSE;
+                }
+                sp[0] = JS_NewBool(ctx, ret_flag);
+                sp += 1;
+            }
+            BREAK;
+
+        CASE(OP_lnot):
+            {
+                int res;
+                JSValue op1;
+
+                op1 = sp[-1];
+                if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
+                    res = JS_VALUE_GET_INT(op1) != 0;
+                } else {
+                    res = JS_ToBoolFree(ctx, op1);
+                }
+                sp[-1] = JS_NewBool(ctx, !res);
+            }
+            BREAK;
+
+        CASE(OP_get_field):
+            {
+                JSValue val;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                val = JS_GetProperty(ctx, sp[-1], atom);
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = val;
+            }
+            BREAK;
+
+        CASE(OP_get_field2):
+            {
+                JSValue val;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                val = JS_GetProperty(ctx, sp[-1], atom);
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+                *sp++ = val;
+            }
+            BREAK;
+
+        CASE(OP_put_field):
+            {
+                int ret;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                ret = JS_SetPropertyInternal(ctx, sp[-2], atom, sp[-1],
+                                             JS_PROP_THROW_STRICT);
+                JS_FreeValue(ctx, sp[-2]);
+                sp -= 2;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_private_symbol):
+            {
+                JSAtom atom;
+                JSValue val;
+                
+                atom = get_u32(pc);
+                pc += 4;
+                val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE);
+                if (JS_IsException(val))
+                    goto exception;
+                *sp++ = val;
+            }
+            BREAK;
+            
+        CASE(OP_get_private_field):
+            {
+                JSValue val;
+
+                val = JS_GetPrivateField(ctx, sp[-2], sp[-1]);
+                JS_FreeValue(ctx, sp[-1]);
+                JS_FreeValue(ctx, sp[-2]);
+                sp[-2] = val;
+                sp--;
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_put_private_field):
+            {
+                int ret;
+                ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]);
+                JS_FreeValue(ctx, sp[-3]);
+                JS_FreeValue(ctx, sp[-1]);
+                sp -= 3;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_define_private_field):
+            {
+                int ret;
+                ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]);
+                JS_FreeValue(ctx, sp[-2]);
+                sp -= 2;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_define_field):
+            {
+                int ret;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1],
+                                             JS_PROP_C_W_E | JS_PROP_THROW);
+                sp--;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_set_name):
+            {
+                int ret;
+                JSAtom atom;
+                atom = get_u32(pc);
+                pc += 4;
+
+                ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE);
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+        CASE(OP_set_name_computed):
+            {
+                int ret;
+                ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE);
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+        CASE(OP_set_proto):
+            {
+                JSValue proto;
+                proto = sp[-1];
+                if (JS_IsObject(proto) || JS_IsNull(proto)) {
+                    if (JS_SetPrototypeInternal(ctx, sp[-2], proto, TRUE) < 0)
+                        goto exception;
+                }
+                JS_FreeValue(ctx, proto);
+                sp--;
+            }
+            BREAK;
+        CASE(OP_set_home_object):
+            js_method_set_home_object(ctx, sp[-1], sp[-2]);
+            BREAK;
+        CASE(OP_define_method):
+        CASE(OP_define_method_computed):
+            {
+                JSValue getter, setter, value;
+                JSValueConst obj;
+                JSAtom atom;
+                int flags, ret, op_flags;
+                BOOL is_computed;
+#define OP_DEFINE_METHOD_METHOD 0
+#define OP_DEFINE_METHOD_GETTER 1
+#define OP_DEFINE_METHOD_SETTER 2
+#define OP_DEFINE_METHOD_ENUMERABLE 4
+
+                is_computed = (opcode == OP_define_method_computed);
+                if (is_computed) {
+                    atom = JS_ValueToAtom(ctx, sp[-2]);
+                    if (unlikely(atom == JS_ATOM_NULL))
+                        goto exception;
+                    opcode += OP_define_method - OP_define_method_computed;
+                } else {
+                    atom = get_u32(pc);
+                    pc += 4;
+                }
+                op_flags = *pc++;
+
+                obj = sp[-2 - is_computed];
+                flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE |
+                    JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW;
+                if (op_flags & OP_DEFINE_METHOD_ENUMERABLE)
+                    flags |= JS_PROP_ENUMERABLE;
+                op_flags &= 3;
+                value = JS_UNDEFINED;
+                getter = JS_UNDEFINED;
+                setter = JS_UNDEFINED;
+                if (op_flags == OP_DEFINE_METHOD_METHOD) {
+                    value = sp[-1];
+                    flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE;
+                } else if (op_flags == OP_DEFINE_METHOD_GETTER) {
+                    getter = sp[-1];
+                    flags |= JS_PROP_HAS_GET;
+                } else {
+                    setter = sp[-1];
+                    flags |= JS_PROP_HAS_SET;
+                }
+                ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj);
+                if (ret >= 0) {
+                    ret = JS_DefineProperty(ctx, obj, atom, value,
+                                            getter, setter, flags);
+                }
+                JS_FreeValue(ctx, sp[-1]);
+                if (is_computed) {
+                    JS_FreeAtom(ctx, atom);
+                    JS_FreeValue(ctx, sp[-2]);
+                }
+                sp -= 1 + is_computed;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_define_class):
+        CASE(OP_define_class_computed):
+            {
+                int class_flags;
+                JSAtom atom;
+                
+                atom = get_u32(pc);
+                class_flags = pc[4];
+                pc += 5;
+                if (js_op_define_class(ctx, sp, atom, class_flags,
+                                       var_refs, sf,
+                                       (opcode == OP_define_class_computed)) < 0)
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_get_array_el):
+            {
+                JSValue val;
+
+                val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]);
+                JS_FreeValue(ctx, sp[-2]);
+                sp[-2] = val;
+                sp--;
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_get_array_el2):
+            {
+                JSValue val;
+
+                val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]);
+                sp[-1] = val;
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_get_ref_value):
+            {
+                JSValue val;
+                if (unlikely(JS_IsUndefined(sp[-2]))) {
+                    JSAtom atom = JS_ValueToAtom(ctx, sp[-1]);
+                    if (atom != JS_ATOM_NULL) {
+                        JS_ThrowReferenceErrorNotDefined(ctx, atom);
+                        JS_FreeAtom(ctx, atom);
+                    }
+                    goto exception;
+                }
+                val = JS_GetPropertyValue(ctx, sp[-2],
+                                          JS_DupValue(ctx, sp[-1]));
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+                sp[0] = val;
+                sp++;
+            }
+            BREAK;
+
+        CASE(OP_get_super_value):
+            {
+                JSValue val;
+                JSAtom atom;
+                atom = JS_ValueToAtom(ctx, sp[-1]);
+                if (unlikely(atom == JS_ATOM_NULL))
+                    goto exception;
+                val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], FALSE);
+                JS_FreeAtom(ctx, atom);
+                if (unlikely(JS_IsException(val)))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                JS_FreeValue(ctx, sp[-2]);
+                JS_FreeValue(ctx, sp[-3]);
+                sp[-3] = val;
+                sp -= 2;
+            }
+            BREAK;
+
+        CASE(OP_put_array_el):
+            {
+                int ret;
+
+                ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT);
+                JS_FreeValue(ctx, sp[-3]);
+                sp -= 3;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_put_ref_value):
+            {
+                int ret;
+                if (unlikely(JS_IsUndefined(sp[-3]))) {
+                    if (is_strict_mode(ctx)) {
+                        JSAtom atom = JS_ValueToAtom(ctx, sp[-2]);
+                        if (atom != JS_ATOM_NULL) {
+                            JS_ThrowReferenceErrorNotDefined(ctx, atom);
+                            JS_FreeAtom(ctx, atom);
+                        }
+                        goto exception;
+                    } else {
+                        sp[-3] = JS_DupValue(ctx, ctx->global_obj);
+                    }
+                }
+                ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT);
+                JS_FreeValue(ctx, sp[-3]);
+                sp -= 3;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_put_super_value):
+            {
+                int ret;
+                JSAtom atom;
+                if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) {
+                    JS_ThrowTypeErrorNotAnObject(ctx);
+                    goto exception;
+                }
+                atom = JS_ValueToAtom(ctx, sp[-2]);
+                if (unlikely(atom == JS_ATOM_NULL))
+                    goto exception;
+                ret = JS_SetPropertyGeneric(ctx, JS_VALUE_GET_OBJ(sp[-3]),
+                                            atom, sp[-1], sp[-4],
+                                            JS_PROP_THROW_STRICT);
+                JS_FreeAtom(ctx, atom);
+                JS_FreeValue(ctx, sp[-4]);
+                JS_FreeValue(ctx, sp[-3]);
+                JS_FreeValue(ctx, sp[-2]);
+                sp -= 4;
+                if (ret < 0)
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_define_array_el):
+            {
+                int ret;
+                ret = JS_DefinePropertyValueValue(ctx, sp[-3], JS_DupValue(ctx, sp[-2]), sp[-1],
+                                                  JS_PROP_C_W_E | JS_PROP_THROW);
+                sp -= 1;
+                if (unlikely(ret < 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_append):    /* array pos enumobj -- array pos */
+            {
+                if (js_append_enumerate(ctx, sp))
+                    goto exception;
+                JS_FreeValue(ctx, *--sp);
+            }
+            BREAK;
+
+        CASE(OP_copy_data_properties):    /* target source excludeList */
+            {
+                /* stack offsets (-1 based):
+                   2 bits for target,
+                   3 bits for source,
+                   2 bits for exclusionList */
+                int mask;
+
+                mask = *pc++;
+                if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)],
+                                          sp[-1 - ((mask >> 2) & 7)],
+                                          sp[-1 - ((mask >> 5) & 7)], 0))
+                    goto exception;
+            }
+            BREAK;
+
+        CASE(OP_add):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    int64_t r;
+                    r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2);
+                    if (unlikely((int)r != r))
+                        goto add_slow;
+                    sp[-2] = JS_NewInt32(ctx, r);
+                    sp--;
+                } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
+                    sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) +
+                                             JS_VALUE_GET_FLOAT64(op2));
+                    sp--;
+                } else {
+                add_slow:
+                    if (js_add_slow(ctx, sp))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+        CASE(OP_add_loc):
+            {
+                JSValue ops[2];
+                int idx;
+                idx = *pc;
+                pc += 1;
+
+                ops[0] = var_buf[idx];
+                ops[1] = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(ops[0], ops[1]))) {
+                    int64_t r;
+                    r = (int64_t)JS_VALUE_GET_INT(ops[0]) + JS_VALUE_GET_INT(ops[1]);
+                    if (unlikely((int)r != r))
+                        goto add_loc_slow;
+                    var_buf[idx] = JS_NewInt32(ctx, r);
+                    sp--;
+                } else if (JS_VALUE_GET_TAG(ops[0]) == JS_TAG_STRING) {
+                    sp--;
+                    ops[1] = JS_ToPrimitiveFree(ctx, ops[1], HINT_NONE);
+                    if (JS_IsException(ops[1])) {
+                        goto exception;
+                    }
+                    /* XXX: should not modify the variable in case of
+                       exception */
+                    ops[0] = JS_ConcatString(ctx, ops[0], ops[1]);
+                    if (JS_IsException(ops[0])) {
+                        var_buf[idx] = JS_UNDEFINED;
+                        goto exception;
+                    }
+                    var_buf[idx] = ops[0];
+                } else {
+                add_loc_slow:
+                    /* XXX: should not modify the variable in case of
+                       exception */
+                    sp--;
+                    /* In case of exception, js_add_slow frees ops[0]
+                       and ops[1]. */
+                    /* XXX: change API */
+                    if (js_add_slow(ctx, ops + 2)) {
+                        var_buf[idx] = JS_UNDEFINED;
+                        goto exception;
+                    }
+                    var_buf[idx] = ops[0];
+                }
+            }
+            BREAK;
+        CASE(OP_sub):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    int64_t r;
+                    r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2);
+                    if (unlikely((int)r != r))
+                        goto binary_arith_slow;
+                    sp[-2] = JS_NewInt32(ctx, r);
+                    sp--;
+                } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
+                    sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) -
+                                             JS_VALUE_GET_FLOAT64(op2));
+                    sp--;
+                } else {
+                    goto binary_arith_slow;
+                }
+            }
+            BREAK;
+        CASE(OP_mul):
+            {
+                JSValue op1, op2;
+                double d;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    int32_t v1, v2;
+                    int64_t r;
+                    v1 = JS_VALUE_GET_INT(op1);
+                    v2 = JS_VALUE_GET_INT(op2);
+                    r = (int64_t)v1 * v2;
+#ifdef CONFIG_BIGNUM
+                    if (unlikely((int)r != r) || (r == 0 && !is_bignum_mode(ctx)))
+                        goto binary_arith_slow;
+#else
+                    if (unlikely((int)r != r)) {
+                        d = (double)r;
+                        goto mul_fp_res;
+                    }
+                    /* need to test zero case for -0 result */
+                    if (unlikely(r == 0 && (v1 | v2) < 0)) {
+                        d = -0.0;
+                        goto mul_fp_res;
+                    }
+#endif
+                    sp[-2] = JS_NewInt32(ctx, r);
+                    sp--;
+                } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
+                    d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2);
+#ifndef CONFIG_BIGNUM
+                mul_fp_res:
+#endif
+                    sp[-2] = __JS_NewFloat64(ctx, d);
+                    sp--;
+                } else {
+                    goto binary_arith_slow;
+                }
+            }
+            BREAK;
+#ifdef CONFIG_BIGNUM
+        CASE(OP_math_div):
+        CASE(OP_div):
+            goto binary_arith_slow;
+#else
+        CASE(OP_div):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    int v1, v2;
+                    v1 = JS_VALUE_GET_INT(op1);
+                    v2 = JS_VALUE_GET_INT(op2);
+                    sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2);
+                    sp--;
+                } else {
+                    goto binary_arith_slow;
+                }
+            }
+            BREAK;
+#endif
+        CASE(OP_mod):
+#ifdef CONFIG_BIGNUM
+        CASE(OP_math_mod):
+#endif
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    int v1, v2, r;
+                    v1 = JS_VALUE_GET_INT(op1);
+                    v2 = JS_VALUE_GET_INT(op2);
+                    /* We must avoid v2 = 0, v1 = INT32_MIN and v2 =
+                       -1 and the cases where the result is -0. */
+                    if (unlikely(v1 < 0 || v2 <= 0))
+                        goto binary_arith_slow;
+                    r = v1 % v2;
+                    sp[-2] = JS_NewInt32(ctx, r);
+                    sp--;
+                } else {
+                    goto binary_arith_slow;
+                }
+            }
+            BREAK;
+        CASE(OP_pow):
+#ifdef CONFIG_BIGNUM
+        CASE(OP_math_pow):
+#endif
+        binary_arith_slow:
+            if (js_binary_arith_slow(ctx, sp, opcode))
+                goto exception;
+            sp--;
+            BREAK;
+
+        CASE(OP_plus):
+            {
+                JSValue op1;
+                uint32_t tag;
+                op1 = sp[-1];
+                tag = JS_VALUE_GET_TAG(op1);
+                if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) {
+                } else {
+                    if (js_unary_arith_slow(ctx, sp, opcode))
+                        goto exception;
+                }
+            }
+            BREAK;
+        CASE(OP_neg):
+            {
+                JSValue op1;
+                uint32_t tag;
+                int val;
+                double d;
+                op1 = sp[-1];
+                tag = JS_VALUE_GET_TAG(op1);
+                if (tag == JS_TAG_INT) {
+                    val = JS_VALUE_GET_INT(op1);
+#ifdef CONFIG_BIGNUM
+                    if (unlikely(val == INT32_MIN) ||
+                        (val == 0 && !is_bignum_mode(ctx)))
+                        goto slow_neg;
+#else
+                    /* Note: -0 cannot be expressed as integer */
+                    if (unlikely(val == 0)) {
+                        d = -0.0;
+                        goto neg_fp_res;
+                    }
+                    if (unlikely(val == INT32_MIN)) {
+                        d = -(double)val;
+                        goto neg_fp_res;
+                    }
+#endif
+                    sp[-1] = JS_NewInt32(ctx, -val);
+                } else if (JS_TAG_IS_FLOAT64(tag)) {
+                    d = -JS_VALUE_GET_FLOAT64(op1);
+#ifndef CONFIG_BIGNUM
+                neg_fp_res:
+#endif
+                    sp[-1] = __JS_NewFloat64(ctx, d);
+                } else {
+#ifdef CONFIG_BIGNUM
+                slow_neg:
+#endif
+                    if (js_unary_arith_slow(ctx, sp, opcode))
+                        goto exception;
+                }
+            }
+            BREAK;
+        CASE(OP_inc):
+            {
+                JSValue op1;
+                int val;
+                op1 = sp[-1];
+                if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
+                    val = JS_VALUE_GET_INT(op1);
+                    if (unlikely(val == INT32_MAX))
+                        goto inc_slow;
+                    sp[-1] = JS_NewInt32(ctx, val + 1);
+                } else {
+                inc_slow:
+                    if (js_unary_arith_slow(ctx, sp, opcode))
+                        goto exception;
+                }
+            }
+            BREAK;
+        CASE(OP_dec):
+            {
+                JSValue op1;
+                int val;
+                op1 = sp[-1];
+                if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
+                    val = JS_VALUE_GET_INT(op1);
+                    if (unlikely(val == INT32_MIN))
+                        goto dec_slow;
+                    sp[-1] = JS_NewInt32(ctx, val - 1);
+                } else {
+                dec_slow:
+                    if (js_unary_arith_slow(ctx, sp, opcode))
+                        goto exception;
+                }
+            }
+            BREAK;
+        CASE(OP_post_inc):
+        CASE(OP_post_dec):
+            if (js_post_inc_slow(ctx, sp, opcode))
+                goto exception;
+            sp++;
+            BREAK;
+        CASE(OP_inc_loc):
+            {
+                JSValue op1;
+                int val;
+                int idx;
+                idx = *pc;
+                pc += 1;
+
+                op1 = var_buf[idx];
+                if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
+                    val = JS_VALUE_GET_INT(op1);
+                    if (unlikely(val == INT32_MAX))
+                        goto inc_loc_slow;
+                    var_buf[idx] = JS_NewInt32(ctx, val + 1);
+                } else {
+                inc_loc_slow:
+                    if (js_unary_arith_slow(ctx, var_buf + idx + 1, OP_inc))
+                        goto exception;
+                }
+            }
+            BREAK;
+        CASE(OP_dec_loc):
+            {
+                JSValue op1;
+                int val;
+                int idx;
+                idx = *pc;
+                pc += 1;
+
+                op1 = var_buf[idx];
+                if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
+                    val = JS_VALUE_GET_INT(op1);
+                    if (unlikely(val == INT32_MIN))
+                        goto dec_loc_slow;
+                    var_buf[idx] = JS_NewInt32(ctx, val - 1);
+                } else {
+                dec_loc_slow:
+                    if (js_unary_arith_slow(ctx, var_buf + idx + 1, OP_dec))
+                        goto exception;
+                }
+            }
+            BREAK;
+        CASE(OP_not):
+            {
+                JSValue op1;
+                op1 = sp[-1];
+                if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
+                    sp[-1] = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1));
+                } else {
+                    if (js_not_slow(ctx, sp))
+                        goto exception;
+                }
+            }
+            BREAK;
+
+        CASE(OP_shl):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    uint32_t v1, v2;
+                    v1 = JS_VALUE_GET_INT(op1);
+                    v2 = JS_VALUE_GET_INT(op2);
+#ifdef CONFIG_BIGNUM
+                    {
+                        int64_t r;
+                        if (is_bignum_mode(ctx)) {
+                            if (v2 > 0x1f)
+                                goto shl_slow;
+                            r = (int64_t)v1 << v2;
+                            if ((int)r != r)
+                                goto shl_slow;
+                        } else {
+                            v2 &= 0x1f;
+                        }
+                    }
+#else
+                    v2 &= 0x1f;
+#endif
+                    sp[-2] = JS_NewInt32(ctx, v1 << v2);
+                    sp--;
+                } else {
+#ifdef CONFIG_BIGNUM
+                shl_slow:
+#endif
+                    if (js_binary_logic_slow(ctx, sp, opcode))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+        CASE(OP_shr):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    uint32_t v2;
+                    v2 = JS_VALUE_GET_INT(op2);
+                    /* v1 >>> v2 retains its JS semantics if CONFIG_BIGNUM */
+                    v2 &= 0x1f;
+                    sp[-2] = JS_NewUint32(ctx,
+                                          (uint32_t)JS_VALUE_GET_INT(op1) >>
+                                          v2);
+                    sp--;
+                } else {
+                    if (js_shr_slow(ctx, sp))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+        CASE(OP_sar):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    uint32_t v2;
+                    v2 = JS_VALUE_GET_INT(op2);
+#ifdef CONFIG_BIGNUM
+                    if (unlikely(v2 > 0x1f)) {
+                        if (is_bignum_mode(ctx))
+                            goto sar_slow;
+                        else
+                            v2 &= 0x1f;
+                    }
+#else
+                    v2 &= 0x1f;
+#endif
+                    sp[-2] = JS_NewInt32(ctx,
+                                          (int)JS_VALUE_GET_INT(op1) >> v2);
+                    sp--;
+                } else {
+#ifdef CONFIG_BIGNUM
+                sar_slow:
+#endif
+                    if (js_binary_logic_slow(ctx, sp, opcode))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+        CASE(OP_and):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    sp[-2] = JS_NewInt32(ctx,
+                                         JS_VALUE_GET_INT(op1) &
+                                         JS_VALUE_GET_INT(op2));
+                    sp--;
+                } else {
+                    if (js_binary_logic_slow(ctx, sp, opcode))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+        CASE(OP_or):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    sp[-2] = JS_NewInt32(ctx,
+                                         JS_VALUE_GET_INT(op1) |
+                                         JS_VALUE_GET_INT(op2));
+                    sp--;
+                } else {
+                    if (js_binary_logic_slow(ctx, sp, opcode))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+        CASE(OP_xor):
+            {
+                JSValue op1, op2;
+                op1 = sp[-2];
+                op2 = sp[-1];
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
+                    sp[-2] = JS_NewInt32(ctx,
+                                         JS_VALUE_GET_INT(op1) ^
+                                         JS_VALUE_GET_INT(op2));
+                    sp--;
+                } else {
+                    if (js_binary_logic_slow(ctx, sp, opcode))
+                        goto exception;
+                    sp--;
+                }
+            }
+            BREAK;
+
+
+#define OP_CMP(opcode, binary_op, slow_call)              \
+            CASE(opcode):                                 \
+                {                                         \
+                JSValue op1, op2;                         \
+                op1 = sp[-2];                             \
+                op2 = sp[-1];                                   \
+                if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {           \
+                    sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \
+                    sp--;                                               \
+                } else {                                                \
+                    if (slow_call)                                      \
+                        goto exception;                                 \
+                    sp--;                                               \
+                }                                                       \
+                }                                                       \
+            BREAK
+
+            OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode));
+            OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode));
+            OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode));
+            OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode));
+            OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0));
+            OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1));
+            OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0));
+            OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1));
+
+#ifdef CONFIG_BIGNUM
+        CASE(OP_mul_pow10):
+            if (js_mul_pow10(ctx, sp))
+                goto exception;
+            sp--;
+            BREAK;
+#endif
+        CASE(OP_in):
+            if (js_operator_in(ctx, sp))
+                goto exception;
+            sp--;
+            BREAK;
+        CASE(OP_instanceof):
+            if (js_operator_instanceof(ctx, sp))
+                goto exception;
+            sp--;
+            BREAK;
+        CASE(OP_typeof):
+            {
+                JSValue op1;
+                JSAtom atom;
+
+                op1 = sp[-1];
+                atom = js_operator_typeof(ctx, op1);
+                JS_FreeValue(ctx, op1);
+                sp[-1] = JS_AtomToString(ctx, atom);
+            }
+            BREAK;
+        CASE(OP_delete):
+            if (js_operator_delete(ctx, sp))
+                goto exception;
+            sp--;
+            BREAK;
+        CASE(OP_delete_var):
+            {
+                JSAtom atom;
+                int ret;
+
+                atom = get_u32(pc);
+                pc += 4;
+
+                ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0);
+                if (unlikely(ret < 0))
+                    goto exception;
+                *sp++ = JS_NewBool(ctx, ret);
+            }
+            BREAK;
+
+        CASE(OP_to_object):
+            if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) {
+                ret_val = JS_ToObject(ctx, sp[-1]);
+                if (JS_IsException(ret_val))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = ret_val;
+            }
+            BREAK;
+
+        CASE(OP_to_propkey):
+            switch (JS_VALUE_GET_TAG(sp[-1])) {
+            case JS_TAG_INT:
+            case JS_TAG_STRING:
+            case JS_TAG_SYMBOL:
+                break;
+            default:
+                ret_val = JS_ToPropertyKey(ctx, sp[-1]);
+                if (JS_IsException(ret_val))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = ret_val;
+                break;
+            }
+            BREAK;
+
+        CASE(OP_to_propkey2):
+            /* must be tested first */
+            if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) {
+                JS_ThrowTypeError(ctx, "value has no property");
+                goto exception;
+            }
+            switch (JS_VALUE_GET_TAG(sp[-1])) {
+            case JS_TAG_INT:
+            case JS_TAG_STRING:
+            case JS_TAG_SYMBOL:
+                break;
+            default:
+                ret_val = JS_ToPropertyKey(ctx, sp[-1]);
+                if (JS_IsException(ret_val))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = ret_val;
+                break;
+            }
+            BREAK;
+#if 0
+        CASE(OP_to_string):
+            if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_STRING) {
+                ret_val = JS_ToString(ctx, sp[-1]);
+                if (JS_IsException(ret_val))
+                    goto exception;
+                JS_FreeValue(ctx, sp[-1]);
+                sp[-1] = ret_val;
+            }
+            BREAK;
+#endif
+        CASE(OP_with_get_var):
+        CASE(OP_with_put_var):
+        CASE(OP_with_delete_var):
+        CASE(OP_with_make_ref):
+        CASE(OP_with_get_ref):
+        CASE(OP_with_get_ref_undef):
+            {
+                JSAtom atom;
+                int32_t diff;
+                JSValue obj, val;
+                int ret, is_with;
+                atom = get_u32(pc);
+                diff = get_u32(pc + 4);
+                is_with = pc[8];
+                pc += 9;
+
+                obj = sp[-1];
+                ret = JS_HasProperty(ctx, obj, atom);
+                if (unlikely(ret < 0))
+                    goto exception;
+                if (ret) {
+                    if (is_with) {
+                        ret = js_has_unscopable(ctx, obj, atom);
+                        if (unlikely(ret < 0))
+                            goto exception;
+                        if (ret)
+                            goto no_with;
+                    }
+                    switch (opcode) {
+                    case OP_with_get_var:
+                        val = JS_GetProperty(ctx, obj, atom);
+                        if (unlikely(JS_IsException(val)))
+                            goto exception;
+                        set_value(ctx, &sp[-1], val);
+                        break;
+                    case OP_with_put_var:
+                        ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2],
+                                                     JS_PROP_THROW_STRICT);
+                        JS_FreeValue(ctx, sp[-1]);
+                        sp -= 2;
+                        if (unlikely(ret < 0))
+                            goto exception;
+                        break;
+                    case OP_with_delete_var:
+                        ret = JS_DeleteProperty(ctx, obj, atom, 0);
+                        if (unlikely(ret < 0))
+                            goto exception;
+                        JS_FreeValue(ctx, sp[-1]);
+                        sp[-1] = JS_NewBool(ctx, ret);
+                        break;
+                    case OP_with_make_ref:
+                        /* produce a pair object/propname on the stack */
+                        *sp++ = JS_AtomToValue(ctx, atom);
+                        break;
+                    case OP_with_get_ref:
+                        /* produce a pair object/method on the stack */
+                        val = JS_GetProperty(ctx, obj, atom);
+                        if (unlikely(JS_IsException(val)))
+                            goto exception;
+                        *sp++ = val;
+                        break;
+                    case OP_with_get_ref_undef:
+                        /* produce a pair undefined/function on the stack */
+                        val = JS_GetProperty(ctx, obj, atom);
+                        if (unlikely(JS_IsException(val)))
+                            goto exception;
+                        JS_FreeValue(ctx, sp[-1]);
+                        sp[-1] = JS_UNDEFINED;
+                        *sp++ = val;
+                        break;
+                    }
+                    pc += diff - 5;
+                } else {
+                no_with:
+                    /* if not jumping, drop the object argument */
+                    JS_FreeValue(ctx, sp[-1]);
+                    sp--;
+                }
+            }
+            BREAK;
+
+        CASE(OP_await):
+            ret_val = JS_NewInt32(ctx, FUNC_RET_AWAIT);
+            goto done_generator;
+        CASE(OP_yield):
+            ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD);
+            goto done_generator;
+        CASE(OP_yield_star):
+        CASE(OP_async_yield_star):
+            ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD_STAR);
+            goto done_generator;
+        CASE(OP_return_async):
+        CASE(OP_initial_yield):
+            ret_val = JS_UNDEFINED;
+            goto done_generator;
+
+        CASE(OP_nop):
+            BREAK;
+#if SHORT_OPCODES
+        CASE(OP_is_undefined):
+            if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) {
+                goto set_true;
+            } else {
+                goto free_and_set_false;
+            }
+        CASE(OP_is_null):
+            if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) {
+                goto set_true;
+            } else {
+                goto free_and_set_false;
+            }
+        CASE(OP_is_function):
+            if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) {
+                goto free_and_set_true;
+            } else {
+                goto free_and_set_false;
+            }
+        free_and_set_true:
+            JS_FreeValue(ctx, sp[-1]);
+        set_true:
+            sp[-1] = JS_TRUE;
+            BREAK;
+        free_and_set_false:
+            JS_FreeValue(ctx, sp[-1]);
+            sp[-1] = JS_FALSE;
+            BREAK;
+#endif
+        CASE(OP_invalid):
+        DEFAULT:
+            JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
+                                  (int)(pc - b->byte_code_buf - 1), opcode);
+            goto exception;
+        }
+    }
+ exception:
+    if (ctx->exception_needs_backtrace) {
+        /* add the backtrace information now (it is not done
+           before if the exception happens in a bytecode
+           operation */
+        build_backtrace(ctx, ctx->current_exception, NULL, 0, pc);
+    }
+    if (!JS_IsUncatchableError(ctx, ctx->current_exception)) {
+        while (sp > stack_buf) {
+            JSValue val = *--sp;
+            JS_FreeValue(ctx, val);
+            if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) {
+                int pos = JS_VALUE_GET_INT(val);
+                if (pos == 0) {
+                    /* enumerator: close it with a throw */
+                    JS_FreeValue(ctx, sp[-1]); /* drop the next method */
+                    sp--;
+                    JS_IteratorClose(ctx, sp[-1], TRUE);
+                } else {
+                    *sp++ = ctx->current_exception;
+                    ctx->current_exception = JS_NULL;
+                    pc = b->byte_code_buf + pos;
+                    goto restart;
+                }
+            }
+        }
+    }
+    ret_val = JS_EXCEPTION;
+    /* the local variables are freed by the caller in the generator
+       case. Hence the label 'done' should never be reached in a
+       generator function. */
+    if (b->func_kind != JS_FUNC_NORMAL) {
+    done_generator:
+        sf->cur_pc = pc;
+        sf->cur_sp = sp;
+    } else {
+    done:
+        if (unlikely(!list_empty(&sf->var_ref_list))) {
+            /* variable references reference the stack: must close them */
+            close_var_refs(ctx->rt, sf);
+        }
+        /* free the local variables and stack */
+        for(pval = local_buf; pval < sp; pval++) {
+            JS_FreeValue(ctx, *pval);
+        }
+    }
+    ctx->current_stack_frame = sf->prev_frame;
+    return ret_val;
+}
+
+JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj,
+                int argc, JSValueConst *argv)
+{
+    return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED,
+                           argc, (JSValue *)argv, CALL_FLAG_COPY_ARGV);
+}
+
+static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj,
+                           int argc, JSValueConst *argv)
+{
+    JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED,
+                                  argc, (JSValue *)argv, CALL_FLAG_COPY_ARGV);
+    JS_FreeValue(ctx, func_obj);
+    return res;
+}
+
+static JSValue js_get_prototype_from_ctor(JSContext *ctx, JSValueConst ctor,
+                                          JSValueConst def_proto)
+{
+    JSValue proto;
+    proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype);
+    if (JS_IsException(proto))
+        return proto;
+    if (!JS_IsObject(proto)) {
+        JS_FreeValue(ctx, proto);
+        proto = JS_DupValue(ctx, def_proto);
+    }
+    return proto;
+}
+
+static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor,
+                                   int class_id)
+{
+    JSValue proto, obj;
+    if (JS_IsUndefined(ctor)) {
+        proto = JS_DupValue(ctx, ctx->class_proto[class_id]);
+    } else {
+        proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype);
+        if (JS_IsException(proto))
+            return proto;
+        if (!JS_IsObject(proto)) {
+            JS_FreeValue(ctx, proto);
+            /* check if revoked proxy */
+            {
+                JSProxyData *s = JS_GetOpaque(ctor, JS_CLASS_PROXY);
+                if (s && s->is_revoked)
+                    return JS_ThrowTypeErrorRevokedProxy(ctx);
+            }
+            /* XXX: should use the ctor realm instead of 'ctx' */
+            proto = JS_DupValue(ctx, ctx->class_proto[class_id]);
+        }
+    }
+    obj = JS_NewObjectProtoClass(ctx, proto, class_id);
+    JS_FreeValue(ctx, proto);
+    return obj;
+}
+
+/* argv[] is modified if (flags & CALL_FLAG_COPY_ARGV) = 0. */
+static JSValue JS_CallConstructorInternal(JSContext *ctx,
+                                          JSValueConst func_obj,
+                                          JSValueConst new_target,
+                                          int argc, JSValue *argv, int flags)
+{
+    JSObject *p;
+    JSFunctionBytecode *b;
+
+    if (js_poll_interrupts(ctx))
+        return JS_EXCEPTION;
+    flags |= CALL_FLAG_CONSTRUCTOR;
+    if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT))
+        goto not_a_function;
+    p = JS_VALUE_GET_OBJ(func_obj);
+    if (unlikely(!p->is_constructor))
+        return JS_ThrowTypeError(ctx, "not a constructor");
+    if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
+        switch(p->class_id) {
+        case JS_CLASS_C_FUNCTION:
+            return js_call_c_function(ctx, func_obj, new_target,
+                                      argc, (JSValueConst *)argv, flags);
+        case JS_CLASS_BOUND_FUNCTION:
+            return js_call_bound_function(ctx, func_obj, new_target, argc,
+                                          (JSValueConst *)argv, flags);
+        case JS_CLASS_PROXY:
+            return js_proxy_call_constructor(ctx, func_obj, new_target, argc,
+                                             (JSValueConst *)argv);
+        default:
+        not_a_function:
+            return JS_ThrowTypeError(ctx, "not a function");
+        }
+    }
+
+    b = p->u.func.function_bytecode;
+    if (b->is_derived_class_constructor) {
+        return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags);
+    } else {
+        JSValue obj, ret;
+        /* legacy constructor behavior */
+        obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT);
+        if (JS_IsException(obj))
+            return JS_EXCEPTION;
+        ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags);
+        if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT ||
+            JS_IsException(ret)) {
+            JS_FreeValue(ctx, obj);
+            return ret;
+        } else {
+            JS_FreeValue(ctx, ret);
+            return obj;
+        }
+    }
+}
+
+JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj,
+                            JSValueConst new_target,
+                            int argc, JSValueConst *argv)
+{
+    return JS_CallConstructorInternal(ctx, func_obj, new_target,
+                                      argc, (JSValue *)argv,
+                                      CALL_FLAG_COPY_ARGV);
+}
+
+JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj,
+                           int argc, JSValueConst *argv)
+{
+    return JS_CallConstructorInternal(ctx, func_obj, func_obj,
+                                      argc, (JSValue *)argv,
+                                      CALL_FLAG_COPY_ARGV);
+}
+
+JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom,
+                  int argc, JSValueConst *argv)
+{
+    JSValue func_obj;
+    func_obj = JS_GetProperty(ctx, this_val, atom);
+    if (JS_IsException(func_obj))
+        return func_obj;
+    return JS_CallFree(ctx, func_obj, this_val, argc, argv);
+}
+
+static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
+                             int argc, JSValueConst *argv)
+{
+    JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv);
+    JS_FreeValue(ctx, this_val);
+    return res;
+}
+
+/* JSAsyncFunctionState (used by generator and async functions) */
+static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s,
+                                       JSValueConst func_obj, JSValueConst this_obj,
+                                       int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    JSFunctionBytecode *b;
+    JSStackFrame *sf;
+    int local_count, i, arg_buf_len, n;
+
+    sf = &s->frame;
+    init_list_head(&sf->var_ref_list);
+    p = JS_VALUE_GET_OBJ(func_obj);
+    b = p->u.func.function_bytecode;
+    sf->js_mode = b->js_mode;
+    sf->cur_pc = b->byte_code_buf;
+    arg_buf_len = max_int(b->arg_count, argc);
+    local_count = arg_buf_len + b->var_count + b->stack_size;
+    sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1));
+    if (!sf->arg_buf)
+        return -1;
+    sf->cur_func = JS_DupValue(ctx, func_obj);
+    s->this_val = JS_DupValue(ctx, this_obj);
+    s->argc = argc;
+    sf->arg_count = arg_buf_len;
+    sf->var_buf = sf->arg_buf + arg_buf_len;
+    sf->cur_sp = sf->var_buf + b->var_count;
+    for(i = 0; i < argc; i++)
+        sf->arg_buf[i] = JS_DupValue(ctx, argv[i]);
+    n = arg_buf_len + b->var_count;
+    for(i = argc; i < n; i++)
+        sf->arg_buf[i] = JS_UNDEFINED;
+    return 0;
+}
+
+static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s,
+                            JS_MarkFunc *mark_func)
+{
+    JSStackFrame *sf;
+    JSValue *sp;
+
+    sf = &s->frame;
+    JS_MarkValue(rt, sf->cur_func, mark_func);
+    JS_MarkValue(rt, s->this_val, mark_func);
+    if (sf->cur_sp) {
+        /* if the function is running, cur_sp is not known so we
+           cannot mark the stack. Marking the variables is not needed
+           because a running function cannot be part of a removable
+           cycle */
+        for(sp = sf->arg_buf; sp < sf->cur_sp; sp++)
+            JS_MarkValue(rt, *sp, mark_func);
+    }
+}
+
+static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
+{
+    JSStackFrame *sf;
+    JSValue *sp;
+
+    sf = &s->frame;
+
+    /* close the closure variables. */
+    close_var_refs(rt, sf);
+    
+    if (sf->arg_buf) {
+        /* cannot free the function if it is running */
+        assert(sf->cur_sp != NULL);
+        for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) {
+            JS_FreeValueRT(rt, *sp);
+        }
+        js_free_rt(rt, sf->arg_buf);
+    }
+    JS_FreeValueRT(rt, sf->cur_func);
+    JS_FreeValueRT(rt, s->this_val);
+}
+
+static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s)
+{
+    JSValue func_obj;
+
+    /* the tag does not matter provided it is not an object */
+    func_obj = JS_MKPTR(JS_TAG_INT, s);
+    return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED,
+                           s->argc, s->frame.arg_buf, CALL_FLAG_GENERATOR);
+}
+
+
+/* Generators */
+
+typedef enum JSGeneratorStateEnum {
+    JS_GENERATOR_STATE_SUSPENDED_START,
+    JS_GENERATOR_STATE_SUSPENDED_YIELD,
+    JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
+    JS_GENERATOR_STATE_EXECUTING,
+    JS_GENERATOR_STATE_COMPLETED,
+} JSGeneratorStateEnum;
+
+typedef struct JSGeneratorData {
+    JSGeneratorStateEnum state;
+    JSAsyncFunctionState func_state;
+} JSGeneratorData;
+
+static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s)
+{
+    if (s->state == JS_GENERATOR_STATE_COMPLETED)
+        return;
+    async_func_free(rt, &s->func_state);
+    s->state = JS_GENERATOR_STATE_COMPLETED;
+}
+
+static void js_generator_finalizer(JSRuntime *rt, JSValue obj)
+{
+    JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR);
+
+    if (s) {
+        free_generator_stack_rt(rt, s);
+        js_free_rt(rt, s);
+    }
+}
+
+static void free_generator_stack(JSContext *ctx, JSGeneratorData *s)
+{
+    free_generator_stack_rt(ctx->rt, s);
+}
+
+static void js_generator_mark(JSRuntime *rt, JSValueConst val,
+                              JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSGeneratorData *s = p->u.generator_data;
+
+    if (!s || s->state == JS_GENERATOR_STATE_COMPLETED)
+        return;
+    async_func_mark(rt, &s->func_state, mark_func);
+}
+
+/* XXX: use enum */
+#define GEN_MAGIC_NEXT   0
+#define GEN_MAGIC_RETURN 1
+#define GEN_MAGIC_THROW  2
+
+static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv,
+                                 BOOL *pdone, int magic)
+{
+    JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR);
+    JSStackFrame *sf;
+    JSValue ret, func_ret;
+    JSValueConst iter_args[1];
+
+    *pdone = TRUE;
+    if (!s)
+        return JS_ThrowTypeError(ctx, "not a generator");
+    sf = &s->func_state.frame;
+ redo:
+    switch(s->state) {
+    default:
+    case JS_GENERATOR_STATE_SUSPENDED_START:
+        if (magic == GEN_MAGIC_NEXT) {
+            goto exec_no_arg;
+        } else {
+            free_generator_stack(ctx, s);
+            goto done;
+        }
+        break;
+    case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
+        {
+            int done;
+            JSValue method, iter_obj;
+
+            iter_obj = sf->cur_sp[-2];
+            if (magic == GEN_MAGIC_NEXT) {
+                method = JS_DupValue(ctx, sf->cur_sp[-1]);
+            } else {
+                method = JS_GetProperty(ctx, iter_obj,
+                                        magic == GEN_MAGIC_RETURN ?
+                                        JS_ATOM_return : JS_ATOM_throw);
+                if (JS_IsException(method))
+                    goto iter_exception;
+            }
+            if (magic != GEN_MAGIC_NEXT &&
+                (JS_IsUndefined(method) || JS_IsNull(method))) {
+                /* default action */
+                if (magic == GEN_MAGIC_RETURN) {
+                    ret = JS_DupValue(ctx, argv[0]);
+                    goto iter_done;
+                } else {
+                    if (JS_IteratorClose(ctx, iter_obj, FALSE))
+                        goto iter_exception;
+                    JS_ThrowTypeError(ctx, "iterator does not have a throw method");
+                    goto iter_exception;
+                }
+            }
+            ret = JS_IteratorNext2(ctx, iter_obj, method, argc, argv, &done);
+            JS_FreeValue(ctx, method);
+            if (JS_IsException(ret)) {
+            iter_exception:
+                goto exec_throw;
+            }
+            /* if not done, the iterator returns the exact object
+               returned by 'method' */
+            if (done == 2) {
+                JSValue done_val, value;
+                done_val = JS_GetProperty(ctx, ret, JS_ATOM_done);
+                if (JS_IsException(done_val)) {
+                    JS_FreeValue(ctx, ret);
+                    goto iter_exception;
+                }
+                done = JS_ToBoolFree(ctx, done_val);
+                if (done) {
+                    value = JS_GetProperty(ctx, ret, JS_ATOM_value);
+                    JS_FreeValue(ctx, ret);
+                    if (JS_IsException(value))
+                        goto iter_exception;
+                    ret = value;
+                    goto iter_done;
+                } else {
+                    *pdone = 2;
+                }
+            } else {
+                if (done) {
+                    /* 'yield *' returns the value associated to done = true */
+                iter_done:
+                    JS_FreeValue(ctx, sf->cur_sp[-2]);
+                    JS_FreeValue(ctx, sf->cur_sp[-1]);
+                    sf->cur_sp--;
+                    goto exec_arg;
+                } else {
+                    *pdone = FALSE;
+                }
+            }
+            break;
+        }
+        break;
+    case JS_GENERATOR_STATE_SUSPENDED_YIELD:
+        /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */
+        ret = JS_DupValue(ctx, argv[0]);
+        if (magic == GEN_MAGIC_THROW) {
+            JS_Throw(ctx, ret);
+        exec_throw:
+            s->func_state.throw_flag = TRUE;
+        } else {
+        exec_arg:
+            sf->cur_sp[-1] = ret;
+            sf->cur_sp[0] = JS_NewBool(ctx, (magic == GEN_MAGIC_RETURN));
+            sf->cur_sp++;
+        exec_no_arg:
+            s->func_state.throw_flag = FALSE;
+        }
+        s->state = JS_GENERATOR_STATE_EXECUTING;
+        func_ret = async_func_resume(ctx, &s->func_state);
+        s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD;
+        if (JS_IsException(func_ret)) {
+            /* finalize the execution in case of exception */
+            free_generator_stack(ctx, s);
+            return func_ret;
+        }
+        if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
+            if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) {
+                /* 'yield *' */
+                s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
+                iter_args[0] = JS_UNDEFINED;
+                argc = 1;
+                argv = iter_args;
+                goto redo;
+            } else {
+                /* get the return the yield value at the top of the stack */
+                ret = sf->cur_sp[-1];
+                sf->cur_sp[-1] = JS_UNDEFINED;
+                *pdone = FALSE;
+            }
+        } else {
+            /* end of iterator */
+            ret = sf->cur_sp[-1];
+            sf->cur_sp[-1] = JS_UNDEFINED;
+            JS_FreeValue(ctx, func_ret);
+            free_generator_stack(ctx, s);
+        }
+        break;
+    case JS_GENERATOR_STATE_COMPLETED:
+    done:
+        /* execution is finished */
+        switch(magic) {
+        default:
+        case GEN_MAGIC_NEXT:
+            ret = JS_UNDEFINED;
+            break;
+        case GEN_MAGIC_RETURN:
+            ret = JS_DupValue(ctx, argv[0]);
+            break;
+        case GEN_MAGIC_THROW:
+            ret = JS_Throw(ctx, JS_DupValue(ctx, argv[0]));
+            break;
+        }
+        break;
+    case JS_GENERATOR_STATE_EXECUTING:
+        ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator");
+        break;
+    }
+    return ret;
+}
+
+static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
+                                          JSValueConst this_obj,
+                                          int argc, JSValueConst *argv)
+{
+    JSValue obj, func_ret;
+    JSGeneratorData *s;
+
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s)
+        return JS_EXCEPTION;
+    s->state = JS_GENERATOR_STATE_SUSPENDED_START;
+    if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
+        s->state = JS_GENERATOR_STATE_COMPLETED;
+        goto fail;
+    }
+
+    /* execute the function up to 'OP_initial_yield' */
+    func_ret = async_func_resume(ctx, &s->func_state);
+    if (JS_IsException(func_ret))
+        goto fail;
+    JS_FreeValue(ctx, func_ret);
+
+    obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR);
+    if (JS_IsException(obj))
+        goto fail;
+    JS_SetOpaque(obj, s);
+    return obj;
+ fail:
+    free_generator_stack_rt(ctx->rt, s);
+    js_free(ctx, s);
+    return JS_EXCEPTION;
+}
+
+/* AsyncFunction */
+
+static void js_async_function_terminate(JSRuntime *rt, JSContext *ctx,
+                                        JSAsyncFunctionData *s)
+{
+    if (s->is_active) {
+        async_func_free(rt, &s->func_state);
+        s->is_active = FALSE;
+    }
+}
+
+/* ctx can be NULL */
+static void js_async_function_free(JSRuntime *rt, JSContext *ctx,
+                                   JSAsyncFunctionData *s)
+{
+    if (--s->header.ref_count == 0) {
+        js_async_function_terminate(rt, ctx, s);
+        JS_FreeValueRT(rt, s->resolving_funcs[0]);
+        JS_FreeValueRT(rt, s->resolving_funcs[1]);
+        js_free_rt(rt, s);
+    }
+}
+
+static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSAsyncFunctionData *s = p->u.async_function_data;
+    if (s) {
+        js_async_function_free(rt, NULL, s);
+    }
+}
+
+static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val,
+                                           JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSAsyncFunctionData *s = p->u.async_function_data;
+    if (s) {
+        JS_MarkValue(rt, JS_MKPTR(JS_TAG_ASYNC_FUNCTION, s), mark_func);
+    }
+}
+
+static int js_async_function_resolve_create(JSContext *ctx,
+                                            JSAsyncFunctionData *s,
+                                            JSValue *resolving_funcs)
+{
+    int i;
+    JSObject *p;
+
+    for(i = 0; i < 2; i++) {
+        resolving_funcs[i] =
+            JS_NewObjectProtoClass(ctx, ctx->function_proto,
+                                   JS_CLASS_ASYNC_FUNCTION_RESOLVE + i);
+        if (JS_IsException(resolving_funcs[i])) {
+            if (i == 1)
+                JS_FreeValue(ctx, resolving_funcs[0]);
+            return -1;
+        }
+        p = JS_VALUE_GET_OBJ(resolving_funcs[i]);
+        s->header.ref_count++;
+        p->u.async_function_data = s;
+    }
+    return 0;
+}
+
+static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
+{
+    JSValue func_ret, ret2;
+
+    func_ret = async_func_resume(ctx, &s->func_state);
+    if (JS_IsException(func_ret)) {
+        JSValue error;
+    fail:
+        error = JS_GetException(ctx);
+        ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
+                       1, (JSValueConst *)&error);
+        JS_FreeValue(ctx, error);
+        js_async_function_terminate(ctx->rt, ctx, s);
+        JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
+    } else {
+        JSValue value;
+        value = s->func_state.frame.cur_sp[-1];
+        s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
+        if (JS_IsUndefined(func_ret)) {
+            /* function returned */
+            ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
+                           1, (JSValueConst *)&value);
+            JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
+            JS_FreeValue(ctx, value);
+            js_async_function_terminate(ctx->rt, ctx, s);
+        } else {
+            JSValue promise, resolving_funcs[2], resolving_funcs1[2];
+            int i, res;
+
+            /* await */
+            JS_FreeValue(ctx, func_ret); /* not used */
+            promise = js_promise_resolve(ctx, ctx->promise_ctor,
+                                         1, (JSValueConst *)&value, 0);
+            JS_FreeValue(ctx, value);
+            if (JS_IsException(promise))
+                goto fail;
+            if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
+                JS_FreeValue(ctx, promise);
+                goto fail;
+            }
+
+            /* Note: no need to create 'thrownawayCapability' as in
+               the spec */
+            for(i = 0; i < 2; i++)
+                resolving_funcs1[i] = JS_UNDEFINED;
+            res = perform_promise_then(ctx, promise,
+                                       (JSValueConst *)resolving_funcs,
+                                       (JSValueConst *)resolving_funcs1);
+            JS_FreeValue(ctx, promise);
+            for(i = 0; i < 2; i++)
+                JS_FreeValue(ctx, resolving_funcs[i]);
+            if (res)
+                goto fail;
+        }
+    }
+}
+
+static JSValue js_async_function_resolve_call(JSContext *ctx,
+                                              JSValueConst func_obj,
+                                              JSValueConst this_obj,
+                                              int argc, JSValueConst *argv)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(func_obj);
+    JSAsyncFunctionData *s = p->u.async_function_data;
+    BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE;
+    JSValueConst arg;
+
+    if (argc > 0)
+        arg = argv[0];
+    else
+        arg = JS_UNDEFINED;
+    s->func_state.throw_flag = is_reject;
+    if (is_reject) {
+        JS_Throw(ctx, JS_DupValue(ctx, arg));
+    } else {
+        /* return value of await */
+        s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
+    }
+    js_async_function_resume(ctx, s);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj,
+                                      JSValueConst this_obj,
+                                      int argc, JSValueConst *argv)
+{
+    JSValue promise;
+    JSAsyncFunctionData *s;
+
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s)
+        return JS_EXCEPTION;
+    s->header.ref_count = 1;
+    s->gc_header.mark = 0;
+    s->is_active = FALSE;
+    s->resolving_funcs[0] = JS_UNDEFINED;
+    s->resolving_funcs[1] = JS_UNDEFINED;
+
+    promise = JS_NewPromiseCapability(ctx, s->resolving_funcs);
+    if (JS_IsException(promise))
+        goto fail;
+
+    if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
+    fail:
+        JS_FreeValue(ctx, promise);
+        js_async_function_free(ctx->rt, ctx, s);
+        return JS_EXCEPTION;
+    }
+    s->is_active = TRUE;
+
+    js_async_function_resume(ctx, s);
+
+    js_async_function_free(ctx->rt, ctx, s);
+
+    return promise;
+}
+
+/* AsyncGenerator */
+
+typedef enum JSAsyncGeneratorStateEnum {
+    JS_ASYNC_GENERATOR_STATE_SUSPENDED_START,
+    JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD,
+    JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
+    JS_ASYNC_GENERATOR_STATE_EXECUTING,
+    JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN,
+    JS_ASYNC_GENERATOR_STATE_COMPLETED,
+} JSAsyncGeneratorStateEnum;
+
+typedef struct JSAsyncGeneratorRequest {
+    struct list_head link;
+    /* completion */
+    int completion_type; /* GEN_MAGIC_x */
+    JSValue result;
+    /* promise capability */
+    JSValue promise;
+    JSValue resolving_funcs[2];
+} JSAsyncGeneratorRequest;
+
+typedef struct JSAsyncGeneratorData {
+    JSObject *generator; /* back pointer to the object (const) */
+    JSAsyncGeneratorStateEnum state;
+    JSAsyncFunctionState func_state;
+    struct list_head queue; /* list of JSAsyncGeneratorRequest.link */
+} JSAsyncGeneratorData;
+
+static void js_async_generator_free(JSRuntime *rt,
+                                    JSAsyncGeneratorData *s)
+{
+    struct list_head *el, *el1;
+    JSAsyncGeneratorRequest *req;
+
+    list_for_each_safe(el, el1, &s->queue) {
+        req = list_entry(el, JSAsyncGeneratorRequest, link);
+        JS_FreeValueRT(rt, req->result);
+        JS_FreeValueRT(rt, req->promise);
+        JS_FreeValueRT(rt, req->resolving_funcs[0]);
+        JS_FreeValueRT(rt, req->resolving_funcs[1]);
+        js_free_rt(rt, req);
+    }
+    if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
+        s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
+        async_func_free(rt, &s->func_state);
+    }
+    js_free_rt(rt, s);
+}
+
+static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj)
+{
+    JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR);
+
+    if (s) {
+        js_async_generator_free(rt, s);
+    }
+}
+
+static void js_async_generator_mark(JSRuntime *rt, JSValueConst val,
+                                    JS_MarkFunc *mark_func)
+{
+    JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR);
+    struct list_head *el;
+    JSAsyncGeneratorRequest *req;
+    if (s) {
+        list_for_each(el, &s->queue) {
+            req = list_entry(el, JSAsyncGeneratorRequest, link);
+            JS_MarkValue(rt, req->result, mark_func);
+            JS_MarkValue(rt, req->promise, mark_func);
+            JS_MarkValue(rt, req->resolving_funcs[0], mark_func);
+            JS_MarkValue(rt, req->resolving_funcs[1], mark_func);
+        }
+        if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
+            s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
+            async_func_mark(rt, &s->func_state, mark_func);
+        }
+    }
+}
+
+static JSValue js_async_generator_resolve_function(JSContext *ctx,
+                                          JSValueConst this_obj,
+                                          int argc, JSValueConst *argv,
+                                          int magic, JSValue *func_data);
+
+static int js_async_generator_resolve_function_create(JSContext *ctx,
+                                                      JSValueConst generator,
+                                                      JSValue *resolving_funcs,
+                                                      BOOL is_resume_next)
+{
+    int i;
+    JSValue func;
+
+    for(i = 0; i < 2; i++) {
+        func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1,
+                                   i + is_resume_next * 2, 1, &generator);
+        if (JS_IsException(func)) {
+            if (i == 1)
+                JS_FreeValue(ctx, resolving_funcs[0]);
+            return -1;
+        }
+        resolving_funcs[i] = func;
+    }
+    return 0;
+}
+
+static int js_async_generator_await(JSContext *ctx,
+                                    JSAsyncGeneratorData *s,
+                                    JSValueConst value)
+{
+    JSValue promise, resolving_funcs[2], resolving_funcs1[2];
+    int i, res;
+
+    promise = js_promise_resolve(ctx, ctx->promise_ctor,
+                                 1, &value, 0);
+    if (JS_IsException(promise))
+        goto fail;
+
+    if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator),
+                                                   resolving_funcs, FALSE)) {
+        JS_FreeValue(ctx, promise);
+        goto fail;
+    }
+
+    /* Note: no need to create 'thrownawayCapability' as in
+       the spec */
+    for(i = 0; i < 2; i++)
+        resolving_funcs1[i] = JS_UNDEFINED;
+    res = perform_promise_then(ctx, promise,
+                               (JSValueConst *)resolving_funcs,
+                               (JSValueConst *)resolving_funcs1);
+    JS_FreeValue(ctx, promise);
+    for(i = 0; i < 2; i++)
+        JS_FreeValue(ctx, resolving_funcs[i]);
+    if (res)
+        goto fail;
+    return 0;
+ fail:
+    return -1;
+}
+
+static void js_async_generator_resolve_or_reject(JSContext *ctx,
+                                                 JSAsyncGeneratorData *s,
+                                                 JSValueConst result,
+                                                 int is_reject)
+{
+    JSAsyncGeneratorRequest *next;
+    JSValue ret;
+
+    next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
+    list_del(&next->link);
+    ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1,
+                  &result);
+    JS_FreeValue(ctx, ret);
+    JS_FreeValue(ctx, next->result);
+    JS_FreeValue(ctx, next->promise);
+    JS_FreeValue(ctx, next->resolving_funcs[0]);
+    JS_FreeValue(ctx, next->resolving_funcs[1]);
+    js_free(ctx, next);
+}
+
+static void js_async_generator_resolve(JSContext *ctx,
+                                       JSAsyncGeneratorData *s,
+                                       JSValueConst value,
+                                       BOOL done)
+{
+    JSValue result;
+    result = js_create_iterator_result(ctx, JS_DupValue(ctx, value), done);
+    /* XXX: better exception handling ? */
+    js_async_generator_resolve_or_reject(ctx, s, result, 0);
+    JS_FreeValue(ctx, result);
+ }
+
+static void js_async_generator_reject(JSContext *ctx,
+                                       JSAsyncGeneratorData *s,
+                                       JSValueConst exception)
+{
+    js_async_generator_resolve_or_reject(ctx, s, exception, 1);
+}
+
+static void js_async_generator_complete(JSContext *ctx,
+                                        JSAsyncGeneratorData *s)
+{
+    if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) {
+        s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
+        async_func_free(ctx->rt, &s->func_state);
+    }
+}
+
+static int js_async_generator_completed_return(JSContext *ctx,
+                                               JSAsyncGeneratorData *s,
+                                               JSValueConst value)
+{
+    JSValue promise, resolving_funcs[2], resolving_funcs1[2];
+    int res;
+
+    promise = js_promise_resolve(ctx, ctx->promise_ctor,
+                                 1, (JSValueConst *)&value, 0);
+    if (JS_IsException(promise))
+        return -1;
+    if (js_async_generator_resolve_function_create(ctx,
+                                                   JS_MKPTR(JS_TAG_OBJECT, s->generator),
+                                                   resolving_funcs1,
+                                                   TRUE)) {
+        JS_FreeValue(ctx, promise);
+        return -1;
+    }
+    resolving_funcs[0] = JS_UNDEFINED;
+    resolving_funcs[1] = JS_UNDEFINED;
+    res = perform_promise_then(ctx, promise,
+                               (JSValueConst *)resolving_funcs1,
+                               (JSValueConst *)resolving_funcs);
+    JS_FreeValue(ctx, resolving_funcs1[0]);
+    JS_FreeValue(ctx, resolving_funcs1[1]);
+    JS_FreeValue(ctx, promise);
+    return res;
+}
+
+static void js_async_generator_resume_next(JSContext *ctx,
+                                           JSAsyncGeneratorData *s)
+{
+    JSAsyncGeneratorRequest *next;
+    JSValue func_ret, value;
+
+    for(;;) {
+        if (list_empty(&s->queue))
+            break;
+        next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
+        switch(s->state) {
+        case JS_ASYNC_GENERATOR_STATE_EXECUTING:
+            /* only happens when restarting execution after await() */
+            goto resume_exec;
+        case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN:
+            goto done;
+        case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START:
+            if (next->completion_type == GEN_MAGIC_NEXT) {
+                goto exec_no_arg;
+            } else {
+                js_async_generator_complete(ctx, s);
+            }
+            break;
+        case JS_ASYNC_GENERATOR_STATE_COMPLETED:
+            if (next->completion_type == GEN_MAGIC_NEXT) {
+                js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE);
+            } else if (next->completion_type == GEN_MAGIC_RETURN) {
+                s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN;
+                js_async_generator_completed_return(ctx, s, next->result);
+                goto done;
+            } else {
+                js_async_generator_reject(ctx, s, next->result);
+            }
+            goto done;
+        case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD:
+        case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
+            value = JS_DupValue(ctx, next->result);
+            if (next->completion_type == GEN_MAGIC_THROW &&
+                s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) {
+                JS_Throw(ctx, value);
+                s->func_state.throw_flag = TRUE;
+            } else {
+                /* 'yield' returns a value. 'yield *' also returns a value
+                   in case the 'throw' method is called */
+                s->func_state.frame.cur_sp[-1] = value;
+                s->func_state.frame.cur_sp[0] =
+                    JS_NewInt32(ctx, next->completion_type);
+                s->func_state.frame.cur_sp++;
+            exec_no_arg:
+                s->func_state.throw_flag = FALSE;
+            }
+            s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING;
+        resume_exec:
+            func_ret = async_func_resume(ctx, &s->func_state);
+            if (JS_IsException(func_ret)) {
+                value = JS_GetException(ctx);
+                js_async_generator_complete(ctx, s);
+                js_async_generator_reject(ctx, s, value);
+                JS_FreeValue(ctx, value);
+            } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
+                int func_ret_code;
+                value = s->func_state.frame.cur_sp[-1];
+                s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
+                func_ret_code = JS_VALUE_GET_INT(func_ret);
+                switch(func_ret_code) {
+                case FUNC_RET_YIELD:
+                case FUNC_RET_YIELD_STAR:
+                    if (func_ret_code == FUNC_RET_YIELD_STAR)
+                        s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
+                    else
+                        s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD;
+                    js_async_generator_resolve(ctx, s, value, FALSE);
+                    JS_FreeValue(ctx, value);
+                    break;
+                case FUNC_RET_AWAIT:
+                    js_async_generator_await(ctx, s, value);
+                    JS_FreeValue(ctx, value);
+                    goto done;
+                default:
+                    abort();
+                }
+            } else {
+                assert(JS_IsUndefined(func_ret));
+                /* end of function */
+                value = s->func_state.frame.cur_sp[-1];
+                s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
+                js_async_generator_complete(ctx, s);
+                js_async_generator_resolve(ctx, s, value, TRUE);
+                JS_FreeValue(ctx, value);
+            }
+            break;
+        default:
+            abort();
+        }
+    }
+ done: ;
+}
+
+static JSValue js_async_generator_resolve_function(JSContext *ctx,
+                                                   JSValueConst this_obj,
+                                                   int argc, JSValueConst *argv,
+                                                   int magic, JSValue *func_data)
+{
+    BOOL is_reject = magic & 1;
+    JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR);
+    JSValueConst arg = argv[0];
+
+    /* XXX: what if s == NULL */
+
+    if (magic >= 2) {
+        /* resume next case in AWAITING_RETURN state */
+        assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN ||
+               s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED);
+        s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
+        if (is_reject) {
+            js_async_generator_reject(ctx, s, arg);
+        } else {
+            js_async_generator_resolve(ctx, s, arg, TRUE);
+        }
+    } else {
+        /* restart function execution after await() */
+        assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING);
+        s->func_state.throw_flag = is_reject;
+        if (is_reject) {
+            JS_Throw(ctx, JS_DupValue(ctx, arg));
+        } else {
+            /* return value of await */
+            s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
+        }
+        js_async_generator_resume_next(ctx, s);
+    }
+    return JS_UNDEFINED;
+}
+
+/* magic = GEN_MAGIC_x */
+static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv,
+                                       int magic)
+{
+    JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR);
+    JSValue promise, resolving_funcs[2];
+    JSAsyncGeneratorRequest *req;
+
+    promise = JS_NewPromiseCapability(ctx, resolving_funcs);
+    if (JS_IsException(promise))
+        return JS_EXCEPTION;
+    if (!s) {
+        JSValue err, res2;
+        JS_ThrowTypeError(ctx, "not an AsyncGenerator object");
+        err = JS_GetException(ctx);
+        res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
+                       1, (JSValueConst *)&err);
+        JS_FreeValue(ctx, err);
+        JS_FreeValue(ctx, res2);
+        JS_FreeValue(ctx, resolving_funcs[0]);
+        JS_FreeValue(ctx, resolving_funcs[1]);
+        return promise;
+    }
+    req = js_mallocz(ctx, sizeof(*req));
+    if (!req)
+        goto fail;
+    req->completion_type = magic;
+    req->result = JS_DupValue(ctx, argv[0]);
+    req->promise = JS_DupValue(ctx, promise);
+    req->resolving_funcs[0] = resolving_funcs[0];
+    req->resolving_funcs[1] = resolving_funcs[1];
+    list_add_tail(&req->link, &s->queue);
+    if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) {
+        js_async_generator_resume_next(ctx, s);
+    }
+    return promise;
+ fail:
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    JS_FreeValue(ctx, promise);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_async_generator_function_call(JSContext *ctx, JSValueConst func_obj,
+                                                JSValueConst this_obj,
+                                                int argc, JSValueConst *argv)
+{
+    JSValue obj, func_ret;
+    JSAsyncGeneratorData *s;
+
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s)
+        return JS_EXCEPTION;
+    s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START;
+    init_list_head(&s->queue);
+    if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
+        s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
+        goto fail;
+    }
+
+    /* execute the function up to 'OP_initial_yield' (no yield nor
+       await are possible) */
+    func_ret = async_func_resume(ctx, &s->func_state);
+    if (JS_IsException(func_ret))
+        goto fail;
+    JS_FreeValue(ctx, func_ret);
+
+    obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR);
+    if (JS_IsException(obj))
+        goto fail;
+    s->generator = JS_VALUE_GET_OBJ(obj);
+    JS_SetOpaque(obj, s);
+    return obj;
+ fail:
+    js_async_generator_free(ctx->rt, s);
+    return JS_EXCEPTION;
+}
+
+/* JS parser */
+
+enum {
+    TOK_NUMBER = -128,
+    TOK_STRING,
+    TOK_TEMPLATE,
+    TOK_IDENT,
+    TOK_REGEXP,
+    /* warning: order matters (see js_parse_assign_expr) */
+    TOK_MUL_ASSIGN,
+    TOK_DIV_ASSIGN,
+    TOK_MOD_ASSIGN,
+    TOK_PLUS_ASSIGN,
+    TOK_MINUS_ASSIGN,
+    TOK_SHL_ASSIGN,
+    TOK_SAR_ASSIGN,
+    TOK_SHR_ASSIGN,
+    TOK_AND_ASSIGN,
+    TOK_XOR_ASSIGN,
+    TOK_OR_ASSIGN,
+#ifdef CONFIG_BIGNUM
+    TOK_MATH_POW_ASSIGN,
+#endif
+    TOK_POW_ASSIGN,
+    TOK_DEC,
+    TOK_INC,
+    TOK_SHL,
+    TOK_SAR,
+    TOK_SHR,
+    TOK_LT,
+    TOK_LTE,
+    TOK_GT,
+    TOK_GTE,
+    TOK_EQ,
+    TOK_STRICT_EQ,
+    TOK_NEQ,
+    TOK_STRICT_NEQ,
+    TOK_LAND,
+    TOK_LOR,
+#ifdef CONFIG_BIGNUM
+    TOK_MATH_POW,
+#endif
+    TOK_POW,
+    TOK_ARROW,
+    TOK_ELLIPSIS,
+    TOK_ERROR,
+    TOK_PRIVATE_NAME,
+    TOK_EOF,
+    /* keywords: WARNING: same order as atoms */
+    TOK_NULL, /* must be first */
+    TOK_FALSE,
+    TOK_TRUE,
+    TOK_IF,
+    TOK_ELSE,
+    TOK_RETURN,
+    TOK_VAR,
+    TOK_THIS,
+    TOK_DELETE,
+    TOK_VOID,
+    TOK_TYPEOF,
+    TOK_NEW,
+    TOK_IN,
+    TOK_INSTANCEOF,
+    TOK_DO,
+    TOK_WHILE,
+    TOK_FOR,
+    TOK_BREAK,
+    TOK_CONTINUE,
+    TOK_SWITCH,
+    TOK_CASE,
+    TOK_DEFAULT,
+    TOK_THROW,
+    TOK_TRY,
+    TOK_CATCH,
+    TOK_FINALLY,
+    TOK_FUNCTION,
+    TOK_DEBUGGER,
+    TOK_WITH,
+    /* FutureReservedWord */
+    TOK_CLASS,
+    TOK_CONST,
+    TOK_ENUM,
+    TOK_EXPORT,
+    TOK_EXTENDS,
+    TOK_IMPORT,
+    TOK_SUPER,
+    /* FutureReservedWords when parsing strict mode code */
+    TOK_IMPLEMENTS,
+    TOK_INTERFACE,
+    TOK_LET,
+    TOK_PACKAGE,
+    TOK_PRIVATE,
+    TOK_PROTECTED,
+    TOK_PUBLIC,
+    TOK_STATIC,
+    TOK_YIELD,
+    TOK_AWAIT, /* must be last */
+    TOK_OF,     /* only used for js_parse_skip_parens_token() */
+};
+
+#define TOK_FIRST_KEYWORD   TOK_NULL
+#define TOK_LAST_KEYWORD    TOK_AWAIT
+
+/* unicode code points */
+#define CP_NBSP 0x00a0
+#define CP_BOM  0xfeff
+
+#define CP_LS   0x2028
+#define CP_PS   0x2029
+
+typedef struct BlockEnv {
+    struct BlockEnv *prev;
+    JSAtom label_name; /* JS_ATOM_NULL if none */
+    int label_break; /* -1 if none */
+    int label_cont; /* -1 if none */
+    int drop_count; /* number of stack elements to drop */
+    int label_finally; /* -1 if none */
+    int scope_level;
+    int has_iterator;
+} BlockEnv;
+
+typedef struct JSHoistedDef {
+    int cpool_idx; /* -1 means variable global definition */
+    uint8_t force_init : 1; /* initialize to undefined */
+    uint8_t is_lexical : 1; /* global let/const definition */
+    uint8_t is_const   : 1; /* const definition */
+    int var_idx;   /* function object index if cpool_idx >= 0 */
+    int scope_level;    /* scope of definition */
+    JSAtom var_name;  /* variable name if cpool_idx < 0 */
+} JSHoistedDef;
+
+typedef struct RelocEntry {
+    struct RelocEntry *next;
+    uint32_t addr; /* address to patch */
+    int size;   /* address size: 1, 2 or 4 bytes */
+} RelocEntry;
+
+typedef struct JumpSlot {
+    int op;
+    int size;
+    int pos;
+    int label;
+} JumpSlot;
+
+typedef struct LabelSlot {
+    int ref_count;
+    int pos;    /* phase 1 address, -1 means not resolved yet */
+    int pos2;   /* phase 2 address, -1 means not resolved yet */
+    int addr;   /* phase 3 address, -1 means not resolved yet */
+    RelocEntry *first_reloc;
+} LabelSlot;
+
+typedef struct LineNumberSlot {
+    uint32_t pc;
+    int line_num;
+} LineNumberSlot;
+
+typedef enum JSParseFunctionEnum {
+    JS_PARSE_FUNC_STATEMENT,
+    JS_PARSE_FUNC_VAR,
+    JS_PARSE_FUNC_EXPR,
+    JS_PARSE_FUNC_ARROW,
+    JS_PARSE_FUNC_GETTER,
+    JS_PARSE_FUNC_SETTER,
+    JS_PARSE_FUNC_METHOD,
+    JS_PARSE_FUNC_CLASS_CONSTRUCTOR,
+    JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR,
+} JSParseFunctionEnum;
+
+typedef enum JSParseExportEnum {
+    JS_PARSE_EXPORT_NONE,
+    JS_PARSE_EXPORT_NAMED,
+    JS_PARSE_EXPORT_DEFAULT,
+} JSParseExportEnum;
+
+typedef struct JSFunctionDef {
+    JSContext *ctx;
+    struct JSFunctionDef *parent;
+    int parent_cpool_idx; /* index in the constant pool of the parent
+                             or -1 if none */
+    int parent_scope_level; /* scope level in parent at point of definition */
+    struct list_head child_list; /* list of JSFunctionDef.link */
+    struct list_head link;
+
+    BOOL is_eval; /* TRUE if eval code */
+    int eval_type; /* only valid if is_eval = TRUE */
+    BOOL is_global_var; /* TRUE if variables are not defined locally:
+                           eval global, eval module or non strict eval */
+    BOOL is_func_expr; /* TRUE if function expression */
+    BOOL has_home_object; /* TRUE if the home object is available */
+    BOOL has_prototype; /* true if a prototype field is necessary */
+    BOOL has_simple_parameter_list;
+    BOOL has_use_strict; /* to reject directive in special cases */
+    BOOL has_eval_call; /* true if the function contains a call to eval() */
+    BOOL has_arguments_binding; /* true if the 'arguments' binding is
+                                   available in the function */
+    BOOL has_this_binding; /* true if the 'this' and new.target binding are
+                              available in the function */
+    BOOL new_target_allowed; /* true if the 'new.target' does not
+                                throw a syntax error */
+    BOOL super_call_allowed; /* true if super() is allowed */
+    BOOL super_allowed; /* true if super. or super[] is allowed */
+    BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */
+    BOOL is_derived_class_constructor;
+    BOOL in_function_body;
+    JSFunctionKindEnum func_kind : 8;
+    JSParseFunctionEnum func_type : 8;
+    uint8_t js_mode; /* bitmap of JS_MODE_x */
+    JSAtom func_name; /* JS_ATOM_NULL if no name */
+
+    JSVarDef *vars;
+    int var_size; /* allocated size for vars[] */
+    int var_count;
+    JSVarDef *args;
+    int arg_size; /* allocated size for args[] */
+    int arg_count; /* number of arguments */
+    int defined_arg_count;
+    int var_object_idx; /* -1 if none */
+    int arguments_var_idx; /* -1 if none */
+    int func_var_idx; /* variable containing the current function (-1
+                         if none, only used if is_func_expr is true) */
+    int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */
+    int this_var_idx; /* variable containg the 'this' value, -1 if none */
+    int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */
+    int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */
+    int home_object_var_idx;
+    BOOL need_home_object;
+    
+    int scope_level;    /* index into fd->scopes if the current lexical scope */
+    int scope_first;    /* index into vd->vars of first lexically scoped variable */
+    int scope_size;     /* allocated size of fd->scopes array */
+    int scope_count;    /* number of entries used in the fd->scopes array */
+    JSVarScope *scopes;
+    JSVarScope def_scope_array[4];
+
+    int hoisted_def_count;
+    int hoisted_def_size;
+    JSHoistedDef *hoisted_def;
+
+    DynBuf byte_code;
+    int last_opcode_pos; /* -1 if no last opcode */
+    int last_opcode_line_num;
+    BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */
+    
+    LabelSlot *label_slots;
+    int label_size; /* allocated size for label_slots[] */
+    int label_count;
+    BlockEnv *top_break; /* break/continue label stack */
+
+    /* constant pool (strings, functions, numbers) */
+    JSValue *cpool;
+    uint32_t cpool_count;
+    uint32_t cpool_size;
+
+    /* list of variables in the closure */
+    int closure_var_count;
+    int closure_var_size;
+    JSClosureVar *closure_var;
+
+    JumpSlot *jump_slots;
+    int jump_size;
+    int jump_count;
+
+    LineNumberSlot *line_number_slots;
+    int line_number_size;
+    int line_number_count;
+    int line_number_last;
+    int line_number_last_pc;
+
+    /* pc2line table */
+    JSAtom filename;
+    int line_num;
+    DynBuf pc2line;
+
+    char *source;  /* raw source, utf-8 encoded */
+    int source_len;
+
+    JSModuleDef *module; /* != NULL when parsing a module */
+} JSFunctionDef;
+
+typedef struct JSToken {
+    int val;
+    int line_num;   /* line number of token start */
+    const uint8_t *ptr;
+    union {
+        struct {
+            JSValue str;
+            int sep;
+        } str;
+        struct {
+            JSValue val;
+#ifdef CONFIG_BIGNUM
+            slimb_t exponent; /* may be != 0 only if val is a float */
+#endif
+        } num;
+        struct {
+            JSAtom atom;
+            BOOL has_escape;
+            BOOL is_reserved;
+        } ident;
+        struct {
+            JSValue body;
+            JSValue flags;
+        } regexp;
+    } u;
+} JSToken;
+
+typedef struct JSParseState {
+    JSContext *ctx;
+    int last_line_num;  /* line number of last token */
+    int line_num;       /* line number of current offset */
+    const char *filename;
+    JSToken token;
+    BOOL got_lf; /* true if got line feed before the current token */
+    const uint8_t *last_ptr;
+    const uint8_t *buf_ptr;
+    const uint8_t *buf_end;
+
+    /* current function code */
+    JSFunctionDef *cur_func;
+    BOOL is_module; /* parsing a module */
+    BOOL allow_html_comments;
+} JSParseState;
+
+typedef struct JSOpCode {
+#ifdef DUMP_BYTECODE
+    const char *name;
+#endif
+    uint8_t size; /* in bytes */
+    /* the opcodes remove n_pop items from the top of the stack, then
+       pushes n_push items */
+    uint8_t n_pop;
+    uint8_t n_push;
+    uint8_t fmt;
+} JSOpCode;
+
+static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = {
+#define FMT(f)
+#ifdef DUMP_BYTECODE
+#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f },
+#else
+#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f },
+#endif
+#include "quickjs-opcode.h"
+#undef DEF
+#undef FMT
+};
+
+#if SHORT_OPCODES
+/* After the final compilation pass, short opcodes are used. Their
+   opcodes overlap with the temporary opcodes which cannot appear in
+   the final bytecode. Their description is after the temporary
+   opcodes in opcode_info[]. */
+#define short_opcode_info(op)           \
+    opcode_info[(op) >= OP_TEMP_START ? \
+                (op) + (OP_TEMP_END - OP_TEMP_START) : (op)]
+#else
+#define short_opcode_info(op) opcode_info[op]
+#endif
+
+static __exception int next_token(JSParseState *s);
+
+static void free_token(JSParseState *s, JSToken *token)
+{
+    switch(token->val) {
+#ifdef CONFIG_BIGNUM
+    case TOK_NUMBER:
+        JS_FreeValue(s->ctx, token->u.num.val);
+        break;
+#endif
+    case TOK_STRING:
+    case TOK_TEMPLATE:
+        JS_FreeValue(s->ctx, token->u.str.str);
+        break;
+    case TOK_REGEXP:
+        JS_FreeValue(s->ctx, token->u.regexp.body);
+        JS_FreeValue(s->ctx, token->u.regexp.flags);
+        break;
+    case TOK_IDENT:
+    case TOK_FIRST_KEYWORD ... TOK_LAST_KEYWORD:
+    case TOK_PRIVATE_NAME:
+        JS_FreeAtom(s->ctx, token->u.ident.atom);
+        break;
+    default:
+        break;
+    }
+}
+
+static void __attribute((unused)) dump_token(JSParseState *s,
+                                             const JSToken *token)
+{
+    switch(token->val) {
+    case TOK_NUMBER:
+        {
+            double d;
+            JS_ToFloat64(s->ctx, &d, token->u.num.val);  /* no exception possible */
+            printf("number: %.14g\n", d);
+        }
+        break;
+    case TOK_IDENT:
+    dump_atom:
+        {
+            char buf[ATOM_GET_STR_BUF_SIZE];
+            printf("ident: '%s'\n",
+                   JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom));
+        }
+        break;
+    case TOK_STRING:
+        {
+            const char *str;
+            /* XXX: quote the string */
+            str = JS_ToCString(s->ctx, token->u.str.str);
+            printf("string: '%s'\n", str);
+            JS_FreeCString(s->ctx, str);
+        }
+        break;
+    case TOK_TEMPLATE:
+        {
+            const char *str;
+            str = JS_ToCString(s->ctx, token->u.str.str);
+            printf("template: `%s`\n", str);
+            JS_FreeCString(s->ctx, str);
+        }
+        break;
+    case TOK_REGEXP:
+        {
+            const char *str, *str2;
+            str = JS_ToCString(s->ctx, token->u.regexp.body);
+            str2 = JS_ToCString(s->ctx, token->u.regexp.flags);
+            printf("regexp: '%s' '%s'\n", str, str2);
+            JS_FreeCString(s->ctx, str);
+            JS_FreeCString(s->ctx, str2);
+        }
+        break;
+    case TOK_EOF:
+        printf("eof\n");
+        break;
+    default:
+        if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) {
+            goto dump_atom;
+        } else if (s->token.val >= 256) {
+            printf("token: %d\n", token->val);
+        } else {
+            printf("token: '%c'\n", token->val);
+        }
+        break;
+    }
+}
+
+int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...)
+{
+    JSContext *ctx = s->ctx;
+    va_list ap;
+
+    va_start(ap, fmt);
+    JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap);
+    va_end(ap);
+
+    build_backtrace(ctx, ctx->current_exception, s->filename, s->line_num, NULL);
+    return -1;
+}
+
+static int js_parse_expect(JSParseState *s, int tok)
+{
+    if (s->token.val != tok) {
+        /* XXX: dump token correctly in all cases */
+        return js_parse_error(s, "expecting '%c'", tok);
+    }
+    return next_token(s);
+}
+
+static int js_parse_expect_semi(JSParseState *s)
+{
+    if (s->token.val != ';') {
+        /* automatic insertion of ';' */
+        if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) {
+            return 0;
+        }
+        return js_parse_error(s, "expecting '%c'", ';');
+    }
+    return next_token(s);
+}
+
+static int js_parse_error_reserved_identifier(JSParseState *s)
+{
+    char buf1[ATOM_GET_STR_BUF_SIZE];
+    return js_parse_error(s, "'%s' is a reserved identifier",
+                          JS_AtomGetStr(s->ctx, buf1, sizeof(buf1),
+                                        s->token.u.ident.atom));
+}
+
+static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p)
+{
+    uint32_t c;
+    StringBuffer b_s, *b = &b_s;
+
+    /* p points to the first byte of the template part */
+    if (string_buffer_init(s->ctx, b, 32))
+        goto fail;
+    for(;;) {
+        if (p >= s->buf_end)
+            goto unexpected_eof;
+        c = *p++;
+        if (c == '`') {
+            /* template end part */
+            break;
+        }
+        if (c == '$' && *p == '{') {
+            /* template start or middle part */
+            p++;
+            break;
+        }
+        if (c == '\\') {
+            if (string_buffer_putc8(b, c))
+                goto fail;
+            if (p >= s->buf_end)
+                goto unexpected_eof;
+            c = *p++;
+        }
+        /* newline sequences are normalized as single '\n' bytes */
+        if (c == '\r') {
+            if (*p == '\n')
+                p++;
+            c = '\n';
+        }
+        if (c == '\n') {
+            s->line_num++;
+        } else if (c >= 0x80) {
+            const uint8_t *p_next;
+            c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
+            if (c > 0x10FFFF) {
+                js_parse_error(s, "invalid UTF-8 sequence");
+                goto fail;
+            }
+            p = p_next;
+        }
+        if (string_buffer_putc(b, c))
+            goto fail;
+    }
+    s->token.val = TOK_TEMPLATE;
+    s->token.u.str.sep = c;
+    s->token.u.str.str = string_buffer_end(b);
+    s->buf_ptr = p;
+    return 0;
+
+ unexpected_eof:
+    js_parse_error(s, "unexpected end of string");
+ fail:
+    string_buffer_free(b);
+    return -1;
+}
+
+static __exception int js_parse_string(JSParseState *s, int sep,
+                                       BOOL do_throw, const uint8_t *p,
+                                       JSToken *token, const uint8_t **pp)
+{
+    int ret;
+    uint32_t c;
+    StringBuffer b_s, *b = &b_s;
+
+    /* string */
+    if (string_buffer_init(s->ctx, b, 32))
+        goto fail;
+    for(;;) {
+        if (p >= s->buf_end)
+            goto invalid_char;
+        c = *p;
+        if (c < 0x20) {
+            if (!s->cur_func) {
+                if (do_throw)
+                    js_parse_error(s, "invalid character in a JSON string");
+                goto fail;
+            }
+            if (sep == '`') {
+                if (c == '\r') {
+                    if (p[1] == '\n')
+                        p++;
+                    c = '\n';
+                }
+                /* do not update s->line_num */
+            } else if (c == '\n' || c == '\r')
+                goto invalid_char;
+        }
+        p++;
+        if (c == sep)
+            break;
+        if (c == '$' && *p == '{' && sep == '`') {
+            /* template start or middle part */
+            p++;
+            break;
+        }
+        if (c == '\\') {
+            c = *p;
+            switch(c) {
+            case '\0':
+                if (p >= s->buf_end)
+                    goto invalid_char;
+                p++;
+                break;
+            case '\'':
+            case '\"':
+            case '\\':
+                p++;
+                break;
+            case '\r':  /* accept DOS and MAC newline sequences */
+                if (p[1] == '\n') {
+                    p++;
+                }
+                /* fall thru */
+            case '\n':
+                /* ignore escaped newline sequence */
+                p++;
+                if (sep != '`')
+                    s->line_num++;
+                continue;
+            default:
+                if (c >= '0' && c <= '7') {
+                    if (!s->cur_func)
+                        goto invalid_octal; /* JSON case */
+                    if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`')
+                        goto parse_escape;
+                    if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) {
+                        p++;
+                        c = '\0';
+                    } else {
+                    invalid_octal:
+                        if (do_throw)
+                            js_parse_error(s, "invalid octal syntax in strict mode");
+                        goto fail;
+                    }
+                } else if (c >= 0x80) {
+                    const uint8_t *p_next;
+                    c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next);
+                    if (c > 0x10FFFF) {
+                        goto invalid_utf8;
+                    }
+                    p = p_next;
+                    /* LS or PS are skipped */
+                    if (c == CP_LS || c == CP_PS)
+                        continue;
+                } else {
+                parse_escape:
+                    ret = lre_parse_escape(&p, TRUE);
+                    if (ret == -1) {
+                        if (do_throw)
+                            js_parse_error(s, "malformed escape sequence in string literal");
+                        goto fail;
+                    } else if (ret < 0) {
+                        /* ignore the '\' (could output a warning) */
+                        p++;
+                    } else {
+                        c = ret;
+                    }
+                }
+                break;
+            }
+        } else if (c >= 0x80) {
+            const uint8_t *p_next;
+            c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
+            if (c > 0x10FFFF)
+                goto invalid_utf8;
+            p = p_next;
+        }
+        if (string_buffer_putc(b, c))
+            goto fail;
+    }
+    token->val = TOK_STRING;
+    token->u.str.sep = c;
+    token->u.str.str = string_buffer_end(b);
+    *pp = p;
+    return 0;
+
+ invalid_utf8:
+    if (do_throw)
+        js_parse_error(s, "invalid UTF-8 sequence");
+    goto fail;
+ invalid_char:
+    if (do_throw)
+        js_parse_error(s, "unexpected end of string");
+ fail:
+    string_buffer_free(b);
+    return -1;
+}
+
+static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) {
+    return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom &&
+        !s->token.u.ident.has_escape;
+}
+
+static __exception int js_parse_regexp(JSParseState *s)
+{
+    const uint8_t *p;
+    BOOL in_class;
+    StringBuffer b_s, *b = &b_s;
+    StringBuffer b2_s, *b2 = &b2_s;
+    uint32_t c;
+
+    p = s->buf_ptr;
+    p++;
+    in_class = FALSE;
+    if (string_buffer_init(s->ctx, b, 32))
+        return -1;
+    if (string_buffer_init(s->ctx, b2, 1))
+        goto fail;
+    for(;;) {
+        if (p >= s->buf_end) {
+        eof_error:
+            js_parse_error(s, "unexpected end of regexp");
+            goto fail;
+        }
+        c = *p++;
+        if (c == '\n' || c == '\r') {
+            goto eol_error;
+        } else if (c == '/') {
+            if (!in_class)
+                break;
+        } else if (c == '[') {
+            in_class = TRUE;
+        } else if (c == ']') {
+            /* XXX: incorrect as the first character in a class */
+            in_class = FALSE;
+        } else if (c == '\\') {
+            if (string_buffer_putc8(b, c))
+                goto fail;
+            c = *p++;
+            if (c == '\n' || c == '\r')
+                goto eol_error;
+            else if (c == '\0' && p >= s->buf_end)
+                goto eof_error;
+            else if (c >= 0x80) {
+                const uint8_t *p_next;
+                c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
+                if (c > 0x10FFFF) {
+                    goto invalid_utf8;
+                }
+                p = p_next;
+                if (c == CP_LS || c == CP_PS)
+                    goto eol_error;
+            }
+        } else if (c >= 0x80) {
+            const uint8_t *p_next;
+            c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
+            if (c > 0x10FFFF) {
+            invalid_utf8:
+                js_parse_error(s, "invalid UTF-8 sequence");
+                goto fail;
+            }
+            p = p_next;
+            /* LS or PS are considered as line terminator */
+            if (c == CP_LS || c == CP_PS) {
+            eol_error:
+                js_parse_error(s, "unexpected line terminator in regexp");
+                goto fail;
+            }
+        }
+        if (string_buffer_putc(b, c))
+            goto fail;
+    }
+
+    /* flags */
+    for(;;) {
+        const uint8_t *p_next = p;
+        c = *p_next++;
+        if (c >= 0x80) {
+            c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next);
+            if (c > 0x10FFFF) {
+                goto invalid_utf8;
+            }
+        }
+        if (!lre_js_is_ident_next(c))
+            break;
+        if (string_buffer_putc(b2, c))
+            goto fail;
+        p = p_next;
+    }
+
+    s->token.val = TOK_REGEXP;
+    s->token.u.regexp.body = string_buffer_end(b);
+    s->token.u.regexp.flags = string_buffer_end(b2);
+    s->buf_ptr = p;
+    return 0;
+ fail:
+    string_buffer_free(b);
+    string_buffer_free(b2);
+    return -1;
+}
+
+static __exception int next_token(JSParseState *s)
+{
+    const uint8_t *p;
+    int c;
+    char buf[4096], *q;
+    BOOL ident_has_escape;
+
+    free_token(s, &s->token);
+
+    p = s->last_ptr = s->buf_ptr;
+    s->got_lf = FALSE;
+    s->last_line_num = s->token.line_num;
+ redo:
+    s->token.line_num = s->line_num;
+    s->token.ptr = p;
+    c = *p;
+    switch(c) {
+    case 0:
+        s->token.val = TOK_EOF;
+        break;
+    case '`':
+        if (!s->cur_func) {
+            /* JSON does not accept templates */
+            goto def_token;
+        }
+        if (js_parse_template_part(s, p + 1))
+            goto fail;
+        p = s->buf_ptr;
+        break;
+    case '\'':
+        if (!s->cur_func) {
+            /* JSON does not accept single quoted strings */
+            goto def_token;
+        }
+        /* fall through */
+    case '\"':
+        if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p))
+            goto fail;
+        break;
+    case '\r':  /* accept DOS and MAC newline sequences */
+        if (p[1] == '\n') {
+            p++;
+        }
+        /* fall thru */
+    case '\n':
+        p++;
+    line_terminator:
+        s->got_lf = TRUE;
+        s->line_num++;
+        goto redo;
+    case '\f':
+    case '\v':
+        if (!s->cur_func) {
+            /* JSONWhitespace does not match <VT>, nor <FF> */
+            goto def_token;
+        }
+        /* fall through */
+    case ' ':
+    case '\t':
+        p++;
+        goto redo;
+    case '/':
+        if (p[1] == '*') {
+            /* comment */
+            p += 2;
+            for(;;) {
+                if (*p == '\0' && p >= s->buf_end) {
+                    js_parse_error(s, "unexpected end of comment");
+                    goto fail;
+                }
+                if (p[0] == '*' && p[1] == '/') {
+                    p += 2;
+                    break;
+                }
+                if (*p == '\n') {
+                    s->line_num++;
+                    s->got_lf = TRUE; /* considered as LF for ASI */
+                    p++;
+                } else if (*p == '\r') {
+                    s->got_lf = TRUE; /* considered as LF for ASI */
+                    p++;
+                } else if (*p >= 0x80) {
+                    c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
+                    if (c == CP_LS || c == CP_PS) {
+                        s->got_lf = TRUE; /* considered as LF for ASI */
+                    }
+                } else {
+                    p++;
+                }
+            }
+            goto redo;
+        } else if (p[1] == '/') {
+            /* line comment */
+            p += 2;
+        skip_line_comment:
+            for(;;) {
+                if (*p == '\0' && p >= s->buf_end)
+                    break;
+                if (*p == '\r' || *p == '\n')
+                    break;
+                if (*p >= 0x80) {
+                    c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
+                    /* LS or PS are considered as line terminator */
+                    if (c == CP_LS || c == CP_PS)
+                        break;
+                } else {
+                    p++;
+                }
+            }
+            goto redo;
+        } else if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_DIV_ASSIGN;
+        } else {
+            p++;
+            s->token.val = c;
+        }
+        break;
+    case '\\':
+        if (p[1] == 'u') {
+            const uint8_t *p1 = p + 1;
+            int c1 = lre_parse_escape(&p1, TRUE);
+            if (c1 >= 0 && lre_js_is_ident_first(c1)) {
+                c = c1;
+                p = p1;
+                ident_has_escape = TRUE;
+                goto has_ident;
+            } else {
+                /* XXX: syntax error? */
+            }
+        }
+        goto def_token;
+    case 'a' ... 'z':
+    case 'A' ... 'Z':
+    case '_':
+    case '$':
+        /* identifier */
+        p++;
+        ident_has_escape = FALSE;
+    has_ident:
+        q = buf;
+        for(;;) {
+            const uint8_t *p1 = p;
+
+            if (c < 128) {
+                *q++ = c;
+            } else {
+                q += unicode_to_utf8((uint8_t*)q, c);
+            }
+            c = *p1++;
+            if (c == '\\' && *p1 == 'u') {
+                c = lre_parse_escape(&p1, TRUE);
+                ident_has_escape = TRUE;
+            } else if (c >= 128) {
+                c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1);
+            }
+            /* XXX: check if c >= 0 and c <= 0x10FFFF */
+            if (!lre_js_is_ident_next(c))
+                break;
+            p = p1;
+            if ((q - buf) >= sizeof(buf) - UTF8_CHAR_LEN_MAX) {
+                js_parse_error(s, "identifier too long");
+                goto fail;
+            }
+        }
+        *q = '\0';
+        s->token.u.ident.atom = JS_NewAtomLen(s->ctx, buf, q - buf);
+        s->token.u.ident.has_escape = ident_has_escape;
+        s->token.u.ident.is_reserved = FALSE;
+        if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD ||
+            (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD &&
+             s->cur_func && (s->cur_func->js_mode & JS_MODE_STRICT)) ||
+            (s->token.u.ident.atom == JS_ATOM_yield && s->cur_func &&
+             ((s->cur_func->func_kind & JS_FUNC_GENERATOR) ||
+              (s->cur_func->func_type == JS_PARSE_FUNC_ARROW &&
+               !s->cur_func->in_function_body && s->cur_func->parent &&
+               (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) ||
+            (s->token.u.ident.atom == JS_ATOM_await &&
+             (s->is_module ||
+              (s->cur_func &&
+               ((s->cur_func->func_kind & JS_FUNC_ASYNC) ||
+                (s->cur_func->func_type == JS_PARSE_FUNC_ARROW &&
+                 !s->cur_func->in_function_body && s->cur_func->parent &&
+                 (s->cur_func->parent->func_kind & JS_FUNC_ASYNC))))))) {
+                  if (ident_has_escape) {
+                      s->token.u.ident.is_reserved = TRUE;
+                      s->token.val = TOK_IDENT;
+                  } else {
+                      /* The keywords atoms are pre allocated */
+                      s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD;
+                  }
+        } else {
+            s->token.val = TOK_IDENT;
+        }
+        break;
+    case '#':
+        /* private name */
+        {
+            const uint8_t *p1;
+            p++;
+            q = buf;
+            *q++ = '#';
+            p1 = p;
+            c = *p1++;
+            if (c == '\\' && *p1 == 'u') {
+                c = lre_parse_escape(&p1, TRUE);
+            } else if (c >= 128) {
+                c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1);
+            }
+            if (!lre_js_is_ident_first(c)) {
+                js_parse_error(s, "invalid first character of private name");
+                goto fail;
+            }
+            p = p1;
+            for(;;) {
+                if (c < 128) {
+                    *q++ = c;
+                } else {
+                    q += unicode_to_utf8((uint8_t*)q, c);
+                }
+                p1 = p;
+                c = *p1++;
+                if (c == '\\' && *p1 == 'u') {
+                    c = lre_parse_escape(&p1, TRUE);
+                    ident_has_escape = TRUE;
+                } else if (c >= 128) {
+                    c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1);
+                }
+                /* XXX: check if c >= 0 and c <= 0x10FFFF */
+                if (!lre_js_is_ident_next(c))
+                    break;
+                p = p1;
+                if ((q - buf) >= sizeof(buf) - UTF8_CHAR_LEN_MAX) {
+                    js_parse_error(s, "private name too long");
+                    goto fail;
+                }
+            }
+            *q = '\0';
+            s->token.u.ident.atom = JS_NewAtomLen(s->ctx, buf, q - buf);
+            s->token.val = TOK_PRIVATE_NAME;
+        }
+        break;
+    case '.':
+        if (p[1] == '.' && p[2] == '.') {
+            p += 3;
+            s->token.val = TOK_ELLIPSIS;
+            break;
+        }
+        if (p[1] >= '0' && p[1] <= '9') {
+            goto parse_number;
+        } else {
+            goto def_token;
+        }
+        break;
+    case '0':
+        /* in strict or JSON parsing mode, octal literals are not accepted */
+        if (is_digit(p[1]) && (!s->cur_func ||
+                               (s->cur_func->js_mode & JS_MODE_STRICT))) {
+            js_parse_error(s, "octal literals are deprecated in strict mode");
+            goto fail;
+        }
+        goto parse_number;
+    case '1' ... '9':
+        /* number */
+    parse_number:
+#ifdef CONFIG_BIGNUM
+        {
+            const uint8_t *p1;
+            int flags;
+            slimb_t exponent;
+            JSValue val;
+
+            s->token.val = TOK_NUMBER;
+            s->token.u.num.val = JS_UNDEFINED;
+            s->token.u.num.exponent = 0;
+            if (!s->cur_func) {
+                /* JSON: use current float precision */
+                flags = BF_ATOF_THROW;
+                if (is_bignum_mode(s->ctx))
+                    flags |= BF_ATOF_INT_PREC_INF;
+                else
+                    flags |= BF_ATOF_FLOAT64;
+                val = js_atof(s->ctx, (const char *)p, (const char **)&p,
+                              10, flags);
+                if (JS_IsException(val))
+                    goto bad_number;
+            } else {
+                bf_t r_s, *r = &r_s;
+                BOOL bigint_mode, is_bigint, is_bigfloat;
+                int res;
+
+                bigint_mode = (s->cur_func->js_mode & JS_MODE_BIGINT) != 0;
+                flags = BF_ATOF_BIN_OCT | BF_ATOF_LEGACY_OCTAL |
+                    BF_ATOF_UNDERSCORE_SEP | BF_ATOF_INT_PREC_INF | BF_RNDZ;
+                if (!bigint_mode)
+                    flags |= BF_ATOF_ONLY_DEC_FLOAT;
+                p += skip_spaces((char *)p);
+                bf_init(s->ctx->bf_ctx, r);
+                res = bf_atof2(r, &exponent, (const char *)p,
+                               (const char **)&p, 0, BF_PREC_INF, flags);
+                if (bf_is_nan(r)) {
+                    bf_delete(r);
+                    goto bad_number;
+                }
+                is_bigint = FALSE;
+                is_bigfloat = FALSE;
+                if (*p == 'n') {
+                    if (!(res & BF_ATOF_ST_INTEGER) ||
+                        (res & BF_ATOF_ST_LEGACY_OCTAL)) {
+                        bf_delete(r);
+                        goto bad_number;
+                    }
+                    p++;
+                    is_bigint = TRUE;
+                } else if (*p == 'l') {
+                    p++;
+                    is_bigfloat = TRUE;
+                }
+                if ((res & BF_ATOF_ST_INTEGER) && !is_bigfloat &&
+                    (bigint_mode || is_bigint)) {
+                    /* in bigint mode: small integer or bigint
+                       in legacy mode: always a bigint */
+                    val = JS_NewBigInt2(s->ctx, r, bigint_mode ^ 1);
+                } else {
+                    /* `n` suffix is implied in math mode */
+                    if (s->cur_func->js_mode & JS_MODE_MATH)
+                        is_bigfloat = TRUE;
+                    if (is_bigfloat) {
+                        /* infinite precision */
+                        val = JS_NewBigFloat(s->ctx, r);
+                        s->token.u.num.exponent = exponent;
+                    } else {
+                        double d;
+                        d = bf_mul_pow10_to_float64(s->ctx, r, exponent);
+                        bf_delete(r);
+                        val = JS_NewFloat64(s->ctx, d);
+                    }
+                }
+            }
+            s->token.u.num.val = val;
+            if (lre_js_is_ident_next(unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1))) {
+            bad_number:
+                js_parse_error(s, "invalid number literal");
+                goto fail;
+            }
+        }
+#else
+        {
+            JSValue ret;
+            const uint8_t *p1;
+            int flags, radix;
+            if (!s->cur_func) {
+                flags = ATOD_THROW;
+                radix = 10;
+            } else {
+                flags = ATOD_ACCEPT_BIN_OCT | ATOD_THROW | ATOD_ACCEPT_LEGACY_OCTAL |
+                    ATOD_ACCEPT_UNDERSCORES;
+                radix = 0;
+            }
+            ret = js_atod(s->ctx, (const char *)p, (const char **)&p, radix,
+                          flags);
+            /* reject `10instanceof Number` */
+            if (JS_IsException(ret) ||
+                lre_js_is_ident_next(unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1))) {
+                js_parse_error(s, "invalid number literal");
+                goto fail;
+            }
+            s->token.val = TOK_NUMBER;
+            s->token.u.num.val = ret;
+        }
+#endif
+        break;
+    case '*':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_MUL_ASSIGN;
+        } else if (p[1] == '*') {
+            if (p[2] == '=') {
+                p += 3;
+                s->token.val = TOK_POW_ASSIGN;
+            } else {
+                p += 2;
+                s->token.val = TOK_POW;
+            }
+        } else {
+            goto def_token;
+        }
+        break;
+    case '%':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_MOD_ASSIGN;
+        } else {
+            goto def_token;
+        }
+        break;
+    case '+':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_PLUS_ASSIGN;
+        } else if (p[1] == '+') {
+            p += 2;
+            s->token.val = TOK_INC;
+        } else {
+            goto def_token;
+        }
+        break;
+    case '-':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_MINUS_ASSIGN;
+        } else if (p[1] == '-') {
+            if (s->allow_html_comments &&
+                p[2] == '>' && s->last_line_num != s->line_num) {
+                /* Annex B: `-->` at beginning of line is an html comment end.
+                   It extends to the end of the line.
+                 */
+                goto skip_line_comment;
+            }
+            p += 2;
+            s->token.val = TOK_DEC;
+        } else {
+            goto def_token;
+        }
+        break;
+    case '<':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_LTE;
+        } else if (p[1] == '<') {
+            if (p[2] == '=') {
+                p += 3;
+                s->token.val = TOK_SHL_ASSIGN;
+            } else {
+                p += 2;
+                s->token.val = TOK_SHL;
+            }
+        } else if (s->allow_html_comments &&
+                   p[1] == '!' && p[2] == '-' && p[3] == '-') {
+            /* Annex B: handle `<!--` single line html comments */
+            goto skip_line_comment;
+        } else {
+            goto def_token;
+        }
+        break;
+    case '>':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_GTE;
+        } else if (p[1] == '>') {
+            if (p[2] == '>') {
+                if (p[3] == '=') {
+                    p += 4;
+                    s->token.val = TOK_SHR_ASSIGN;
+                } else {
+                    p += 3;
+                    s->token.val = TOK_SHR;
+                }
+            } else if (p[2] == '=') {
+                p += 3;
+                s->token.val = TOK_SAR_ASSIGN;
+            } else {
+                p += 2;
+                s->token.val = TOK_SAR;
+            }
+        } else {
+            goto def_token;
+        }
+        break;
+    case '=':
+        if (p[1] == '=') {
+            if (p[2] == '=') {
+                p += 3;
+                s->token.val = TOK_STRICT_EQ;
+            } else {
+                p += 2;
+                s->token.val = TOK_EQ;
+            }
+        } else if (p[1] == '>') {
+            p += 2;
+            s->token.val = TOK_ARROW;
+        } else {
+            goto def_token;
+        }
+        break;
+    case '!':
+        if (p[1] == '=') {
+            if (p[2] == '=') {
+                p += 3;
+                s->token.val = TOK_STRICT_NEQ;
+            } else {
+                p += 2;
+                s->token.val = TOK_NEQ;
+            }
+        } else {
+            goto def_token;
+        }
+        break;
+    case '&':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_AND_ASSIGN;
+        } else if (p[1] == '&') {
+            p += 2;
+            s->token.val = TOK_LAND;
+        } else {
+            goto def_token;
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+        /* in math mode, '^' is the power operator. '^^' is always the
+           xor operator and '**' is always the power operator */
+    case '^':
+        if (p[1] == '=') {
+            p += 2;
+            if (s->cur_func && (s->cur_func->js_mode & JS_MODE_MATH))
+                s->token.val = TOK_MATH_POW_ASSIGN;
+            else
+                s->token.val = TOK_XOR_ASSIGN;
+        } else if (p[1] == '^') {
+            if (p[2] == '=') {
+                p += 3;
+                s->token.val = TOK_XOR_ASSIGN;
+            } else {
+                p += 2;
+                s->token.val = '^';
+            }
+        } else {
+            p++;
+            if (s->cur_func && (s->cur_func->js_mode & JS_MODE_MATH))
+                s->token.val = TOK_MATH_POW;
+            else
+                s->token.val = '^';
+        }
+        break;
+#else
+    case '^':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_XOR_ASSIGN;
+        } else {
+            goto def_token;
+        }
+        break;
+#endif
+    case '|':
+        if (p[1] == '=') {
+            p += 2;
+            s->token.val = TOK_OR_ASSIGN;
+        } else if (p[1] == '|') {
+            p += 2;
+            s->token.val = TOK_LOR;
+        } else {
+            goto def_token;
+        }
+        break;
+    default:
+        if (c >= 128) {
+            /* unicode value */
+            c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
+            switch(c) {
+            case CP_PS:
+            case CP_LS:
+                if (!s->cur_func) {
+                    /* <PS> and <LS> are not JSONWhitespace */
+                    goto def_token;
+                } else {
+                    /* XXX: should avoid incrementing line_number, but
+                       needed to handle HTML comments */
+                    goto line_terminator; 
+                }
+            default:
+                if (lre_is_space(c)) {
+                    if (!s->cur_func) {
+                        /* category z spaces are not JSONWhitespace */
+                        goto def_token;
+                    } else {
+                        goto redo;
+                    }
+                } else if (lre_js_is_ident_first(c)) {
+                    ident_has_escape = FALSE;
+                    goto has_ident;
+                } else {
+                    js_parse_error(s, "unexpected character");
+                    goto fail;
+                }
+            }
+        }
+    def_token:
+        s->token.val = c;
+        p++;
+        break;
+    }
+    s->buf_ptr = p;
+
+    //    dump_token(s, &s->token);
+    return 0;
+
+ fail:
+    s->token.val = TOK_ERROR;
+    return -1;
+}
+
+/* only used for ':' and '=>', 'let' or 'function' look-ahead. *pp is
+   only set if TOK_IMPORT is returned */
+/* XXX: handle all unicode cases */
+static int simple_next_token(const uint8_t **pp, BOOL no_line_terminator)
+{
+    const uint8_t *p;
+    uint32_t c;
+    
+    /* skip spaces and comments */
+    p = *pp;
+    for (;;) {
+        switch(c = *p++) {
+        case '\r':
+        case '\n':
+            if (no_line_terminator)
+                return '\n';
+            continue;
+        case ' ':
+        case '\t':
+        case '\v':
+        case '\f':
+            continue;
+        case '/':
+            if (*p == '/') {
+                if (no_line_terminator)
+                    return '\n';
+                while (*p && *p != '\r' && *p != '\n')
+                    p++;
+                continue;
+            }
+            if (*p == '*') {
+                while (*++p) {
+                    if ((*p == '\r' || *p == '\n') && no_line_terminator)
+                        return '\n';
+                    if (*p == '*' && p[1] == '/') {
+                        p += 2;
+                        break;
+                    }
+                }
+                continue;
+            }
+            break;
+        case '=':
+            if (*p == '>')
+                return TOK_ARROW;
+            break;
+        default:
+            if (lre_js_is_ident_first(c)) {
+                if (c == 'i') {
+                    if (p[0] == 'n' && !lre_js_is_ident_next(p[1])) {
+                        return TOK_IN;
+                    }
+                    if (p[0] == 'm' && p[1] == 'p' && p[2] == 'o' &&
+                        p[3] == 'r' && p[4] == 't' &&
+                        !lre_js_is_ident_next(p[5])) {
+                        *pp = p + 5;
+                        return TOK_IMPORT;
+                    }
+                } else if (c == 'o' && *p == 'f' && !lre_js_is_ident_next(p[1])) {
+                    return TOK_OF;
+                } else if (c == 'f' && p[0] == 'u' && p[1] == 'n' &&
+                         p[2] == 'c' && p[3] == 't' && p[4] == 'i' &&
+                         p[5] == 'o' && p[6] == 'n' && !lre_js_is_ident_next(p[7])) {
+                    return TOK_FUNCTION;
+                }
+                return TOK_IDENT;
+            }
+            break;
+        }
+        return c;
+    }
+}
+
+static int peek_token(JSParseState *s, BOOL no_line_terminator)
+{
+    const uint8_t *p = s->buf_ptr;
+    return simple_next_token(&p, no_line_terminator);
+}
+
+/* return true if 'input' contains the source of a module
+   (heuristic). 'input' must be a zero terminated.
+
+   Heuristic: skip comments and expect 'import' keyword not followed
+   by '(' or '.'
+*/
+BOOL JS_DetectModule(const char *input, size_t input_len)
+{
+    const uint8_t *p = (const uint8_t *)input;
+    int tok;
+    if (simple_next_token(&p, FALSE) != TOK_IMPORT)
+        return FALSE;
+    tok = simple_next_token(&p, FALSE);
+    return (tok != '.' && tok != '(');
+}
+
+static inline int get_prev_opcode(JSFunctionDef *fd) {
+    if (fd->last_opcode_pos < 0)
+        return OP_invalid;
+    else
+        return fd->byte_code.buf[fd->last_opcode_pos];
+}
+
+static BOOL js_is_live_code(JSParseState *s) {
+    switch (get_prev_opcode(s->cur_func)) {
+    case OP_tail_call:
+    case OP_tail_call_method:
+    case OP_return:
+    case OP_return_undef:
+    case OP_return_async:
+    case OP_throw:
+    case OP_throw_var:
+    case OP_goto:
+#if SHORT_OPCODES
+    case OP_goto8:
+    case OP_goto16:
+#endif
+    case OP_ret:
+        return FALSE;
+    default:
+        return TRUE;
+    }
+}
+
+static void emit_u8(JSParseState *s, uint8_t val)
+{
+    dbuf_putc(&s->cur_func->byte_code, val);
+}
+
+static void emit_u16(JSParseState *s, uint16_t val)
+{
+    dbuf_put_u16(&s->cur_func->byte_code, val);
+}
+
+static void emit_u32(JSParseState *s, uint32_t val)
+{
+    dbuf_put_u32(&s->cur_func->byte_code, val);
+}
+
+static void emit_op(JSParseState *s, uint8_t val)
+{
+    JSFunctionDef *fd = s->cur_func;
+    DynBuf *bc = &fd->byte_code;
+
+    /* Use the line number of the last token used, not the next token,
+       nor the current offset in the source file.
+     */
+    if (unlikely(fd->last_opcode_line_num != s->last_line_num)) {
+        dbuf_putc(bc, OP_line_num);
+        dbuf_put_u32(bc, s->last_line_num);
+        fd->last_opcode_line_num = s->last_line_num;
+    }
+    fd->last_opcode_pos = bc->size;
+    dbuf_putc(bc, val);
+}
+
+static void emit_atom(JSParseState *s, JSAtom name)
+{
+    emit_u32(s, JS_DupAtom(s->ctx, name));
+}
+
+static int update_label(JSFunctionDef *s, int label, int delta)
+{
+    LabelSlot *ls;
+
+    assert(label >= 0 && label < s->label_count);
+    ls = &s->label_slots[label];
+    ls->ref_count += delta;
+    assert(ls->ref_count >= 0);
+    return ls->ref_count;
+}
+
+static int new_label_fd(JSFunctionDef *fd, int label)
+{
+    LabelSlot *ls;
+
+    if (label < 0) {
+        if (fd->label_count >= fd->label_size) {
+            int new_size;
+            size_t slack;
+            LabelSlot *new_tab;
+
+            /* XXX: potential arithmetic overflow */
+            new_size = fd->label_size * 3 / 2 + 4;
+            new_tab = js_realloc2(fd->ctx, fd->label_slots, new_size * sizeof(*new_tab), &slack);
+            if (!new_tab)
+                return -1;
+            new_size += slack / sizeof(*new_tab);
+            fd->label_slots = new_tab;
+            fd->label_size = new_size;
+        }
+        label = fd->label_count++;
+        ls = &fd->label_slots[label];
+        ls->ref_count = 0;
+        ls->pos = -1;
+        ls->pos2 = -1;
+        ls->addr = -1;
+        ls->first_reloc = NULL;
+    }
+    return label;
+}
+
+static int new_label(JSParseState *s)
+{
+    return new_label_fd(s->cur_func, -1);
+}
+
+/* return the label ID offset */
+static int emit_label(JSParseState *s, int label)
+{
+    if (label >= 0) {
+        emit_op(s, OP_label);
+        emit_u32(s, label);
+        s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
+        return s->cur_func->byte_code.size - 4;
+    } else {
+        return -1;
+    }
+}
+
+/* return label or -1 if dead code */
+static int emit_goto(JSParseState *s, int opcode, int label)
+{
+    if (js_is_live_code(s)) {
+        if (label < 0)
+            label = new_label(s);
+        emit_op(s, opcode);
+        emit_u32(s, label);
+        s->cur_func->label_slots[label].ref_count++;
+        return label;
+    }
+    return -1;
+}
+
+/* return the constant pool index. 'val' is not duplicated. */
+static int cpool_add(JSParseState *s, JSValue val)
+{
+    JSFunctionDef *fd = s->cur_func;
+    if (fd->cpool_count >= fd->cpool_size) {
+        int new_size;
+        size_t slack;
+        JSValue *new_tab;
+        /* XXX: potential arithmetic overflow */
+        new_size = max_int(fd->cpool_count + 1, fd->cpool_size * 3 / 2);
+        new_tab = js_realloc2(s->ctx, fd->cpool, new_size * sizeof(JSValue), &slack);
+        if (!new_tab)
+            return -1;
+        new_size += slack / sizeof(*new_tab);
+        fd->cpool = new_tab;
+        fd->cpool_size = new_size;
+    }
+    fd->cpool[fd->cpool_count++] = val;
+    return fd->cpool_count - 1;
+}
+
+static __exception int emit_push_const(JSParseState *s, JSValueConst val,
+                                       BOOL as_atom)
+{
+    int idx;
+
+    if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING && as_atom) {
+        JSAtom atom;
+        /* warning: JS_NewAtomStr frees the string value */
+        JS_DupValue(s->ctx, val);
+        atom = JS_NewAtomStr(s->ctx, JS_VALUE_GET_STRING(val));
+        if (atom != JS_ATOM_NULL && !__JS_AtomIsTaggedInt(atom)) {
+            emit_op(s, OP_push_atom_value);
+            emit_u32(s, atom);
+            return 0;
+        }
+    }
+
+    idx = cpool_add(s, JS_DupValue(s->ctx, val));
+    if (idx < 0)
+        return -1;
+    emit_op(s, OP_push_const);
+    emit_u32(s, idx);
+    return 0;
+}
+
+/* return the variable index or -1 if not found,
+   add ARGUMENT_VAR_OFFSET for argument variables */
+static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
+{
+    int i;
+    for(i = fd->arg_count; i-- > 0;) {
+        if (fd->args[i].var_name == name)
+            return i | ARGUMENT_VAR_OFFSET;
+    }
+    return -1;
+}
+
+static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
+{
+    int i;
+    for(i = fd->var_count; i-- > 0;) {
+        if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0)
+            return i;
+    }
+    return find_arg(ctx, fd, name);
+}
+
+/* return true if scope == parent_scope or if scope is a child of
+   parent_scope */
+static BOOL is_child_scope(JSContext *ctx, JSFunctionDef *fd,
+                           int scope, int parent_scope)
+{
+    while (scope >= 0) {
+        if (scope == parent_scope)
+            return TRUE;
+        scope = fd->scopes[scope].parent;
+    }
+    return FALSE;
+}
+
+/* find a 'var' declaration in the same scope or a child scope */
+static int find_var_in_child_scope(JSContext *ctx, JSFunctionDef *fd,
+                                   JSAtom name, int scope_level)
+{
+    int i;
+    for(i = 0; i < fd->var_count; i++) {
+        JSVarDef *vd = &fd->vars[i];
+        if (vd->var_name == name && vd->scope_level == 0) {
+            if (is_child_scope(ctx, fd, vd->func_pool_or_scope_idx,
+                               scope_level))
+                return i;
+        }
+    }
+    return -1;
+}
+
+
+static JSHoistedDef *find_hoisted_def(JSFunctionDef *fd, JSAtom name)
+{
+    int i;
+    for(i = 0; i < fd->hoisted_def_count; i++) {
+        JSHoistedDef *hf = &fd->hoisted_def[i];
+        if (hf->var_name == name)
+            return hf;
+    }
+    return NULL;
+
+}
+
+static JSHoistedDef *find_lexical_hoisted_def(JSFunctionDef *fd, JSAtom name)
+{
+    JSHoistedDef *hf = find_hoisted_def(fd, name);
+    if (hf && hf->is_lexical)
+        return hf;
+    else
+        return NULL;
+}
+
+static int find_lexical_decl(JSContext *ctx, JSFunctionDef *fd, JSAtom name,
+                             int scope_idx, BOOL check_catch_var)
+{
+    while (scope_idx >= 0) {
+        JSVarDef *vd = &fd->vars[scope_idx];
+        if (vd->var_name == name &&
+            (vd->is_lexical || (vd->var_kind == JS_VAR_CATCH &&
+                                check_catch_var)))
+            return scope_idx;
+        scope_idx = vd->scope_next;
+    }
+
+    if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_GLOBAL) {
+        if (find_lexical_hoisted_def(fd, name))
+            return GLOBAL_VAR_OFFSET;
+    }
+    return -1;
+}
+
+static int push_scope(JSParseState *s) {
+    if (s->cur_func) {
+        JSFunctionDef *fd = s->cur_func;
+        int scope = fd->scope_count;
+        /* XXX: should check for scope overflow */
+        if ((fd->scope_count + 1) > fd->scope_size) {
+            int new_size;
+            size_t slack;
+            JSVarScope *new_buf;
+            /* XXX: potential arithmetic overflow */
+            new_size = max_int(fd->scope_count + 1, fd->scope_size * 3 / 2);
+            if (fd->scopes == fd->def_scope_array) {
+                new_buf = js_realloc2(s->ctx, NULL, new_size * sizeof(*fd->scopes), &slack);
+                if (!new_buf)
+                    return -1;
+                memcpy(new_buf, fd->scopes, fd->scope_count * sizeof(*fd->scopes));
+            } else {
+                new_buf = js_realloc2(s->ctx, fd->scopes, new_size * sizeof(*fd->scopes), &slack);
+                if (!new_buf)
+                    return -1;
+            }
+            new_size += slack / sizeof(*new_buf);
+            fd->scopes = new_buf;
+            fd->scope_size = new_size;
+        }
+        fd->scope_count++;
+        fd->scopes[scope].parent = fd->scope_level;
+        fd->scopes[scope].first = fd->scope_first;
+        emit_op(s, OP_enter_scope);
+        emit_u16(s, scope);
+        return fd->scope_level = scope;
+    }
+    return 0;
+}
+
+static int get_first_lexical_var(JSFunctionDef *fd, int scope)
+{
+    while (scope >= 0) {
+        int scope_idx = fd->scopes[scope].first;
+        if (scope_idx >= 0)
+            return scope_idx;
+        scope = fd->scopes[scope].parent;
+    }
+    return -1;
+}
+
+static void pop_scope(JSParseState *s) {
+    if (s->cur_func) {
+        /* disable scoped variables */
+        JSFunctionDef *fd = s->cur_func;
+        int scope = fd->scope_level;
+        emit_op(s, OP_leave_scope);
+        emit_u16(s, scope);
+        fd->scope_level = fd->scopes[scope].parent;
+        fd->scope_first = get_first_lexical_var(fd, fd->scope_level);
+    }
+}
+
+static void close_scopes(JSParseState *s, int scope, int scope_stop)
+{
+    while (scope > scope_stop) {
+        emit_op(s, OP_leave_scope);
+        emit_u16(s, scope);
+        scope = s->cur_func->scopes[scope].parent;
+    }
+}
+
+/* return the variable index or -1 if error */
+static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
+{
+    JSVarDef *vd;
+
+    /* the local variable indexes are currently stored on 16 bits */
+    if (fd->var_count >= JS_MAX_LOCAL_VARS) {
+        JS_ThrowInternalError(ctx, "too many local variables");
+        return -1;
+    }
+    if ((fd->var_count + 1) > fd->var_size) {
+        int new_size;
+        size_t slack;
+        JSVarDef *new_buf;
+        new_size = max_int(fd->var_count + 1, fd->var_size * 3 / 2);
+        new_buf = js_realloc2(ctx, fd->vars, new_size * sizeof(*fd->vars), &slack);
+        if (!new_buf)
+            return -1;
+        new_size += slack / sizeof(*new_buf);
+        fd->vars = new_buf;
+        fd->var_size = new_size;
+    }
+    vd = &fd->vars[fd->var_count++];
+    memset(vd, 0, sizeof(*vd));
+    vd->var_name = JS_DupAtom(ctx, name);
+    return fd->var_count - 1;
+}
+
+static int add_scope_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name,
+                         JSVarKindEnum var_kind)
+{
+    int idx = add_var(ctx, fd, name);
+    if (idx >= 0) {
+        JSVarDef *vd = &fd->vars[idx];
+        vd->var_kind = var_kind;
+        vd->scope_level = fd->scope_level;
+        vd->scope_next = fd->scope_first;
+        fd->scopes[fd->scope_level].first = idx;
+        fd->scope_first = idx;
+    }
+    return idx;
+}
+
+static int add_func_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
+{
+    int idx = fd->func_var_idx;
+    if (idx < 0 && (idx = add_var(ctx, fd, name)) >= 0) {
+        fd->func_var_idx = idx;
+        fd->vars[idx].is_func_var = TRUE;
+        if (fd->js_mode & JS_MODE_STRICT)
+            fd->vars[idx].is_const = TRUE;
+    }
+    return idx;
+}
+
+static int add_arguments_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
+{
+    int idx = fd->arguments_var_idx;
+    if (idx < 0 && (idx = add_var(ctx, fd, name)) >= 0) {
+        fd->arguments_var_idx = idx;
+    }
+    return idx;
+}
+
+static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
+{
+    JSVarDef *vd;
+
+    /* the local variable indexes are currently stored on 16 bits */
+    if (fd->arg_count >= JS_MAX_LOCAL_VARS) {
+        JS_ThrowInternalError(ctx, "too many arguments");
+        return -1;
+    }
+    if ((fd->arg_count + 1) > fd->arg_size) {
+        int new_size;
+        size_t slack;
+        JSVarDef *new_buf;
+        new_size = max_int(fd->arg_count + 1, fd->arg_size * 3 / 2);
+        new_buf = js_realloc2(ctx, fd->args, new_size * sizeof(*fd->args), &slack);
+        if (!new_buf)
+            return -1;
+        new_size += slack / sizeof(*new_buf);
+        fd->args = new_buf;
+        fd->arg_size = new_size;
+    }
+    vd = &fd->args[fd->arg_count++];
+    memset(vd, 0, sizeof(*vd));
+    vd->var_name = JS_DupAtom(ctx, name);
+    return fd->arg_count - 1;
+}
+
+/* add a Hoisted definition for a function (cpool_idx >= 0) or a
+   global variable (cpool_idx = -1) */
+static JSHoistedDef *add_hoisted_def(JSContext *ctx,
+                                     JSFunctionDef *s, int cpool_idx,
+                                     JSAtom name, int var_idx, BOOL is_lexical)
+{
+    JSHoistedDef *hf;
+
+    if (s->hoisted_def_count >= s->hoisted_def_size) {
+        int new_size;
+        size_t slack;
+        JSHoistedDef *new_tab;
+        new_size = max_int(s->hoisted_def_count + 1,
+                           s->hoisted_def_size * 3 / 2);
+        new_tab = js_realloc2(ctx, s->hoisted_def, new_size * sizeof(s->hoisted_def[0]), &slack);
+        if (!new_tab)
+            return NULL;
+        new_size += slack / sizeof(*new_tab);
+        s->hoisted_def = new_tab;
+        s->hoisted_def_size = new_size;
+    }
+    hf = &s->hoisted_def[s->hoisted_def_count++];
+    hf->cpool_idx = cpool_idx;
+    hf->force_init = 0;
+    hf->is_lexical = is_lexical;
+    hf->is_const = FALSE;
+    hf->var_idx = var_idx;
+    hf->scope_level = s->scope_level;
+    hf->var_name = JS_ATOM_NULL;
+    if (name != JS_ATOM_NULL) {
+        hf->var_name = JS_DupAtom(ctx, name);
+    }
+    return hf;
+}
+
+typedef enum {
+    JS_VAR_DEF_WITH,
+    JS_VAR_DEF_LET,
+    JS_VAR_DEF_CONST,
+    JS_VAR_DEF_FUNCTION_DECL, /* function declaration */
+    JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */
+    JS_VAR_DEF_CATCH,
+    JS_VAR_DEF_VAR,
+} JSVarDefEnum;
+
+static int define_var(JSParseState *s, JSFunctionDef *fd, JSAtom name,
+                      JSVarDefEnum var_def_type)
+{
+    JSContext *ctx = s->ctx;
+    JSVarDef *vd;
+    int idx;
+
+    switch (var_def_type) {
+    case JS_VAR_DEF_WITH:
+        idx = add_scope_var(ctx, fd, name, JS_VAR_NORMAL);
+        break;
+
+    case JS_VAR_DEF_LET:
+    case JS_VAR_DEF_CONST:
+    case JS_VAR_DEF_FUNCTION_DECL:
+    case JS_VAR_DEF_NEW_FUNCTION_DECL:
+        idx = find_lexical_decl(ctx, fd, name, fd->scope_first, TRUE);
+        if (idx >= 0) {
+            if (idx < GLOBAL_VAR_OFFSET) {
+                if (fd->vars[idx].scope_level == fd->scope_level) {
+                    /* same scope: in non strict mode, functions
+                       can be redefined (annex B.3.3.4). */
+                    if (!(!(fd->js_mode & JS_MODE_STRICT) &&
+                          var_def_type == JS_VAR_DEF_FUNCTION_DECL &&
+                          fd->vars[idx].var_kind == JS_VAR_FUNCTION_DECL)) {
+                        goto redef_lex_error;
+                    }
+                } else if (fd->vars[idx].var_kind == JS_VAR_CATCH && (fd->vars[idx].scope_level + 2) == fd->scope_level) {
+                    goto redef_lex_error;
+                }
+            } else {
+                if (fd->scope_level == 1) {
+                redef_lex_error:
+                    /* redefining a scoped var in the same scope: error */
+                    return js_parse_error(s, "invalid redefinition of lexical identifier");
+                }
+            }
+        }
+        if (var_def_type != JS_VAR_DEF_FUNCTION_DECL &&
+            var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL &&
+            (fd->func_kind == JS_FUNC_ASYNC ||
+             fd->func_kind == JS_FUNC_GENERATOR ||
+             fd->func_kind == JS_FUNC_ASYNC_GENERATOR ||
+             fd->func_type == JS_PARSE_FUNC_METHOD ||
+             fd->scope_level == 1) &&
+            find_arg(ctx, fd, name) >= 0) {
+            /* lexical variable redefines a parameter name */
+            return js_parse_error(s, "invalid redefinition of parameter name");
+        }
+
+        if (find_var_in_child_scope(ctx, fd, name, fd->scope_level) >= 0) {
+            return js_parse_error(s, "invalid redefinition of a variable");
+        }
+        
+        if (fd->is_global_var) {
+            JSHoistedDef *hf;
+            hf = find_hoisted_def(fd, name);
+            if (hf && is_child_scope(ctx, fd, hf->scope_level,
+                                     fd->scope_level)) {
+                return js_parse_error(s, "invalid redefinition of global identifier");
+            }
+        }
+        
+        if (fd->is_eval &&
+            (fd->eval_type == JS_EVAL_TYPE_GLOBAL ||
+             fd->eval_type == JS_EVAL_TYPE_MODULE) &&
+            fd->scope_level == 1) {
+            JSHoistedDef *hf;
+            hf = add_hoisted_def(s->ctx, fd, -1, name, -1, TRUE);
+            if (!hf)
+                return -1;
+            hf->is_const = (var_def_type == JS_VAR_DEF_CONST);
+            idx = GLOBAL_VAR_OFFSET;
+        } else {
+            JSVarKindEnum var_kind;
+            if (var_def_type == JS_VAR_DEF_FUNCTION_DECL)
+                var_kind = JS_VAR_FUNCTION_DECL;
+            else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL)
+                var_kind = JS_VAR_NEW_FUNCTION_DECL;
+            else
+                var_kind = JS_VAR_NORMAL;
+            idx = add_scope_var(ctx, fd, name, var_kind);
+            if (idx >= 0) {
+                vd = &fd->vars[idx];
+                vd->is_lexical = 1;
+                vd->is_const = (var_def_type == JS_VAR_DEF_CONST);
+            }
+        }
+        break;
+
+    case JS_VAR_DEF_CATCH:
+        idx = add_scope_var(ctx, fd, name, JS_VAR_CATCH);
+        break;
+
+    case JS_VAR_DEF_VAR:
+        if (find_lexical_decl(ctx, fd, name, fd->scope_first,
+                              FALSE) >= 0) {
+       invalid_lexical_redefinition:
+            /* error to redefine a var that inside a lexical scope */
+            return js_parse_error(s, "invalid redefinition of lexical identifier");
+        }
+        if (fd->is_global_var) {
+            JSHoistedDef *hf;
+            hf = find_hoisted_def(fd, name);
+            if (hf && hf->is_lexical && hf->scope_level == fd->scope_level &&
+                fd->eval_type == JS_EVAL_TYPE_MODULE) {
+                goto invalid_lexical_redefinition;
+            }
+            hf = add_hoisted_def(s->ctx, fd, -1, name, -1, FALSE);
+            if (!hf)
+                return -1;
+            idx = GLOBAL_VAR_OFFSET;
+        } else {
+            /* if the variable already exists, don't add it again  */
+            idx = find_var(ctx, fd, name);
+            if (idx >= 0)
+                break;
+            idx = add_var(ctx, fd, name);
+            if (idx >= 0) {
+                if (name == JS_ATOM_arguments && fd->has_arguments_binding)
+                    fd->arguments_var_idx = idx;
+                fd->vars[idx].func_pool_or_scope_idx = fd->scope_level;
+            }
+        }
+        break;
+    default:
+        abort();
+    }
+    return idx;
+}
+
+/* add a private field variable in the current scope */
+static int add_private_class_field(JSParseState *s, JSFunctionDef *fd,
+                                   JSAtom name, JSVarKindEnum var_kind)
+{
+    JSContext *ctx = s->ctx;
+    JSVarDef *vd;
+    int idx;
+
+    idx = add_scope_var(ctx, fd, name, var_kind);
+    if (idx < 0)
+        return idx;
+    vd = &fd->vars[idx];
+    vd->is_lexical = 1;
+    vd->is_const = 1;
+    return idx;
+}
+
+static __exception int js_parse_expr(JSParseState *s);
+static __exception int js_parse_function_decl(JSParseState *s,
+                                              JSParseFunctionEnum func_type,
+                                              JSFunctionKindEnum func_kind,
+                                              JSAtom func_name, const uint8_t *ptr,
+                                              int start_line);
+static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s);
+static __exception int js_parse_function_decl2(JSParseState *s,
+                                               JSParseFunctionEnum func_type,
+                                               JSFunctionKindEnum func_kind,
+                                               JSAtom func_name,
+                                               const uint8_t *ptr,
+                                               int function_line_num,
+                                               JSParseExportEnum export_flag,
+                                               JSFunctionDef **pfd);
+static __exception int js_parse_assign_expr(JSParseState *s, int in_accepted);
+static __exception int js_parse_unary(JSParseState *s, int exponentiation_flag);
+static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
+                             JSAtom label_name,
+                             int label_break, int label_cont,
+                             int drop_count);
+static void pop_break_entry(JSFunctionDef *fd);
+static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m,
+                                       JSAtom local_name, JSAtom export_name,
+                                       JSExportTypeEnum export_type);
+
+/* Note: all the fields are already sealed except length */
+static int seal_template_obj(JSContext *ctx, JSValueConst obj)
+{
+    JSObject *p;
+    JSShapeProperty *prs;
+
+    p = JS_VALUE_GET_OBJ(obj);
+    prs = find_own_property1(p, JS_ATOM_length);
+    if (prs) {
+        if (js_update_property_flags(ctx, p, &prs,
+                                     prs->flags & ~(JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)))
+            return -1;
+    }
+    p->extensible = FALSE;
+    return 0;
+}
+
+static __exception int js_parse_template(JSParseState *s, int call, int *argc)
+{
+    JSContext *ctx = s->ctx;
+    JSValue raw_array, template_object;
+    JSToken cooked;
+    int depth, ret;
+
+    raw_array = JS_UNDEFINED; /* avoid warning */
+    template_object = JS_UNDEFINED; /* avoid warning */
+    if (call) {
+        /* Create a template object: an array of cooked strings */
+        /* Create an array of raw strings and store it to the raw property */
+        template_object = JS_NewArray(ctx);
+        if (JS_IsException(template_object))
+            return -1;
+        //        pool_idx = s->cur_func->cpool_count;
+        ret = emit_push_const(s, template_object, 0);
+        JS_FreeValue(ctx, template_object);
+        if (ret)
+            return -1;
+        raw_array = JS_NewArray(ctx);
+        if (JS_IsException(raw_array))
+            return -1;
+        if (JS_DefinePropertyValue(ctx, template_object, JS_ATOM_raw,
+                                   raw_array, JS_PROP_THROW) < 0) {
+            return -1;
+        }
+    }
+
+    depth = 0;
+    while (s->token.val == TOK_TEMPLATE) {
+        const uint8_t *p = s->token.ptr + 1;
+        cooked = s->token;
+        if (call) {
+            if (JS_DefinePropertyValueUint32(ctx, raw_array, depth,
+                                             JS_DupValue(ctx, s->token.u.str.str),
+                                             JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) {
+                return -1;
+            }
+            /* re-parse the string with escape sequences but do not throw a
+               syntax error if it contains invalid sequences
+             */
+            if (js_parse_string(s, '`', FALSE, p, &cooked, &p)) {
+                cooked.u.str.str = JS_UNDEFINED;
+            }
+            if (JS_DefinePropertyValueUint32(ctx, template_object, depth,
+                                             cooked.u.str.str,
+                                             JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) {
+                return -1;
+            }
+        } else {
+            JSString *str;
+            /* re-parse the string with escape sequences and throw a
+               syntax error if it contains invalid sequences
+             */
+            JS_FreeValue(ctx, s->token.u.str.str);
+            s->token.u.str.str = JS_UNDEFINED;
+            if (js_parse_string(s, '`', TRUE, p, &cooked, &p))
+                return -1;
+            str = JS_VALUE_GET_STRING(cooked.u.str.str);
+            if (str->len != 0 || depth == 0) {
+                ret = emit_push_const(s, cooked.u.str.str, 1);
+                JS_FreeValue(s->ctx, cooked.u.str.str);
+                if (ret)
+                    return -1;
+                if (depth == 0) {
+                    if (s->token.u.str.sep == '`')
+                        goto done1;
+                    emit_op(s, OP_get_field2);
+                    emit_atom(s, JS_ATOM_concat);
+                }
+                depth++;
+            } else {
+                JS_FreeValue(s->ctx, cooked.u.str.str);
+            }
+        }
+        if (s->token.u.str.sep == '`')
+            goto done;
+        if (next_token(s))
+            return -1;
+        if (js_parse_assign_expr(s, TRUE))
+            return -1;
+        depth++;
+        if (s->token.val != '}') {
+            return js_parse_error(s, "expected '}' after template expression");
+        }
+        /* XXX: should convert to string at this stage? */
+        free_token(s, &s->token);
+        /* Resume TOK_TEMPLATE parsing (s->token.line_num and s->token.ptr are OK) */
+        s->got_lf = FALSE;
+        s->last_line_num = s->token.line_num;
+        if (js_parse_template_part(s, s->buf_ptr))
+            return -1;
+    }
+    return js_parse_expect(s, TOK_TEMPLATE);
+
+ done:
+    if (call) {
+        /* Seal the objects */
+        seal_template_obj(ctx, raw_array);
+        seal_template_obj(ctx, template_object);
+        *argc = depth + 1;
+    } else {
+        emit_op(s, OP_call_method);
+        emit_u16(s, depth - 1);
+    }
+ done1:
+    return next_token(s);
+}
+
+
+#define PROP_TYPE_IDENT 0
+#define PROP_TYPE_VAR   1
+#define PROP_TYPE_GET   2
+#define PROP_TYPE_SET   3
+#define PROP_TYPE_STAR  4
+#define PROP_TYPE_ASYNC 5
+#define PROP_TYPE_ASYNC_STAR 6
+
+#define PROP_TYPE_PRIVATE (1 << 4)
+
+static BOOL token_is_ident(int tok)
+{
+    /* Accept keywords and reserved words as property names */
+    return (tok == TOK_IDENT ||
+            (tok >= TOK_FIRST_KEYWORD &&
+             tok <= TOK_LAST_KEYWORD));
+}
+
+/* if the property is an expression, name = JS_ATOM_NULL */
+static int __exception js_parse_property_name(JSParseState *s,
+                                              JSAtom *pname,
+                                              BOOL allow_method, BOOL allow_var,
+                                              BOOL allow_private)
+{
+    int is_private = 0;
+    BOOL is_non_reserved_ident;
+    JSAtom name;
+    int prop_type;
+    
+    prop_type = PROP_TYPE_IDENT;
+    if (allow_method) {
+        if (token_is_pseudo_keyword(s, JS_ATOM_get)
+        ||  token_is_pseudo_keyword(s, JS_ATOM_set)) {
+            /* get x(), set x() */
+            name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
+            if (next_token(s))
+                goto fail1;
+            if (s->token.val == ':' || s->token.val == ',' ||
+                s->token.val == '}' || s->token.val == '(') {
+                is_non_reserved_ident = TRUE;
+                goto ident_found;
+            }
+            prop_type = PROP_TYPE_GET + (name == JS_ATOM_set);
+            JS_FreeAtom(s->ctx, name);
+        } else if (s->token.val == '*') {
+            if (next_token(s))
+                goto fail;
+            prop_type = PROP_TYPE_STAR;
+        } else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
+                   peek_token(s, TRUE) != '\n') {
+            name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
+            if (next_token(s))
+                goto fail1;
+            if (s->token.val == ':' || s->token.val == ',' ||
+                s->token.val == '}' || s->token.val == '(') {
+                is_non_reserved_ident = TRUE;
+                goto ident_found;
+            }
+            JS_FreeAtom(s->ctx, name);
+            if (s->token.val == '*') {
+                if (next_token(s))
+                    goto fail;
+                prop_type = PROP_TYPE_ASYNC_STAR;
+            } else {
+                prop_type = PROP_TYPE_ASYNC;
+            }
+        }
+    }
+
+    if (token_is_ident(s->token.val)) {
+        /* variable can only be a non-reserved identifier */
+        is_non_reserved_ident =
+            (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved);
+        /* keywords and reserved words have a valid atom */
+        name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
+        if (next_token(s))
+            goto fail1;
+    ident_found:
+        if (is_non_reserved_ident &&
+            prop_type == PROP_TYPE_IDENT && allow_var) {
+            if (!(s->token.val == ':' ||
+                  (s->token.val == '(' && allow_method))) {
+                prop_type = PROP_TYPE_VAR;
+            }
+        }
+    } else if (s->token.val == TOK_STRING) {
+        name = JS_ValueToAtom(s->ctx, s->token.u.str.str);
+        if (name == JS_ATOM_NULL)
+            goto fail;
+        if (next_token(s))
+            goto fail1;
+    } else if (s->token.val == TOK_NUMBER) {
+        JSValue val;
+        val = s->token.u.num.val;
+#ifdef CONFIG_BIGNUM
+        if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            double d;
+            d = bf_mul_pow10_to_float64(s->ctx, &p->num,
+                                        s->token.u.num.exponent);
+            val = __JS_NewFloat64(s->ctx, d);
+            name = JS_ValueToAtom(s->ctx, val);
+            JS_FreeValue(s->ctx, val);
+        } else
+#endif
+        {
+            name = JS_ValueToAtom(s->ctx, val);
+        }
+        if (name == JS_ATOM_NULL)
+            goto fail;
+        if (next_token(s))
+            goto fail1;
+    } else if (s->token.val == '[') {
+        if (next_token(s))
+            goto fail;
+        if (js_parse_expr(s))
+            goto fail;
+        if (js_parse_expect(s, ']'))
+            goto fail;
+        name = JS_ATOM_NULL;
+    } else if (s->token.val == TOK_PRIVATE_NAME && allow_private) {
+        name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
+        if (next_token(s))
+            goto fail1;
+        is_private = PROP_TYPE_PRIVATE;
+    } else {
+        goto invalid_prop;
+    }
+    if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR &&
+        s->token.val != '(') {
+        JS_FreeAtom(s->ctx, name);
+    invalid_prop:
+        js_parse_error(s, "invalid property name");
+        goto fail;
+    }
+    *pname = name;
+    return prop_type | is_private;
+ fail1:
+    JS_FreeAtom(s->ctx, name);
+ fail:
+    *pname = JS_ATOM_NULL;
+    return -1;
+}
+
+typedef struct JSParsePos {
+    int last_line_num;
+    int line_num;
+    BOOL got_lf;
+    const uint8_t *ptr;
+} JSParsePos;
+
+static int js_parse_get_pos(JSParseState *s, JSParsePos *sp)
+{
+    sp->last_line_num = s->last_line_num;
+    sp->line_num = s->token.line_num;
+    sp->ptr = s->token.ptr;
+    sp->got_lf = s->got_lf;
+    return 0;
+}
+
+static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp)
+{
+    s->token.line_num = sp->last_line_num;
+    s->line_num = sp->line_num;
+    s->buf_ptr = sp->ptr;
+    s->got_lf = sp->got_lf;
+    return next_token(s);
+}
+
+/* return TRUE if a regexp literal is allowed after this token */
+static BOOL is_regexp_allowed(int tok)
+{
+    switch (tok) {
+    case TOK_NUMBER:
+    case TOK_STRING:
+    case TOK_REGEXP:
+    case TOK_DEC:
+    case TOK_INC:
+    case TOK_NULL:
+    case TOK_FALSE:
+    case TOK_TRUE:
+    case TOK_THIS:
+    case ')':
+    case ']':
+    case '}': /* XXX: regexp may occur after */
+    case TOK_IDENT:
+        return FALSE;
+    default:
+        return TRUE;
+    }
+}
+
+#define SKIP_HAS_SEMI      (1 << 0)
+#define SKIP_HAS_ELLIPSIS  (1 << 1)
+
+/* XXX: improve speed with early bailout */
+/* XXX: no longer works if regexps are present. Could use previous
+   regexp parsing heuristics to handle most cases */
+static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_terminator)
+{
+    char state[256];
+    size_t level = 0;
+    JSParsePos pos;
+    int last_tok, tok = TOK_EOF;
+    int tok_len, bits = 0;
+
+    /* protect from underflow */
+    state[level++] = 0;
+
+    js_parse_get_pos(s, &pos);
+    last_tok = 0;
+    for (;;) {
+        switch(s->token.val) {
+        case '(':
+        case '[':
+        case '{':
+            if (level >= sizeof(state))
+                goto done;
+            state[level++] = s->token.val;
+            break;
+        case ')':
+            if (state[--level] != '(')
+                goto done;
+            break;
+        case ']':
+            if (state[--level] != '[')
+                goto done;
+            break;
+        case '}':
+            if (state[--level] != '{')
+                goto done;
+            break;
+        case TOK_EOF:
+            goto done;
+        case ';':
+            if (level == 2) {
+                bits |= SKIP_HAS_SEMI;
+            }
+            break;
+        case TOK_ELLIPSIS:
+            if (level == 2) {
+                bits |= SKIP_HAS_ELLIPSIS;
+            }
+            break;
+
+        case TOK_DIV_ASSIGN:
+            tok_len = 2;
+            goto parse_regexp;
+        case '/':
+            tok_len = 1;
+        parse_regexp:
+            if (is_regexp_allowed(last_tok)) {
+                s->buf_ptr -= tok_len;
+                if (js_parse_regexp(s)) {
+                    /* XXX: should clear the exception */
+                    goto done;
+                }
+            }
+            break;
+        }
+        /* last_tok is only used to recognize regexps */
+        if (s->token.val == TOK_IDENT &&
+            (token_is_pseudo_keyword(s, JS_ATOM_of) ||
+             token_is_pseudo_keyword(s, JS_ATOM_yield))) {
+            last_tok = TOK_OF;
+        } else {
+            last_tok = s->token.val;
+        }
+        if (next_token(s)) {
+            /* XXX: should clear the exception generated by next_token() */
+            break;
+        }
+        if (level <= 1) {
+            tok = s->token.val;
+            if (token_is_pseudo_keyword(s, JS_ATOM_of))
+                tok = TOK_OF;
+            if (no_line_terminator && s->last_line_num != s->token.line_num)
+                tok = '\n';
+            break;
+        }
+    }
+ done:
+    if (pbits) {
+        *pbits = bits;
+    }
+    if (js_parse_seek_token(s, &pos))
+        return -1;
+    return tok;
+}
+
+static void set_object_name(JSParseState *s, JSAtom name)
+{
+    JSFunctionDef *fd = s->cur_func;
+    int opcode;
+
+    opcode = get_prev_opcode(fd);
+    if (opcode == OP_set_name) {
+        /* XXX: should free atom after OP_set_name? */
+        fd->byte_code.size = fd->last_opcode_pos;
+        fd->last_opcode_pos = -1;
+        emit_op(s, OP_set_name);
+        emit_atom(s, name);
+    } else if (opcode == OP_set_class_name) {
+        int define_class_pos;
+        JSAtom atom;
+        define_class_pos = fd->last_opcode_pos + 1 -
+            get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+        assert(fd->byte_code.buf[define_class_pos] == OP_define_class);
+        /* for consistency we free the previous atom which is
+           JS_ATOM_empty_string */
+        atom = get_u32(fd->byte_code.buf + define_class_pos + 1);
+        JS_FreeAtom(s->ctx, atom);
+        put_u32(fd->byte_code.buf + define_class_pos + 1,
+                JS_DupAtom(s->ctx, name));
+        fd->last_opcode_pos = -1;
+    }
+}
+
+static void set_object_name_computed(JSParseState *s)
+{
+    JSFunctionDef *fd = s->cur_func;
+    int opcode;
+
+    opcode = get_prev_opcode(fd);
+    if (opcode == OP_set_name) {
+        /* XXX: should free atom after OP_set_name? */
+        fd->byte_code.size = fd->last_opcode_pos;
+        fd->last_opcode_pos = -1;
+        emit_op(s, OP_set_name_computed);
+    } else if (opcode == OP_set_class_name) {
+        int define_class_pos;
+        define_class_pos = fd->last_opcode_pos + 1 -
+            get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+        assert(fd->byte_code.buf[define_class_pos] == OP_define_class);
+        fd->byte_code.buf[define_class_pos] = OP_define_class_computed;
+        fd->last_opcode_pos = -1;
+    }
+}
+
+static __exception int js_parse_object_literal(JSParseState *s)
+{
+    JSAtom name = JS_ATOM_NULL;
+    const uint8_t *start_ptr;
+    int start_line, prop_type;
+    BOOL has_proto;
+
+    if (next_token(s))
+        goto fail;
+    /* XXX: add an initial length that will be patched back */
+    emit_op(s, OP_object);
+    has_proto = FALSE;
+    while (s->token.val != '}') {
+        /* specific case for getter/setter */
+        start_ptr = s->token.ptr;
+        start_line = s->token.line_num;
+
+        if (s->token.val == TOK_ELLIPSIS) {
+            if (next_token(s))
+                return -1;
+            if (js_parse_assign_expr(s, TRUE))
+                return -1;
+            emit_op(s, OP_null);  /* dummy excludeList */
+            emit_op(s, OP_copy_data_properties);
+            emit_u8(s, 2 | (1 << 2) | (0 << 5));
+            emit_op(s, OP_drop); /* pop excludeList */
+            emit_op(s, OP_drop); /* pop src object */
+            goto next;
+        }
+
+        prop_type = js_parse_property_name(s, &name, TRUE, TRUE, FALSE);
+        if (prop_type < 0)
+            goto fail;
+
+        if (prop_type == PROP_TYPE_VAR) {
+            /* shortcut for x: x */
+            emit_op(s, OP_scope_get_var);
+            emit_atom(s, name);
+            emit_u16(s, s->cur_func->scope_level);
+            emit_op(s, OP_define_field);
+            emit_atom(s, name);
+        } else if (s->token.val == '(') {
+            BOOL is_getset = (prop_type == PROP_TYPE_GET ||
+                              prop_type == PROP_TYPE_SET);
+            JSParseFunctionEnum func_type;
+            JSFunctionKindEnum func_kind;
+            int op_flags;
+
+            func_kind = JS_FUNC_NORMAL;
+            if (is_getset) {
+                func_type = JS_PARSE_FUNC_GETTER + prop_type - PROP_TYPE_GET;
+            } else {
+                func_type = JS_PARSE_FUNC_METHOD;
+                if (prop_type == PROP_TYPE_STAR)
+                    func_kind = JS_FUNC_GENERATOR;
+                else if (prop_type == PROP_TYPE_ASYNC)
+                    func_kind = JS_FUNC_ASYNC;
+                else if (prop_type == PROP_TYPE_ASYNC_STAR)
+                    func_kind = JS_FUNC_ASYNC_GENERATOR;
+            }
+            if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL,
+                                       start_ptr, start_line))
+                goto fail;
+            if (name == JS_ATOM_NULL) {
+                emit_op(s, OP_define_method_computed);
+            } else {
+                emit_op(s, OP_define_method);
+                emit_atom(s, name);
+            }
+            if (is_getset) {
+                op_flags = OP_DEFINE_METHOD_GETTER +
+                    prop_type - PROP_TYPE_GET;
+            } else {
+                op_flags = OP_DEFINE_METHOD_METHOD;
+            }
+            emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE);
+        } else {
+            if (js_parse_expect(s, ':'))
+                goto fail;
+            if (js_parse_assign_expr(s, TRUE))
+                goto fail;
+            if (name == JS_ATOM_NULL) {
+                set_object_name_computed(s);
+                emit_op(s, OP_define_array_el);
+                emit_op(s, OP_drop);
+            } else if (name == JS_ATOM___proto__) {
+                if (has_proto) {
+                    js_parse_error(s, "duplicate __proto__ property name");
+                    goto fail;
+                }
+                emit_op(s, OP_set_proto);
+                has_proto = TRUE;
+            } else {
+                set_object_name(s, name);
+                emit_op(s, OP_define_field);
+                emit_atom(s, name);
+            }
+        }
+        JS_FreeAtom(s->ctx, name);
+    next:
+        name = JS_ATOM_NULL;
+        if (s->token.val != ',')
+            break;
+        if (next_token(s))
+            goto fail;
+    }
+    if (js_parse_expect(s, '}'))
+        goto fail;
+    return 0;
+ fail:
+    JS_FreeAtom(s->ctx, name);
+    return -1;
+}
+
+static __exception int js_parse_postfix_expr(JSParseState *s,
+                                             BOOL accept_lparen);
+
+/* XXX: is there is nicer solution ? */
+static __exception int js_parse_class_default_ctor(JSParseState *s,
+                                                   BOOL has_super,
+                                                   JSFunctionDef **pfd)
+{
+    JSParsePos pos;
+    const char *str;
+    int ret, line_num;
+    JSParseFunctionEnum func_type;
+    const uint8_t *saved_buf_end;
+    
+    js_parse_get_pos(s, &pos);
+    if (has_super) {
+        str = "(...a){super(...a);}";
+        func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR;
+    } else {
+        str = "(){}";
+        func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
+    }
+    line_num = s->token.line_num;
+    saved_buf_end = s->buf_end;
+    s->buf_ptr = (uint8_t *)str;
+    s->buf_end = (uint8_t *)(str + strlen(str));
+    ret = next_token(s);
+    if (!ret) {
+        ret = js_parse_function_decl2(s, func_type, JS_FUNC_NORMAL,
+                                      JS_ATOM_NULL, (uint8_t *)str,
+                                      line_num, JS_PARSE_EXPORT_NONE, pfd);
+    }
+    s->buf_end = saved_buf_end;
+    ret |= js_parse_seek_token(s, &pos);
+    return ret;
+}
+
+/* find field in the current scope */
+static int find_private_class_field(JSContext *ctx, JSFunctionDef *fd,
+                                    JSAtom name, int scope_level)
+{
+    int idx;
+    idx = fd->scopes[scope_level].first;
+    while (idx != -1) {
+        if (fd->vars[idx].scope_level != scope_level)
+            break;
+        if (fd->vars[idx].var_name == name)
+            return idx;
+        idx = fd->vars[idx].scope_next;
+    }
+    return -1;
+}
+
+/* initialize the class fields, called by the constructor. Note:
+   super() can be called in an arrow function, so <this> and
+   <class_fields_init> can be variable references */
+static void emit_class_field_init(JSParseState *s)
+{
+    int label_next;
+    
+    emit_op(s, OP_scope_get_var);
+    emit_atom(s, JS_ATOM_class_fields_init);
+    emit_u16(s, s->cur_func->scope_level);
+
+    /* no need to call the class field initializer if not defined */
+    emit_op(s, OP_dup);
+    label_next = emit_goto(s, OP_if_false, -1);
+    
+    emit_op(s, OP_scope_get_var);
+    emit_atom(s, JS_ATOM_this);
+    emit_u16(s, 0);
+    
+    emit_op(s, OP_swap);
+    
+    emit_op(s, OP_call_method);
+    emit_u16(s, 0);
+
+    emit_label(s, label_next);
+    emit_op(s, OP_drop);
+}
+
+/* build a private setter function name from the private getter name */
+static JSAtom get_private_setter_name(JSContext *ctx, JSAtom name)
+{
+    return js_atom_concat_str(ctx, name, "<set>");
+}
+
+typedef struct {
+    JSFunctionDef *fields_init_fd;
+    int computed_fields_count;
+    BOOL has_brand;
+    int brand_push_pos;
+} ClassFieldsDef;
+
+static __exception int emit_class_init_start(JSParseState *s,
+                                             ClassFieldsDef *cf)
+{
+    int label_add_brand;
+    
+    cf->fields_init_fd = js_parse_function_class_fields_init(s);
+    if (!cf->fields_init_fd)
+        return -1;
+
+    s->cur_func = cf->fields_init_fd;
+    
+    /* XXX: would be better to add the code only if needed, maybe in a
+       later pass */
+    emit_op(s, OP_push_false); /* will be patched later */
+    cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos;
+    label_add_brand = emit_goto(s, OP_if_false, -1);
+    
+    emit_op(s, OP_scope_get_var);
+    emit_atom(s, JS_ATOM_this);
+    emit_u16(s, 0);
+    
+    emit_op(s, OP_scope_get_var);
+    emit_atom(s, JS_ATOM_home_object);
+    emit_u16(s, 0);
+    
+    emit_op(s, OP_add_brand);
+    
+    emit_label(s, label_add_brand);
+
+    s->cur_func = s->cur_func->parent;
+    return 0;
+}
+
+static __exception int add_brand(JSParseState *s, ClassFieldsDef *cf)
+{
+    if (!cf->has_brand) {
+        /* define the brand field in 'this' of the initializer */
+        if (!cf->fields_init_fd) {
+            if (emit_class_init_start(s, cf))
+                return -1;
+        }
+        /* patch the start of the function to enable the OP_add_brand code */
+        cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true;
+        
+        cf->has_brand = TRUE;
+    }
+    return 0;
+}
+
+static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf)
+{
+    int cpool_idx;
+        
+    s->cur_func = cf->fields_init_fd;
+    emit_op(s, OP_return_undef);
+    s->cur_func = s->cur_func->parent;
+    
+    cpool_idx = cpool_add(s, JS_NULL);
+    cf->fields_init_fd->parent_cpool_idx = cpool_idx;
+    emit_op(s, OP_fclosure);
+    emit_u32(s, cpool_idx);
+    emit_op(s, OP_set_home_object);
+}
+
+
+static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
+                                      JSParseExportEnum export_flag)
+{
+    JSContext *ctx = s->ctx;
+    JSFunctionDef *fd = s->cur_func;
+    JSAtom name = JS_ATOM_NULL, class_name = JS_ATOM_NULL, class_name1;
+    JSAtom class_var_name = JS_ATOM_NULL;
+    JSFunctionDef *method_fd, *ctor_fd;
+    int saved_js_mode, class_name_var_idx, prop_type, ctor_cpool_offset;
+    int class_flags = 0, i, define_class_offset;
+    BOOL is_static, is_private;
+    const uint8_t *class_start_ptr = s->token.ptr;
+    const uint8_t *start_ptr;
+    ClassFieldsDef class_fields[2];
+        
+    /* classes are parsed and executed in strict mode */
+    saved_js_mode = fd->js_mode;
+    fd->js_mode |= JS_MODE_STRICT;
+    if (next_token(s))
+        goto fail;
+    if (s->token.val == TOK_IDENT) {
+        if (s->token.u.ident.is_reserved) {
+            js_parse_error_reserved_identifier(s);
+            goto fail;
+        }
+        class_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+        if (next_token(s))
+            goto fail;
+    } else if (!is_class_expr && export_flag != JS_PARSE_EXPORT_DEFAULT) {
+        js_parse_error(s, "class statement requires a name");
+        goto fail;
+    }
+    if (!is_class_expr) {
+        if (class_name == JS_ATOM_NULL)
+            class_var_name = JS_ATOM__default_; /* export default */
+        else
+            class_var_name = class_name;
+        class_var_name = JS_DupAtom(ctx, class_var_name);
+    }
+
+    push_scope(s);
+
+    if (s->token.val == TOK_EXTENDS) {
+        class_flags = JS_DEFINE_CLASS_HAS_HERITAGE;
+        if (next_token(s))
+            goto fail;
+        /* XXX: the grammar only allows LeftHandSideExpression */
+        if (js_parse_postfix_expr(s, TRUE))
+            goto fail;
+    } else {
+        emit_op(s, OP_undefined);
+    }
+
+    /* add a 'const' definition for the class name */
+    if (class_name != JS_ATOM_NULL) {
+        class_name_var_idx = define_var(s, fd, class_name, JS_VAR_DEF_CONST);
+        if (class_name_var_idx < 0)
+            goto fail;
+    }
+
+    if (js_parse_expect(s, '{'))
+        goto fail;
+
+    /* this scope contains the private fields */
+    push_scope(s);
+
+    emit_op(s, OP_push_const);
+    ctor_cpool_offset = fd->byte_code.size;
+    emit_u32(s, 0); /* will be patched at the end of the class parsing */
+
+    if (class_name == JS_ATOM_NULL) {
+        if (class_var_name != JS_ATOM_NULL)
+            class_name1 = JS_ATOM_default;
+        else
+            class_name1 = JS_ATOM_empty_string;
+    } else {
+        class_name1 = class_name;
+    }
+    
+    emit_op(s, OP_define_class);
+    emit_atom(s, class_name1);
+    emit_u8(s, class_flags);
+    define_class_offset = fd->last_opcode_pos;
+
+    for(i = 0; i < 2; i++) {
+        ClassFieldsDef *cf = &class_fields[i];
+        cf->fields_init_fd = NULL;
+        cf->computed_fields_count = 0;
+        cf->has_brand = FALSE;
+    }
+    
+    ctor_fd = NULL;
+    while (s->token.val != '}') {
+        if (s->token.val == ';') {
+            if (next_token(s))
+                goto fail;
+            continue;
+        }
+        is_static = (s->token.val == TOK_STATIC);
+        if (is_static) {
+            if (next_token(s))
+                goto fail;
+        }
+        if (is_static)
+            emit_op(s, OP_swap);
+        start_ptr = s->token.ptr;
+        prop_type = js_parse_property_name(s, &name, TRUE, FALSE, TRUE);
+        if (prop_type < 0)
+            goto fail;
+        is_private = prop_type & PROP_TYPE_PRIVATE;
+        prop_type &= ~PROP_TYPE_PRIVATE;
+        
+        if ((name == JS_ATOM_constructor && !is_static &&
+             prop_type != PROP_TYPE_IDENT) ||
+            (name == JS_ATOM_prototype && is_static) ||
+            name == JS_ATOM_hash_constructor) {
+            js_parse_error(s, "invalid method name");
+            goto fail;
+        }
+        if (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET) {
+            BOOL is_set = prop_type - PROP_TYPE_GET;
+            JSFunctionDef *method_fd;
+
+            if (is_private) {
+                int idx, var_kind;
+                idx = find_private_class_field(ctx, fd, name, fd->scope_level);
+                if (idx >= 0) {
+                    var_kind = fd->vars[idx].var_kind;
+                    if (var_kind == JS_VAR_PRIVATE_FIELD ||
+                        var_kind == JS_VAR_PRIVATE_METHOD ||
+                        var_kind == JS_VAR_PRIVATE_GETTER_SETTER ||
+                        var_kind == (JS_VAR_PRIVATE_GETTER + is_set)) {
+                        goto private_field_already_defined;
+                    }
+                    fd->vars[idx].var_kind = JS_VAR_PRIVATE_GETTER_SETTER;
+                } else {
+                    if (add_private_class_field(s, fd, name,
+                                                JS_VAR_PRIVATE_GETTER + is_set) < 0)
+                        goto fail;
+                }
+                if (add_brand(s, &class_fields[is_static]) < 0)
+                    goto fail;
+            }
+
+            if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set,
+                                        JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                        start_ptr, s->token.line_num,
+                                        JS_PARSE_EXPORT_NONE, &method_fd))
+                goto fail;
+            if (is_private) {
+                method_fd->need_home_object = TRUE; /* needed for brand check */
+                emit_op(s, OP_set_home_object);
+                /* XXX: missing function name */
+                emit_op(s, OP_scope_put_var_init);
+                if (is_set) {
+                    JSAtom setter_name;
+                    int ret;
+                    
+                    setter_name = get_private_setter_name(ctx, name);
+                    if (setter_name == JS_ATOM_NULL)
+                        goto fail;
+                    emit_atom(s, setter_name);
+                    ret = add_private_class_field(s, fd, setter_name,
+                                                  JS_VAR_PRIVATE_SETTER);
+                    JS_FreeAtom(ctx, setter_name);
+                    if (ret < 0)
+                        goto fail;
+                } else {
+                    emit_atom(s, name);
+                }
+                emit_u16(s, s->cur_func->scope_level);
+            } else {
+                if (name == JS_ATOM_NULL) {
+                    emit_op(s, OP_define_method_computed);
+                } else {
+                    emit_op(s, OP_define_method);
+                    emit_atom(s, name);
+                }
+                emit_u8(s, OP_DEFINE_METHOD_GETTER + is_set);
+            }
+        } else if (prop_type == PROP_TYPE_IDENT && s->token.val != '(') {
+            ClassFieldsDef *cf = &class_fields[is_static];
+            JSAtom field_var_name = JS_ATOM_NULL;
+            
+            /* class field */
+
+            /* XXX: spec: not consistent with method name checks */
+            if (name == JS_ATOM_constructor || name == JS_ATOM_prototype) {
+                js_parse_error(s, "invalid field name");
+                goto fail;
+            }
+            
+            if (is_private) {
+                if (find_private_class_field(ctx, fd, name,
+                                             fd->scope_level) >= 0) {
+                    goto private_field_already_defined;
+                }
+                if (add_private_class_field(s, fd, name,
+                                            JS_VAR_PRIVATE_FIELD) < 0)
+                    goto fail;
+                emit_op(s, OP_private_symbol);
+                emit_atom(s, name);
+                emit_op(s, OP_scope_put_var_init);
+                emit_atom(s, name);
+                emit_u16(s, s->cur_func->scope_level);
+            }
+
+            if (!cf->fields_init_fd) {
+                if (emit_class_init_start(s, cf))
+                    goto fail;
+            }
+            if (name == JS_ATOM_NULL ) {
+                /* save the computed field name into a variable */
+                field_var_name = js_atom_concat_num(ctx, JS_ATOM_computed_field + is_static, cf->computed_fields_count);
+                if (field_var_name == JS_ATOM_NULL)
+                    goto fail;
+                if (define_var(s, fd, field_var_name, JS_VAR_DEF_CONST) < 0) {
+                    JS_FreeAtom(ctx, field_var_name);
+                    goto fail;
+                }
+                emit_op(s, OP_to_propkey);
+                emit_op(s, OP_scope_put_var_init);
+                emit_atom(s, field_var_name);
+                emit_u16(s, s->cur_func->scope_level);
+            }
+            s->cur_func = cf->fields_init_fd;
+            emit_op(s, OP_scope_get_var);
+            emit_atom(s, JS_ATOM_this);
+            emit_u16(s, 0);
+
+            if (name == JS_ATOM_NULL) {
+                emit_op(s, OP_scope_get_var);
+                emit_atom(s, field_var_name);
+                emit_u16(s, s->cur_func->scope_level);
+                cf->computed_fields_count++;
+                JS_FreeAtom(ctx, field_var_name);
+            } else if (is_private) {
+                emit_op(s, OP_scope_get_var);
+                emit_atom(s, name);
+                emit_u16(s, s->cur_func->scope_level);
+            }
+            
+            if (s->token.val == '=') {
+                if (next_token(s))
+                    goto fail;
+                if (js_parse_assign_expr(s, TRUE))
+                    goto fail;
+            } else {
+                emit_op(s, OP_undefined);
+            }
+            if (is_private) {
+                set_object_name_computed(s);
+                emit_op(s, OP_define_private_field);
+            } else if (name == JS_ATOM_NULL) {
+                set_object_name_computed(s);
+                emit_op(s, OP_define_array_el);
+                emit_op(s, OP_drop);
+            } else {
+                set_object_name(s, name);
+                emit_op(s, OP_define_field);
+                emit_atom(s, name);
+            }
+            s->cur_func = s->cur_func->parent;
+            if (js_parse_expect_semi(s))
+                goto fail;
+        } else {
+            JSParseFunctionEnum func_type;
+            JSFunctionKindEnum func_kind;
+            
+            func_type = JS_PARSE_FUNC_METHOD;
+            func_kind = JS_FUNC_NORMAL;
+            if (prop_type == PROP_TYPE_STAR) {
+                func_kind = JS_FUNC_GENERATOR;
+            } else if (prop_type == PROP_TYPE_ASYNC) {
+                func_kind = JS_FUNC_ASYNC;
+            } else if (prop_type == PROP_TYPE_ASYNC_STAR) {
+                func_kind = JS_FUNC_ASYNC_GENERATOR;
+            } else if (name == JS_ATOM_constructor && !is_static) {
+                if (ctor_fd) {
+                    js_parse_error(s, "property constructor appears more than once");
+                    goto fail;
+                }
+                if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE)
+                    func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR;
+                else
+                    func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
+            }
+            if (is_private) {
+                if (add_brand(s, &class_fields[is_static]) < 0)
+                    goto fail;
+            }
+            if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd))
+                goto fail;
+            if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR ||
+                func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
+                ctor_fd = method_fd;
+            } else if (is_private) {
+                method_fd->need_home_object = TRUE; /* needed for brand check */
+                if (find_private_class_field(ctx, fd, name,
+                                             fd->scope_level) >= 0) {
+                private_field_already_defined:
+                    js_parse_error(s, "private class field is already defined");
+                    goto fail;
+                }
+                if (add_private_class_field(s, fd, name,
+                                            JS_VAR_PRIVATE_METHOD) < 0)
+                    goto fail;
+                emit_op(s, OP_set_home_object);
+                emit_op(s, OP_set_name);
+                emit_atom(s, name);
+                emit_op(s, OP_scope_put_var_init);
+                emit_atom(s, name);
+                emit_u16(s, s->cur_func->scope_level);
+            } else {
+                if (name == JS_ATOM_NULL) {
+                    emit_op(s, OP_define_method_computed);
+                } else {
+                    emit_op(s, OP_define_method);
+                    emit_atom(s, name);
+                }
+                emit_u8(s, OP_DEFINE_METHOD_METHOD);
+            }
+        }
+        if (is_static)
+            emit_op(s, OP_swap);
+        JS_FreeAtom(ctx, name);
+        name = JS_ATOM_NULL;
+    }
+
+    if (s->token.val != '}') {
+        js_parse_error(s, "expecting '%c'", '}');
+        goto fail;
+    }
+
+    if (!ctor_fd) {
+        if (js_parse_class_default_ctor(s, class_flags & JS_DEFINE_CLASS_HAS_HERITAGE, &ctor_fd))
+            goto fail;
+    }
+    /* patch the constant pool index for the constructor */
+    put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx);
+
+    /* store the class source code in the constructor. */
+    if (!(fd->js_mode & JS_MODE_STRIP)) {
+        js_free(ctx, ctor_fd->source);
+        ctor_fd->source_len = s->buf_ptr - class_start_ptr;
+        ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr,
+                                     ctor_fd->source_len);
+        if (!ctor_fd->source)
+            goto fail;
+    }
+
+    /* consume the '}' */
+    if (next_token(s))
+        goto fail;
+
+    /* store the function to initialize the fields to that it can be
+       referenced by the constructor */
+    {
+        ClassFieldsDef *cf = &class_fields[0];
+        int var_idx;
+        
+        var_idx = define_var(s, fd, JS_ATOM_class_fields_init,
+                             JS_VAR_DEF_CONST);
+        if (var_idx < 0)
+            goto fail;
+        if (cf->fields_init_fd) {
+            emit_class_init_end(s, cf);
+        } else {
+            emit_op(s, OP_undefined);
+        }
+        emit_op(s, OP_scope_put_var_init);
+        emit_atom(s, JS_ATOM_class_fields_init);
+        emit_u16(s, s->cur_func->scope_level);
+    }
+
+    /* drop the prototype */
+    emit_op(s, OP_drop);
+
+    /* initialize the static fields */
+    if (class_fields[1].fields_init_fd != NULL) {
+        ClassFieldsDef *cf = &class_fields[1];
+        emit_op(s, OP_dup);
+        emit_class_init_end(s, cf);
+        emit_op(s, OP_call_method);
+        emit_u16(s, 0);
+        emit_op(s, OP_drop);
+    }
+    
+    if (class_name != JS_ATOM_NULL) {
+        /* store the class name in the scoped class name variable (it
+           is independent from the class statement variable
+           definition) */
+        emit_op(s, OP_dup);
+        emit_op(s, OP_scope_put_var_init);
+        emit_atom(s, class_name);
+        emit_u16(s, fd->scope_level);
+    }
+    pop_scope(s);
+    pop_scope(s);
+
+    /* the class statements have a block level scope */
+    if (class_var_name != JS_ATOM_NULL) {
+        if (define_var(s, fd, class_var_name, JS_VAR_DEF_LET) < 0)
+            goto fail;
+        emit_op(s, OP_scope_put_var_init);
+        emit_atom(s, class_var_name);
+        emit_u16(s, fd->scope_level);
+    } else {
+        if (class_name == JS_ATOM_NULL) {
+            /* cannot use OP_set_name because the name of the class
+               must be defined before the static initializers are
+               executed */
+            emit_op(s, OP_set_class_name);
+            emit_u32(s, fd->last_opcode_pos + 1 - define_class_offset);
+        }
+    }
+
+    if (export_flag != JS_PARSE_EXPORT_NONE) {
+        if (!add_export_entry(s, fd->module,
+                              class_var_name,
+                              export_flag == JS_PARSE_EXPORT_NAMED ? class_var_name : JS_ATOM_default,
+                              JS_EXPORT_TYPE_LOCAL))
+            goto fail;
+    }
+
+    JS_FreeAtom(ctx, class_name);
+    JS_FreeAtom(ctx, class_var_name);
+    fd->js_mode = saved_js_mode;
+    return 0;
+ fail:
+    JS_FreeAtom(ctx, name);
+    JS_FreeAtom(ctx, class_name);
+    JS_FreeAtom(ctx, class_var_name);
+    fd->js_mode = saved_js_mode;
+    return -1;
+}
+
+static __exception int js_parse_array_literal(JSParseState *s)
+{
+    uint32_t idx;
+    BOOL need_length;
+
+    if (next_token(s))
+        return -1;
+    /* small regular arrays are created on the stack */
+    idx = 0;
+    while (s->token.val != ']' && idx < 32) {
+        if (s->token.val == ',' || s->token.val == TOK_ELLIPSIS)
+            break;
+        if (js_parse_assign_expr(s, TRUE))
+            return -1;
+        idx++;
+        /* accept trailing comma */
+        if (s->token.val == ',') {
+            if (next_token(s))
+                return -1;
+        } else
+        if (s->token.val != ']')
+            goto done;
+    }
+    emit_op(s, OP_array_from);
+    emit_u16(s, idx);
+
+    /* larger arrays and holes are handled with explicit indices */
+    need_length = FALSE;
+    while (s->token.val != ']' && idx < 0x7fffffff) {
+        if (s->token.val == TOK_ELLIPSIS)
+            break;
+        need_length = TRUE;
+        if (s->token.val != ',') {
+            if (js_parse_assign_expr(s, TRUE))
+                return -1;
+            emit_op(s, OP_define_field);
+            emit_u32(s, __JS_AtomFromUInt32(idx));
+            need_length = FALSE;
+        }
+        idx++;
+        /* accept trailing comma */
+        if (s->token.val == ',') {
+            if (next_token(s))
+                return -1;
+        }
+    }
+    if (s->token.val == ']') {
+        if (need_length) {
+            /* Set the length: Cannot use OP_define_field because
+               length is not configurable */
+            emit_op(s, OP_dup);
+            emit_op(s, OP_push_i32);
+            emit_u32(s, idx);
+            emit_op(s, OP_put_field);
+            emit_atom(s, JS_ATOM_length);
+        }
+        goto done;
+    }
+
+    /* huge arrays and spread elements require a dynamic index on the stack */
+    emit_op(s, OP_push_i32);
+    emit_u32(s, idx);
+
+    /* stack has array, index */
+    while (s->token.val != ']') {
+        if (s->token.val == TOK_ELLIPSIS) {
+            if (next_token(s))
+                return -1;
+            if (js_parse_assign_expr(s, TRUE))
+                return -1;
+#if 1
+            emit_op(s, OP_append);
+#else
+            int label_next, label_done;
+            label_next = new_label(s);
+            label_done = new_label(s);
+            /* enumerate object */
+            emit_op(s, OP_for_of_start);
+            emit_op(s, OP_rot5l);
+            emit_op(s, OP_rot5l);
+            emit_label(s, label_next);
+            /* on stack: enum_rec array idx */
+            emit_op(s, OP_for_of_next);
+            emit_u8(s, 2);
+            emit_goto(s, OP_if_true, label_done);
+            /* append element */
+            /* enum_rec array idx val -> enum_rec array new_idx */
+            emit_op(s, OP_define_array_el);
+            emit_op(s, OP_inc);
+            emit_goto(s, OP_goto, label_next);
+            emit_label(s, label_done);
+            /* close enumeration */
+            emit_op(s, OP_drop); /* drop undef val */
+            emit_op(s, OP_nip1); /* drop enum_rec */
+            emit_op(s, OP_nip1);
+            emit_op(s, OP_nip1);
+#endif
+        } else {
+            need_length = TRUE;
+            if (s->token.val != ',') {
+                if (js_parse_assign_expr(s, TRUE))
+                    return -1;
+                /* a idx val */
+                emit_op(s, OP_define_array_el);
+                need_length = FALSE;
+            }
+            emit_op(s, OP_inc);
+        }
+        if (s->token.val != ',')
+            break;
+        if (next_token(s))
+            return -1;
+    }
+    if (need_length) {
+        /* Set the length: cannot use OP_define_field because
+           length is not configurable */
+        emit_op(s, OP_dup1);    /* array length - array array length */
+        emit_op(s, OP_put_field);
+        emit_atom(s, JS_ATOM_length);
+    } else {
+        emit_op(s, OP_drop);    /* array length - array */
+    }
+done:
+    return js_parse_expect(s, ']');
+}
+
+/* XXX: remove */
+static BOOL has_with_scope(JSFunctionDef *s, int scope_level)
+{
+    /* check if scope chain contains a with statement */
+    while (s) {
+        int scope_idx = s->scopes[scope_level].first;
+        while (scope_idx >= 0) {
+            JSVarDef *vd = &s->vars[scope_idx];
+
+            if (vd->var_name == JS_ATOM__with_)
+                return TRUE;
+            scope_idx = vd->scope_next;
+        }
+        /* check parent scopes */
+        scope_level = s->parent_scope_level;
+        s = s->parent;
+    }
+    return FALSE;
+}
+
+typedef struct JSLValue {
+    int opcode;
+    int scope;
+    int label;
+    int depth;
+    int tok;
+    JSAtom name;
+} JSLValue;
+
+static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope,
+                                  JSAtom *pname, int *plabel, int *pdepth, BOOL keep,
+                                  int tok)
+{
+    JSFunctionDef *fd;
+    int opcode, scope, label, depth;
+    JSAtom name;
+
+    /* we check the last opcode to get the lvalue type */
+    fd = s->cur_func;
+    scope = 0;
+    name = JS_ATOM_NULL;
+    label = -1;
+    depth = 0;
+    switch(opcode = get_prev_opcode(fd)) {
+    case OP_scope_get_var:
+        name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+        scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
+        if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) &&
+            (fd->js_mode & JS_MODE_STRICT)) {
+            return js_parse_error(s, "invalid lvalue in strict mode");
+        }
+        if (name == JS_ATOM_this || name == JS_ATOM_new_target)
+            goto invalid_lvalue;
+        depth = 2;  /* will generate OP_get_ref_value */
+        break;
+    case OP_get_field:
+        name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+        depth = 1;
+        break;
+    case OP_scope_get_private_field:
+        name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+        scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
+        depth = 1;
+        break;
+    case OP_get_array_el:
+        depth = 2;
+        break;
+    case OP_get_super_value:
+        depth = 3;
+        break;
+    default:
+    invalid_lvalue:
+        if (tok == TOK_FOR) {
+            return js_parse_error(s, "invalid for in/of left hand-side");
+        } else if (tok == TOK_INC || tok == TOK_DEC) {
+            return js_parse_error(s, "invalid increment/decrement operand");
+        } else if (tok == '[' || tok == '{') {
+            return js_parse_error(s, "invalid destructuring target");
+        } else {
+            return js_parse_error(s, "invalid assignment left-hand side");
+        }
+    }
+    /* remove the last opcode */
+    fd->byte_code.size = fd->last_opcode_pos;
+    fd->last_opcode_pos = -1;
+
+    if (keep) {
+        /* get the value but keep the object/fields on the stack */
+        switch(opcode) {
+        case OP_scope_get_var:
+            label = new_label(s);
+            emit_op(s, OP_scope_make_ref);
+            emit_atom(s, name);
+            emit_u32(s, label);
+            emit_u16(s, scope);
+            update_label(fd, label, 1);
+            emit_op(s, OP_get_ref_value);
+            opcode = OP_get_ref_value;
+            break;
+        case OP_get_field:
+            emit_op(s, OP_get_field2);
+            emit_atom(s, name);
+            break;
+        case OP_scope_get_private_field:
+            emit_op(s, OP_scope_get_private_field2);
+            emit_atom(s, name);
+            emit_u16(s, scope);
+            break;
+        case OP_get_array_el:
+            /* XXX: replace by a single opcode ? */
+            emit_op(s, OP_to_propkey2);
+            emit_op(s, OP_dup2);
+            emit_op(s, OP_get_array_el);
+            break;
+        case OP_get_super_value:
+            emit_op(s, OP_to_propkey);
+            emit_op(s, OP_dup3);
+            emit_op(s, OP_get_super_value);
+            break;
+        default:
+            abort();
+        }
+    } else {
+        switch(opcode) {
+        case OP_scope_get_var:
+            label = new_label(s);
+            emit_op(s, OP_scope_make_ref);
+            emit_atom(s, name);
+            emit_u32(s, label);
+            emit_u16(s, scope);
+            update_label(fd, label, 1);
+            opcode = OP_get_ref_value;
+            break;
+        case OP_get_array_el:
+            emit_op(s, OP_to_propkey2);
+            break;
+        case OP_get_super_value:
+            emit_op(s, OP_to_propkey);
+            break;
+        }
+    }
+
+    *popcode = opcode;
+    *pscope = scope;
+    /* name has refcount for OP_get_field and OP_get_ref_value,
+       and JS_ATOM_NULL for other opcodes */
+    *pname = name;
+    *plabel = label;
+    if (pdepth)
+        *pdepth = depth;
+    return 0;
+}
+
+/* if special = TRUE: specific post inc/dec case */
+/* name has a live reference */
+static void put_lvalue(JSParseState *s, int opcode, int scope,
+                       JSAtom name, int label, BOOL special)
+{
+    switch(opcode) {
+    case OP_get_field:
+        if (!special)
+            emit_op(s, OP_insert2); /* obj v -> v obj v */
+        else
+            emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */
+        emit_op(s, OP_put_field);
+        emit_u32(s, name);  /* name has refcount */
+        break;
+    case OP_scope_get_private_field:
+        if (!special)
+            emit_op(s, OP_insert2); /* obj v -> v obj v */
+        else
+            emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */
+        emit_op(s, OP_scope_put_private_field);
+        emit_u32(s, name);  /* name has refcount */
+        emit_u16(s, scope);
+        break;
+    case OP_get_array_el:
+        if (!special)
+            emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */
+        else
+            emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */
+        emit_op(s, OP_put_array_el);
+        break;
+    case OP_get_ref_value:
+        JS_FreeAtom(s->ctx, name);
+        emit_label(s, label);
+        if (!special)
+            emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */
+        else
+            emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */
+        emit_op(s, OP_put_ref_value);
+        break;
+    case OP_get_super_value:
+        if (!special)
+            emit_op(s, OP_insert4); /* this obj prop v -> v this obj prop v */
+        else
+            emit_op(s, OP_perm5); /* this obj prop v0 v -> v0 this obj prop v */
+        emit_op(s, OP_put_super_value);
+        break;
+    default:
+        abort();
+    }
+}
+
+static void put_lvalue_nokeep(JSParseState *s, int opcode, int scope,
+                              JSAtom name, int label, int var_tok)
+{
+    switch(opcode) {
+    case OP_scope_get_var:  /* val -- */
+        emit_op(s, (var_tok == TOK_CONST || var_tok == TOK_LET) ?
+                OP_scope_put_var_init : OP_scope_put_var);
+        emit_u32(s, name);  /* has refcount */
+        emit_u16(s, scope);
+        break;
+    case OP_get_field:      /* obj val -- */
+        emit_op(s, OP_put_field);
+        emit_u32(s, name);  /* has refcount */
+        break;
+    case OP_scope_get_private_field:
+        emit_op(s, OP_scope_put_private_field);
+        emit_u32(s, name);  /* has refcount */
+        emit_u16(s, scope);
+        break;
+    case OP_get_array_el:   /* obj prop val -- */
+        emit_op(s, OP_put_array_el);
+        break;
+    case OP_get_ref_value:   /* obj prop val -- */
+        /* XXX: currently this reference is never optimized */
+        JS_FreeAtom(s->ctx, name);
+        emit_label(s, label);
+        //emit_op(s, OP_nop);   /* emit 2 bytes for optimizer */
+        emit_op(s, OP_put_ref_value);
+        break;
+    case OP_get_super_value:
+        emit_op(s, OP_put_super_value);
+        break;
+    default:
+        abort();
+    }
+}
+
+static __exception int js_parse_expr_paren(JSParseState *s)
+{
+    if (js_parse_expect(s, '('))
+        return -1;
+    if (js_parse_expr(s))
+        return -1;
+    if (js_parse_expect(s, ')'))
+        return -1;
+    return 0;
+}
+
+static int js_unsupported_keyword(JSParseState *s, JSAtom atom)
+{
+    char buf[ATOM_GET_STR_BUF_SIZE];
+    return js_parse_error(s, "unsupported keyword: %s",
+                          JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom));
+}
+
+static __exception int js_define_var(JSParseState *s, JSAtom name, int tok)
+{
+    JSFunctionDef *fd = s->cur_func;
+    JSVarDefEnum var_def_type;
+    
+    if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
+        return js_parse_error(s, "yield is a reserved identifier");
+    }
+    if ((name == JS_ATOM_arguments || name == JS_ATOM_eval)
+    &&  (fd->js_mode & JS_MODE_STRICT)) {
+        return js_parse_error(s, "invalid variable name in strict mode");
+    }
+    if ((name == JS_ATOM_let || name == JS_ATOM_undefined)
+    &&  (tok == TOK_LET || tok == TOK_CONST)) {
+        return js_parse_error(s, "invalid lexical variable name");
+    }
+    switch(tok) {
+    case TOK_LET:
+        var_def_type = JS_VAR_DEF_LET;
+        break;
+    case TOK_CONST:
+        var_def_type = JS_VAR_DEF_CONST;
+        break;
+    case TOK_VAR:
+        var_def_type = JS_VAR_DEF_VAR;
+        break;
+    case TOK_CATCH:
+        var_def_type = JS_VAR_DEF_CATCH;
+        break;
+    default:
+        abort();
+    }
+    if (define_var(s, fd, name, var_def_type) < 0)
+        return -1;
+    return 0;
+}
+
+static void js_emit_spread_code(JSParseState *s, int depth)
+{
+    int label_rest_next, label_rest_done;
+
+    /* XXX: could check if enum object is an actual array and optimize
+       slice extraction. enumeration record and target array are in a
+       different order from OP_append case. */
+    /* enum_rec xxx -- enum_rec xxx array 0 */
+    emit_op(s, OP_array_from);
+    emit_u16(s, 0);
+    emit_op(s, OP_push_i32);
+    emit_u32(s, 0);
+    emit_label(s, label_rest_next = new_label(s));
+    emit_op(s, OP_for_of_next);
+    emit_u8(s, 2 + depth);
+    label_rest_done = emit_goto(s, OP_if_true, -1);
+    /* array idx val -- array idx */
+    emit_op(s, OP_define_array_el);
+    emit_op(s, OP_inc);
+    emit_goto(s, OP_goto, label_rest_next);
+    emit_label(s, label_rest_done);
+    /* enum_rec xxx array idx undef -- enum_rec xxx array */
+    emit_op(s, OP_drop);
+    emit_op(s, OP_drop);
+}
+
+static int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name)
+{
+    /* Check for duplicate parameter names */
+    JSFunctionDef *fd = s->cur_func;
+    int i;
+    for (i = 0; i < fd->arg_count; i++) {
+        if (fd->args[i].var_name == name)
+            goto duplicate;
+    }
+    for (i = 0; i < fd->var_count; i++) {
+        if (fd->vars[i].var_name == name)
+            goto duplicate;
+    }
+    return 0;
+
+duplicate:
+    return js_parse_error(s, "duplicate parameter names not allowed in this context");
+}
+
+static JSAtom js_parse_destructing_var(JSParseState *s, int tok, int is_arg)
+{
+    JSAtom name;
+
+    if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
+    ||  ((s->cur_func->js_mode & JS_MODE_STRICT) &&
+         (s->token.u.ident.atom == JS_ATOM_eval || s->token.u.ident.atom == JS_ATOM_arguments))) {
+        js_parse_error(s, "invalid destructuring target");
+        return JS_ATOM_NULL;
+    }
+    name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
+    if (is_arg && js_parse_check_duplicate_parameter(s, name))
+        goto fail;
+    if (next_token(s))
+        goto fail;
+
+    return name;
+fail:
+    JS_FreeAtom(s->ctx, name);
+    return JS_ATOM_NULL;
+}
+
+static int js_parse_destructing_element(JSParseState *s, int tok, int is_arg,
+                                        int hasval, int has_ellipsis,
+                                        BOOL allow_initializer)
+{
+    int label_parse, label_assign, label_done, label_lvalue, depth_lvalue;
+    int start_addr, assign_addr;
+    JSAtom prop_name, var_name;
+    int opcode, scope, tok1, skip_bits;
+
+    if (has_ellipsis < 0) {
+        /* pre-parse destructuration target for spread detection */
+        js_parse_skip_parens_token(s, &skip_bits, FALSE);
+        has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS;
+    }
+
+    label_parse = new_label(s);
+    label_assign = new_label(s);
+
+    start_addr = s->cur_func->byte_code.size;
+    if (hasval) {
+        /* consume value from the stack */
+        emit_op(s, OP_dup);
+        emit_op(s, OP_undefined);
+        emit_op(s, OP_strict_eq);
+        emit_goto(s, OP_if_true, label_parse);
+        emit_label(s, label_assign);
+    } else {
+        emit_goto(s, OP_goto, label_parse);
+        emit_label(s, label_assign);
+        /* leave value on the stack */
+        emit_op(s, OP_dup);
+    }
+    assign_addr = s->cur_func->byte_code.size;
+    if (s->token.val == '{') {
+        if (next_token(s))
+            return -1;
+        /* throw an exception if the value cannot be converted to an object */
+        emit_op(s, OP_to_object);
+        if (has_ellipsis) {
+            /* add excludeList on stack just below src object */
+            emit_op(s, OP_object);
+            emit_op(s, OP_swap);
+        }
+        while (s->token.val != '}') {
+            int prop_type;
+            if (s->token.val == TOK_ELLIPSIS) {
+                if (!has_ellipsis) {
+                    JS_ThrowInternalError(s->ctx, "unexpected ellipsis token");
+                    return -1;
+                }
+                if (next_token(s))
+                    return -1;
+                if (tok) {
+                    var_name = js_parse_destructing_var(s, tok, is_arg);
+                    if (var_name == JS_ATOM_NULL)
+                        return -1;
+                    opcode = OP_scope_get_var;
+                    scope = s->cur_func->scope_level;
+                    label_lvalue = -1;
+                    depth_lvalue = 0;
+                } else {
+                    if (js_parse_postfix_expr(s, TRUE))
+                        return -1;
+
+                    if (get_lvalue(s, &opcode, &scope, &var_name,
+                                   &label_lvalue, &depth_lvalue, FALSE, '{'))
+                        return -1;
+                }
+                if (s->token.val != '}') {
+                    js_parse_error(s, "assignment rest property must be last");
+                    goto var_error;
+                }
+                emit_op(s, OP_object);  /* target */
+                emit_op(s, OP_copy_data_properties);
+                emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5));
+                goto set_val;
+            }
+            prop_type = js_parse_property_name(s, &prop_name, FALSE, TRUE, FALSE);
+            if (prop_type < 0)
+                return -1;
+            var_name = JS_ATOM_NULL;
+            opcode = OP_scope_get_var;
+            scope = s->cur_func->scope_level;
+            label_lvalue = -1;
+            depth_lvalue = 0;
+            if (prop_type == PROP_TYPE_IDENT) {
+                if (next_token(s))
+                    goto prop_error;
+                if ((s->token.val == '[' || s->token.val == '{')
+                    &&  ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
+                         tok1 == '=' || tok1 == '}')) {
+                    if (prop_name == JS_ATOM_NULL) {
+                        /* computed property name on stack */
+                        if (has_ellipsis) {
+                            /* define the property in excludeList */
+                            emit_op(s, OP_to_propkey); /* avoid calling ToString twice */
+                            emit_op(s, OP_perm3); /* TOS: src excludeList prop */
+                            emit_op(s, OP_null); /* TOS: src excludeList prop null */
+                            emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */
+                            emit_op(s, OP_perm3); /* TOS: excludeList src prop */
+                        }
+                        /* get the computed property from the source object */
+                        emit_op(s, OP_get_array_el2);
+                    } else {
+                        /* named property */
+                        if (has_ellipsis) {
+                            /* define the property in excludeList */
+                            emit_op(s, OP_swap); /* TOS: src excludeList */
+                            emit_op(s, OP_null); /* TOS: src excludeList null */
+                            emit_op(s, OP_define_field); /* TOS: src excludeList */
+                            emit_atom(s, prop_name);
+                            emit_op(s, OP_swap); /* TOS: excludeList src */
+                        }
+                        /* get the named property from the source object */
+                        emit_op(s, OP_get_field2);
+                        emit_u32(s, prop_name);
+                    }
+                    if (js_parse_destructing_element(s, tok, is_arg, TRUE, -1, TRUE))
+                        return -1;
+                    if (s->token.val == '}')
+                        break;
+                    /* accept a trailing comma before the '}' */
+                    if (js_parse_expect(s, ','))
+                        return -1;
+                    continue;
+                }
+                if (prop_name == JS_ATOM_NULL) {
+                    emit_op(s, OP_to_propkey2);
+                    if (has_ellipsis) {
+                        /* define the property in excludeList */
+                        emit_op(s, OP_perm3);
+                        emit_op(s, OP_null);
+                        emit_op(s, OP_define_array_el);
+                        emit_op(s, OP_perm3);
+                    }
+                    /* source prop -- source source prop */
+                    emit_op(s, OP_dup1);
+                } else {
+                    if (has_ellipsis) {
+                        /* define the property in excludeList */
+                        emit_op(s, OP_swap);
+                        emit_op(s, OP_null);
+                        emit_op(s, OP_define_field);
+                        emit_atom(s, prop_name);
+                        emit_op(s, OP_swap);
+                    }
+                    /* source -- source source */
+                    emit_op(s, OP_dup);
+                }
+                if (tok) {
+                    var_name = js_parse_destructing_var(s, tok, is_arg);
+                    if (var_name == JS_ATOM_NULL)
+                        goto prop_error;
+                } else {
+                    if (js_parse_postfix_expr(s, TRUE))
+                        goto prop_error;
+                lvalue:
+                    if (get_lvalue(s, &opcode, &scope, &var_name,
+                                   &label_lvalue, &depth_lvalue, FALSE, '{'))
+                        goto prop_error;
+                    /* swap ref and lvalue object if any */
+                    if (prop_name == JS_ATOM_NULL) {
+                        switch(depth_lvalue) {
+                        case 1:
+                            /* source prop x -> x source prop */
+                            emit_op(s, OP_rot3r);
+                            break;
+                        case 2:
+                            /* source prop x y -> x y source prop */
+                            emit_op(s, OP_swap2);   /* t p2 s p1 */
+                            break;
+                        case 3:
+                            /* source prop x y z -> x y z source prop */
+                            emit_op(s, OP_rot5l);
+                            emit_op(s, OP_rot5l);
+                            break;
+                        }
+                    } else {
+                        switch(depth_lvalue) {
+                        case 1:
+                            /* source x -> x source */
+                            emit_op(s, OP_swap);
+                            break;
+                        case 2:
+                            /* source x y -> x y source */
+                            emit_op(s, OP_rot3l);
+                            break;
+                        case 3:
+                            /* source x y z -> x y z source */
+                            emit_op(s, OP_rot4l);
+                            break;
+                        }
+                    }
+                }
+                if (prop_name == JS_ATOM_NULL) {
+                    /* computed property name on stack */
+                    /* XXX: should have OP_get_array_el2x with depth */
+                    /* source prop -- val */
+                    emit_op(s, OP_get_array_el);
+                } else {
+                    /* named property */
+                    /* XXX: should have OP_get_field2x with depth */
+                    /* source -- val */
+                    emit_op(s, OP_get_field);
+                    emit_u32(s, prop_name);
+                }
+            } else {
+                /* prop_type = PROP_TYPE_VAR, cannot be a computed property */
+                if (is_arg && js_parse_check_duplicate_parameter(s, prop_name))
+                    goto prop_error;
+                if ((s->cur_func->js_mode & JS_MODE_STRICT) &&
+                    (prop_name == JS_ATOM_eval || prop_name == JS_ATOM_arguments)) {
+                    js_parse_error(s, "invalid destructuring target");
+                    goto prop_error;
+                }
+                if (has_ellipsis) {
+                    /* define the property in excludeList */
+                    emit_op(s, OP_swap);
+                    emit_op(s, OP_null);
+                    emit_op(s, OP_define_field);
+                    emit_atom(s, prop_name);
+                    emit_op(s, OP_swap);
+                }
+                if (!tok || tok == TOK_VAR) {
+                    /* generate reference */
+                    /* source -- source source */
+                    emit_op(s, OP_dup);
+                    emit_op(s, OP_scope_get_var);
+                    emit_atom(s, prop_name);
+                    emit_u16(s, s->cur_func->scope_level);
+                    goto lvalue;
+                }
+                var_name = JS_DupAtom(s->ctx, prop_name);
+                /* source -- source val */
+                emit_op(s, OP_get_field2);
+                emit_u32(s, prop_name);
+            }
+        set_val:
+            if (tok) {
+                if (js_define_var(s, var_name, tok))
+                    goto var_error;
+                scope = s->cur_func->scope_level;
+            }
+            if (s->token.val == '=') {  /* handle optional default value */
+                int label_hasval;
+                emit_op(s, OP_dup);
+                emit_op(s, OP_undefined);
+                emit_op(s, OP_strict_eq);
+                label_hasval = emit_goto(s, OP_if_false, -1);
+                if (next_token(s))
+                    goto var_error;
+                emit_op(s, OP_drop);
+                if (js_parse_assign_expr(s, TRUE))
+                    goto var_error;
+                if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
+                    set_object_name(s, var_name);
+                emit_label(s, label_hasval);
+            }
+            /* store value into lvalue object */
+            put_lvalue_nokeep(s, opcode, scope, var_name, label_lvalue, tok);
+            if (s->token.val == '}')
+                break;
+            /* accept a trailing comma before the '}' */
+            if (js_parse_expect(s, ','))
+                return -1;
+        }
+        /* drop the source object */
+        emit_op(s, OP_drop);
+        if (has_ellipsis) {
+            emit_op(s, OP_drop); /* pop excludeList */
+        }
+        if (next_token(s))
+            return -1;
+    } else if (s->token.val == '[') {
+        BOOL has_spread;
+        int enum_depth;
+        BlockEnv block_env;
+
+        if (next_token(s))
+            return -1;
+        /* the block environment is only needed in generators in case
+           'yield' triggers a 'return' */
+        push_break_entry(s->cur_func, &block_env,
+                         JS_ATOM_NULL, -1, -1, 2);
+        block_env.has_iterator = TRUE;
+        emit_op(s, OP_for_of_start);
+        has_spread = FALSE;
+        while (s->token.val != ']') {
+            /* get the next value */
+            if (s->token.val == TOK_ELLIPSIS) {
+                if (next_token(s))
+                    return -1;
+                if (s->token.val == ',' || s->token.val == ']')
+                    return js_parse_error(s, "missing binding pattern...");
+                has_spread = TRUE;
+            }
+            if (s->token.val == ',') {
+                /* do nothing, skip the value, has_spread is false */
+                emit_op(s, OP_for_of_next);
+                emit_u8(s, 0);
+                emit_op(s, OP_drop);
+                emit_op(s, OP_drop);
+            } else if ((s->token.val == '[' || s->token.val == '{')
+                   &&  ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
+                        tok1 == '=' || tok1 == ']')) {
+                if (has_spread) {
+                    if (tok1 == '=')
+                        return js_parse_error(s, "rest element cannot have a default value");
+                    js_emit_spread_code(s, 0);
+                } else {
+                    emit_op(s, OP_for_of_next);
+                    emit_u8(s, 0);
+                    emit_op(s, OP_drop);
+                }
+                if (js_parse_destructing_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE))
+                    return -1;
+            } else {
+                var_name = JS_ATOM_NULL;
+                enum_depth = 0;
+                if (tok) {
+                    var_name = js_parse_destructing_var(s, tok, is_arg);
+                    if (var_name == JS_ATOM_NULL)
+                        goto var_error;
+                    if (js_define_var(s, var_name, tok))
+                        goto var_error;
+                    opcode = OP_scope_get_var;
+                    scope = s->cur_func->scope_level;
+                } else {
+                    if (js_parse_postfix_expr(s, TRUE))
+                        return -1;
+                    if (get_lvalue(s, &opcode, &scope, &var_name,
+                                   &label_lvalue, &enum_depth, FALSE, '[')) {
+                        return -1;
+                    }
+                }
+                if (has_spread) {
+                    js_emit_spread_code(s, enum_depth);
+                } else {
+                    emit_op(s, OP_for_of_next);
+                    emit_u8(s, enum_depth);
+                    emit_op(s, OP_drop);
+                }
+                if (s->token.val == '=' && !has_spread) {
+                    /* handle optional default value */
+                    int label_hasval;
+                    emit_op(s, OP_dup);
+                    emit_op(s, OP_undefined);
+                    emit_op(s, OP_strict_eq);
+                    label_hasval = emit_goto(s, OP_if_false, -1);
+                    if (next_token(s))
+                        goto var_error;
+                    emit_op(s, OP_drop);
+                    if (js_parse_assign_expr(s, TRUE))
+                        goto var_error;
+                    if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
+                        set_object_name(s, var_name);
+                    emit_label(s, label_hasval);
+                }
+                /* store value into lvalue object */
+                put_lvalue_nokeep(s, opcode, scope, var_name,
+                                  label_lvalue, tok);
+            }
+            if (s->token.val == ']')
+                break;
+            if (has_spread)
+                return js_parse_error(s, "rest element must be the last one");
+            /* accept a trailing comma before the ']' */
+            if (js_parse_expect(s, ','))
+                return -1;
+        }
+        /* close iterator object:
+           if completed, enum_obj has been replaced by undefined */
+        emit_op(s, OP_iterator_close);
+        pop_break_entry(s->cur_func);
+        if (next_token(s))
+            return -1;
+    } else {
+        return js_parse_error(s, "invalid assignment syntax");
+    }
+    if (s->token.val == '=' && allow_initializer) {
+        label_done = emit_goto(s, OP_goto, -1);
+        if (next_token(s))
+            return -1;
+        emit_label(s, label_parse);
+        if (hasval)
+            emit_op(s, OP_drop);
+        if (js_parse_assign_expr(s, TRUE))
+            return -1;
+        emit_goto(s, OP_goto, label_assign);
+        emit_label(s, label_done);
+    } else {
+        assert(hasval);
+        /* remove test and decrement label ref count */
+        memset(s->cur_func->byte_code.buf + start_addr, OP_nop,
+               assign_addr - start_addr);
+        s->cur_func->label_slots[label_parse].ref_count--;
+    }
+    return 0;
+
+ prop_error:
+    JS_FreeAtom(s->ctx, prop_name);
+ var_error:
+    JS_FreeAtom(s->ctx, var_name);
+    return -1;
+}
+
+typedef enum FuncCallType {
+    FUNC_CALL_NORMAL,
+    FUNC_CALL_NEW,
+    FUNC_CALL_SUPER_CTOR,
+    FUNC_CALL_TEMPLATE,
+} FuncCallType;
+
+static __exception int js_parse_postfix_expr(JSParseState *s, BOOL accept_lparen)
+{
+    FuncCallType call_type;
+
+    call_type = FUNC_CALL_NORMAL;
+    switch(s->token.val) {
+    case TOK_NUMBER:
+        {
+            JSValue val;
+            val = s->token.u.num.val;
+
+            if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
+                emit_op(s, OP_push_i32);
+                emit_u32(s, JS_VALUE_GET_INT(val));
+            } else
+#ifdef CONFIG_BIGNUM
+            if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) {
+                bf_t r_s, *r = &r_s;
+                slimb_t e;
+                int ret;
+
+                /* need a runtime conversion */
+                /* XXX: could add a cache and/or do it once at
+                   the start of the function */
+                if (emit_push_const(s, val, 0) < 0)
+                    return -1;
+                e = s->token.u.num.exponent;
+                if (e == (int32_t)e) {
+                    emit_op(s, OP_push_i32);
+                    emit_u32(s, e);
+                } else {
+                    bf_init(s->ctx->bf_ctx, r);
+                    bf_set_si(r, e);
+                    val = JS_NewBigInt(s->ctx, r);
+                    ret = emit_push_const(s, val, 0);
+                    JS_FreeValue(s->ctx, val);
+                    if (ret < 0)
+                        return -1;
+                }
+                emit_op(s, OP_mul_pow10);
+            } else
+#endif
+            {
+                if (emit_push_const(s, val, 0) < 0)
+                    return -1;
+            }
+        }
+        if (next_token(s))
+            return -1;
+        break;
+    case TOK_TEMPLATE:
+        if (js_parse_template(s, 0, NULL))
+            return -1;
+        break;
+    case TOK_STRING:
+        if (emit_push_const(s, s->token.u.str.str, 1))
+            return -1;
+        if (next_token(s))
+            return -1;
+        break;
+        
+    case TOK_DIV_ASSIGN:
+        s->buf_ptr -= 2;
+        goto parse_regexp;
+    case '/':
+        s->buf_ptr--;
+    parse_regexp:
+        {
+            JSValue str;
+            int ret;
+            if (!s->ctx->compile_regexp)
+                return js_parse_error(s, "RegExp are not supported");
+            /* the previous token is '/' or '/=', so no need to free */
+            if (js_parse_regexp(s))
+                return -1;
+            ret = emit_push_const(s, s->token.u.regexp.body, 0);
+            str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body,
+                                         s->token.u.regexp.flags);
+            if (JS_IsException(str)) {
+                /* add the line number info */
+                build_backtrace(s->ctx, s->ctx->current_exception,
+                                s->filename, s->token.line_num, NULL);
+                return -1;
+            }
+            ret = emit_push_const(s, str, 0);
+            JS_FreeValue(s->ctx, str);
+            if (ret)
+                return -1;
+            /* we use a specific opcode to be sure the correct
+               function is called (otherwise the bytecode would have
+               to be verified by the RegExp constructor) */
+            emit_op(s, OP_regexp);
+            if (next_token(s))
+                return -1;
+        }
+        break;
+    case '(':
+        if (js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) {
+            if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
+                                       JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                       s->token.ptr, s->token.line_num))
+                return -1;
+        } else {
+            if (js_parse_expr_paren(s))
+                return -1;
+        }
+        break;
+    case TOK_FUNCTION:
+        if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR,
+                                   JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                   s->token.ptr, s->token.line_num))
+            return -1;
+        break;
+    case TOK_CLASS:
+        if (js_parse_class(s, TRUE, JS_PARSE_EXPORT_NONE))
+            return -1;
+        break;
+    case TOK_NULL:
+        if (next_token(s))
+            return -1;
+        emit_op(s, OP_null);
+        break;
+    case TOK_THIS:
+        if (next_token(s))
+            return -1;
+        emit_op(s, OP_scope_get_var);
+        emit_atom(s, JS_ATOM_this);
+        emit_u16(s, 0);
+        break;
+    case TOK_FALSE:
+        if (next_token(s))
+            return -1;
+        emit_op(s, OP_push_false);
+        break;
+    case TOK_TRUE:
+        if (next_token(s))
+            return -1;
+        emit_op(s, OP_push_true);
+        break;
+    case TOK_IDENT:
+        {
+            JSAtom name;
+            if (s->token.u.ident.is_reserved) {
+                return js_parse_error_reserved_identifier(s);
+            }
+            if (peek_token(s, TRUE) == TOK_ARROW) {
+                if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
+                                           JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                           s->token.ptr, s->token.line_num))
+                    return -1;
+            } else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
+                       peek_token(s, TRUE) != '\n') {
+                const uint8_t *source_ptr;
+                int source_line_num;
+
+                source_ptr = s->token.ptr;
+                source_line_num = s->token.line_num;
+                if (next_token(s))
+                    return -1;
+                if (s->token.val == TOK_FUNCTION) {
+                    if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR,
+                                               JS_FUNC_ASYNC, JS_ATOM_NULL,
+                                               source_ptr, source_line_num))
+                        return -1;
+                } else if ((s->token.val == '(' &&
+                            js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) ||
+                           (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
+                            peek_token(s, TRUE) == TOK_ARROW)) {
+                    if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
+                                               JS_FUNC_ASYNC, JS_ATOM_NULL,
+                                               source_ptr, source_line_num))
+                        return -1;
+                } else {
+                    name = JS_DupAtom(s->ctx, JS_ATOM_async);
+                    goto do_get_var;
+                }
+            } else {
+                if (s->token.u.ident.atom == JS_ATOM_arguments &&
+                    !s->cur_func->arguments_allowed) {
+                    js_parse_error(s, "'arguments' identifier is not allowed in class field initializer");
+                    return -1;
+                }
+                name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
+                if (next_token(s))  /* update line number before emitting code */
+                    return -1;
+            do_get_var:
+                emit_op(s, OP_scope_get_var);
+                emit_u32(s, name);
+                emit_u16(s, s->cur_func->scope_level);
+            }
+        }
+        break;
+    case '{':
+    case '[':
+        {
+            int skip_bits;
+            if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
+                if (js_parse_destructing_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE))
+                    return -1;
+            } else {
+                if (s->token.val == '{') {
+                    if (js_parse_object_literal(s))
+                        return -1;
+                } else {
+                    if (js_parse_array_literal(s))
+                        return -1;
+                }
+            }
+        }
+        break;
+    case TOK_NEW:
+        if (next_token(s))
+            return -1;
+        if (s->token.val == '.') {
+            if (next_token(s))
+                return -1;
+            if (!token_is_pseudo_keyword(s, JS_ATOM_target))
+                return js_parse_error(s, "expecting target");
+            if (!s->cur_func->new_target_allowed)
+                return js_parse_error(s, "new.target only allowed within functions");
+            if (next_token(s))
+                return -1;
+            emit_op(s, OP_scope_get_var);
+            emit_atom(s, JS_ATOM_new_target);
+            emit_u16(s, 0);
+        } else {
+            if (js_parse_postfix_expr(s, FALSE))
+                return -1;
+            accept_lparen = TRUE;
+            if (s->token.val != '(') {
+                /* new operator on an object */
+                emit_op(s, OP_dup);
+                emit_op(s, OP_call_constructor);
+                emit_u16(s, 0);
+            } else {
+                call_type = FUNC_CALL_NEW;
+            }
+        }
+        break;
+    case TOK_SUPER:
+        if (next_token(s))
+            return -1;
+        if (s->token.val == '(') {
+            if (!s->cur_func->super_call_allowed)
+                return js_parse_error(s, "super() is only valid in a derived class constructor");
+            call_type = FUNC_CALL_SUPER_CTOR;
+        } else if (s->token.val == '.' || s->token.val == '[') {
+            if (!s->cur_func->super_allowed)
+                return js_parse_error(s, "'super' is only valid in a method");
+            emit_op(s, OP_scope_get_var);
+            emit_atom(s, JS_ATOM_this);
+            emit_u16(s, 0);
+            emit_op(s, OP_scope_get_var);
+            emit_atom(s, JS_ATOM_home_object);
+            emit_u16(s, 0);
+            emit_op(s, OP_get_super);
+        } else {
+            return js_parse_error(s, "invalid use of 'super'");
+        }
+        break;
+    case TOK_IMPORT:
+        if (next_token(s))
+            return -1;
+        if (s->token.val == '.') {
+            if (next_token(s))
+                return -1;
+            if (!token_is_pseudo_keyword(s, JS_ATOM_meta))
+                return js_parse_error(s, "meta expected");
+            if (!s->is_module)
+                return js_parse_error(s, "import.meta only valid in module code");
+            if (next_token(s))
+                return -1;
+            emit_op(s, OP_special_object);
+            emit_u8(s, OP_SPECIAL_OBJECT_IMPORT_META);
+        } else {
+            if (js_parse_expect(s, '('))
+                return -1;
+            if (!accept_lparen)
+                return js_parse_error(s, "invalid use of 'import()'");
+            if (js_parse_assign_expr(s, TRUE))
+                return -1;
+            if (js_parse_expect(s, ')'))
+                return -1;
+            emit_op(s, OP_import);
+        }
+        break;
+    default:
+        return js_parse_error(s, "unexpected token in expression: '%.*s'",
+                              (int)(s->buf_ptr - s->token.ptr), s->token.ptr);
+    }
+
+    for(;;) {
+        JSFunctionDef *fd = s->cur_func;
+
+        if (s->token.val == TOK_TEMPLATE && call_type == FUNC_CALL_NORMAL) {
+            call_type = FUNC_CALL_TEMPLATE;
+            goto parse_func_call;
+        }
+        if (s->token.val == '(' && accept_lparen) {
+            int opcode;
+            int arg_count;
+
+            /* function call */
+            if (next_token(s))
+                return -1;
+
+            if (call_type == FUNC_CALL_NORMAL) {
+            parse_func_call:
+                switch(opcode = get_prev_opcode(fd)) {
+                case OP_get_field:
+                    /* keep the object on the stack */
+                    fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
+                    break;
+                case OP_scope_get_private_field:
+                    /* keep the object on the stack */
+                    fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2;
+                    break;
+                case OP_get_array_el:
+                    /* keep the object on the stack */
+                    fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
+                    break;
+                case OP_scope_get_var:
+                    {
+                        JSAtom name;
+                        int scope;
+                        name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+                        scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
+                        if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL) {
+                            /* direct 'eval' */
+                            opcode = OP_eval;
+                        } else {
+                            /* verify if function name resolves to a simple
+                               get_loc/get_arg: a function call inside a `with`
+                               statement can resolve to a method call of the
+                               `with` context object
+                             */
+                            /* XXX: always generate the OP_scope_get_ref
+                               and remove it in variable resolution
+                               pass ? */
+                            if (has_with_scope(fd, scope)) {
+                                opcode = OP_scope_get_ref;
+                                fd->byte_code.buf[fd->last_opcode_pos] = opcode;
+                            }
+                        }
+                    }
+                    break;
+                case OP_get_super_value:
+                    fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el;
+                    /* on stack: this func_obj */
+                    opcode = OP_get_array_el;
+                    break;
+                default:
+                    opcode = OP_invalid;
+                    break;
+                }
+            } else {
+                opcode = OP_invalid;
+            }
+
+            if (call_type == FUNC_CALL_TEMPLATE) {
+                if (js_parse_template(s, 1, &arg_count))
+                    return -1;
+                goto emit_func_call;
+            } else if (call_type == FUNC_CALL_SUPER_CTOR) {
+                emit_op(s, OP_scope_get_var);
+                emit_atom(s, JS_ATOM_this_active_func);
+                emit_u16(s, 0);
+
+                emit_op(s, OP_get_super_ctor);
+
+                emit_op(s, OP_scope_get_var);
+                emit_atom(s, JS_ATOM_new_target);
+                emit_u16(s, 0);
+            } else if (call_type == FUNC_CALL_NEW) {
+                emit_op(s, OP_dup); /* new.target = function */
+            }
+
+            /* parse arguments */
+            arg_count = 0;
+            while (s->token.val != ')') {
+                if (arg_count >= 65535) {
+                    return js_parse_error(s, "Too many call arguments");
+                }
+                if (s->token.val == TOK_ELLIPSIS)
+                    break;
+                if (js_parse_assign_expr(s, TRUE))
+                    return -1;
+                arg_count++;
+                if (s->token.val == ')')
+                    break;
+                /* accept a trailing comma before the ')' */
+                if (js_parse_expect(s, ','))
+                    return -1;
+            }
+            if (s->token.val == TOK_ELLIPSIS) {
+                emit_op(s, OP_array_from);
+                emit_u16(s, arg_count);
+                emit_op(s, OP_push_i32);
+                emit_u32(s, arg_count);
+
+                /* on stack: array idx */
+                while (s->token.val != ')') {
+                    if (s->token.val == TOK_ELLIPSIS) {
+                        if (next_token(s))
+                            return -1;
+                        if (js_parse_assign_expr(s, TRUE))
+                            return -1;
+#if 1
+                        /* XXX: could pass is_last indicator? */
+                        emit_op(s, OP_append);
+#else
+                        int label_next, label_done;
+                        label_next = new_label(s);
+                        label_done = new_label(s);
+                        /* push enumerate object below array/idx pair */
+                        emit_op(s, OP_for_of_start);
+                        emit_op(s, OP_rot5l);
+                        emit_op(s, OP_rot5l);
+                        emit_label(s, label_next);
+                        /* on stack: enum_rec array idx */
+                        emit_op(s, OP_for_of_next);
+                        emit_u8(s, 2);
+                        emit_goto(s, OP_if_true, label_done);
+                        /* append element */
+                        /* enum_rec array idx val -> enum_rec array new_idx */
+                        emit_op(s, OP_define_array_el);
+                        emit_op(s, OP_inc);
+                        emit_goto(s, OP_goto, label_next);
+                        emit_label(s, label_done);
+                        /* close enumeration, drop enum_rec and idx */
+                        emit_op(s, OP_drop); /* drop undef */
+                        emit_op(s, OP_nip1); /* drop enum_rec */
+                        emit_op(s, OP_nip1);
+                        emit_op(s, OP_nip1);
+#endif
+                    } else {
+                        if (js_parse_assign_expr(s, TRUE))
+                            return -1;
+                        /* array idx val */
+                        emit_op(s, OP_define_array_el);
+                        emit_op(s, OP_inc);
+                    }
+                    if (s->token.val == ')')
+                        break;
+                    /* accept a trailing comma before the ')' */
+                    if (js_parse_expect(s, ','))
+                        return -1;
+                }
+                if (next_token(s))
+                    return -1;
+                /* drop the index */
+                emit_op(s, OP_drop);
+
+                /* apply function call */
+                switch(opcode) {
+                case OP_get_field:
+                case OP_scope_get_private_field:
+                case OP_get_array_el:
+                case OP_scope_get_ref:
+                    /* obj func array -> func obj array */
+                    emit_op(s, OP_perm3);
+                    emit_op(s, OP_apply);
+                    emit_u16(s, call_type == FUNC_CALL_NEW);
+                    break;
+                case OP_eval:
+                    emit_op(s, OP_apply_eval);
+                    emit_u16(s, fd->scope_level);
+                    fd->has_eval_call = TRUE;
+                    break;
+                default:
+                    if (call_type == FUNC_CALL_SUPER_CTOR) {
+                        emit_op(s, OP_apply);
+                        emit_u16(s, 1);
+                        /* set the 'this' value */
+                        emit_op(s, OP_dup);
+                        emit_op(s, OP_scope_put_var_init);
+                        emit_atom(s, JS_ATOM_this);
+                        emit_u16(s, 0);
+
+                        emit_class_field_init(s);
+                    } else if (call_type == FUNC_CALL_NEW) {
+                        /* obj func array -> func obj array */
+                        emit_op(s, OP_perm3);
+                        emit_op(s, OP_apply);
+                        emit_u16(s, 1);
+                    } else {
+                        /* func array -> func undef array */
+                        emit_op(s, OP_undefined);
+                        emit_op(s, OP_swap);
+                        emit_op(s, OP_apply);
+                        emit_u16(s, 0);
+                    }
+                    break;
+                }
+            } else {
+                if (next_token(s))
+                    return -1;
+            emit_func_call:
+                switch(opcode) {
+                case OP_get_field:
+                case OP_scope_get_private_field:
+                case OP_get_array_el:
+                case OP_scope_get_ref:
+                    emit_op(s, OP_call_method);
+                    emit_u16(s, arg_count);
+                    break;
+                case OP_eval:
+                    emit_op(s, OP_eval);
+                    emit_u16(s, arg_count);
+                    emit_u16(s, fd->scope_level);
+                    fd->has_eval_call = TRUE;
+                    break;
+                default:
+                    if (call_type == FUNC_CALL_SUPER_CTOR) {
+                        emit_op(s, OP_call_constructor);
+                        emit_u16(s, arg_count);
+
+                        /* set the 'this' value */
+                        emit_op(s, OP_dup);
+                        emit_op(s, OP_scope_put_var_init);
+                        emit_atom(s, JS_ATOM_this);
+                        emit_u16(s, 0);
+
+                        emit_class_field_init(s);
+                    } else if (call_type == FUNC_CALL_NEW) {
+                        emit_op(s, OP_call_constructor);
+                        emit_u16(s, arg_count);
+                    } else {
+                        emit_op(s, OP_call);
+                        emit_u16(s, arg_count);
+                    }
+                    break;
+                }
+            }
+            call_type = FUNC_CALL_NORMAL;
+        } else if (s->token.val == '.') {
+            if (next_token(s))
+                return -1;
+            if (s->token.val == TOK_PRIVATE_NAME) {
+                /* private class field */
+                if (get_prev_opcode(fd) == OP_get_super) {
+                    return js_parse_error(s, "private class field forbidden after super");
+                }
+                emit_op(s, OP_scope_get_private_field);
+                emit_atom(s, s->token.u.ident.atom);
+                emit_u16(s, s->cur_func->scope_level);
+            } else {
+                if (!token_is_ident(s->token.val)) {
+                    return js_parse_error(s, "expecting field name");
+                }
+                if (get_prev_opcode(fd) == OP_get_super) {
+                    JSValue val;
+                    int ret;
+                    val = JS_AtomToValue(s->ctx, s->token.u.ident.atom);
+                    ret = emit_push_const(s, val, 1);
+                    JS_FreeValue(s->ctx, val);
+                    if (ret)
+                        return -1;
+                    emit_op(s, OP_get_super_value);
+                } else {
+                    emit_op(s, OP_get_field);
+                    emit_atom(s, s->token.u.ident.atom);
+                }
+            }
+            if (next_token(s))
+                return -1;
+        } else if (s->token.val == '[') {
+            int prev_op;
+            if (next_token(s))
+                return -1;
+            prev_op = get_prev_opcode(fd);
+            if (js_parse_expr(s))
+                return -1;
+            if (js_parse_expect(s, ']'))
+                return -1;
+            if (prev_op == OP_get_super) {
+                emit_op(s, OP_get_super_value);
+            } else {
+                emit_op(s, OP_get_array_el);
+            }
+        } else if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
+            int opcode, op, scope, label;
+            JSAtom name;
+            op = s->token.val;
+            if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
+                return -1;
+            emit_op(s, OP_post_dec + op - TOK_DEC);
+            put_lvalue(s, opcode, scope, name, label, TRUE);
+            if (next_token(s))
+                return -1;
+        } else {
+            break;
+        }
+    }
+    return 0;
+}
+
+static __exception int js_parse_delete(JSParseState *s)
+{
+    JSFunctionDef *fd = s->cur_func;
+    JSAtom name;
+    int opcode;
+
+    if (next_token(s))
+        return -1;
+    if (js_parse_unary(s, -1))
+        return -1;
+    switch(opcode = get_prev_opcode(fd)) {
+    case OP_get_field:
+        {
+            JSValue val;
+            int ret;
+
+            name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+            fd->byte_code.size = fd->last_opcode_pos;
+            fd->last_opcode_pos = -1;
+            val = JS_AtomToValue(s->ctx, name);
+            ret = emit_push_const(s, val, 1);
+            JS_FreeValue(s->ctx, val);
+            JS_FreeAtom(s->ctx, name);
+            if (ret)
+                return ret;
+        }
+        goto do_delete;
+    case OP_get_array_el:
+        fd->byte_code.size = fd->last_opcode_pos;
+        fd->last_opcode_pos = -1;
+    do_delete:
+        emit_op(s, OP_delete);
+        break;
+    case OP_scope_get_var:
+        /* 'delete this': this is not a reference */
+        name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
+        if (name == JS_ATOM_this || name == JS_ATOM_new_target)
+            goto ret_true;
+        if (fd->js_mode & JS_MODE_STRICT) {
+            return js_parse_error(s, "cannot delete a direct reference in strict mode");
+        } else {
+            fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_delete_var;
+        }
+        break;
+    case OP_scope_get_private_field:
+        return js_parse_error(s, "cannot delete a private class field");
+    case OP_get_super_value:
+        emit_op(s, OP_throw_var);
+        emit_atom(s, JS_ATOM_NULL);
+        emit_u8(s, JS_THROW_VAR_DELETE_SUPER);
+        break;
+    default:
+    ret_true:
+        emit_op(s, OP_drop);
+        emit_op(s, OP_push_true);
+        break;
+    }
+    return 0;
+}
+
+static __exception int js_parse_unary(JSParseState *s, int exponentiation_flag)
+{
+    int op;
+
+    switch(s->token.val) {
+    case '+':
+    case '-':
+    case '!':
+    case '~':
+    case TOK_VOID:
+        op = s->token.val;
+        if (next_token(s))
+            return -1;
+        if (js_parse_unary(s, -1))
+            return -1;
+        switch(op) {
+        case '-':
+            emit_op(s, OP_neg);
+            break;
+        case '+':
+            emit_op(s, OP_plus);
+            break;
+        case '!':
+            emit_op(s, OP_lnot);
+            break;
+        case '~':
+            emit_op(s, OP_not);
+            break;
+        case TOK_VOID:
+            emit_op(s, OP_drop);
+            emit_op(s, OP_undefined);
+            break;
+        default:
+            abort();
+        }
+        exponentiation_flag = 0;
+        break;
+    case TOK_DEC:
+    case TOK_INC:
+        {
+            int opcode, op, scope, label;
+            JSAtom name;
+            op = s->token.val;
+            if (next_token(s))
+                return -1;
+            /* XXX: should parse LeftHandSideExpression */
+            if (js_parse_unary(s, 0))
+                return -1;
+            if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
+                return -1;
+            emit_op(s, OP_dec + op - TOK_DEC);
+            put_lvalue(s, opcode, scope, name, label, FALSE);
+        }
+        break;
+    case TOK_TYPEOF:
+        {
+            JSFunctionDef *fd;
+            if (next_token(s))
+                return -1;
+            if (js_parse_unary(s, -1))
+                return -1;
+            /* reference access should not return an exception, so we
+               patch the get_var */
+            fd = s->cur_func;
+            if (get_prev_opcode(fd) == OP_scope_get_var) {
+                fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_var_undef;
+            }
+            emit_op(s, OP_typeof);
+            exponentiation_flag = 0;
+        }
+        break;
+    case TOK_DELETE:
+        if (js_parse_delete(s))
+            return -1;
+        exponentiation_flag = 0;
+        break;
+    case TOK_AWAIT:
+        if (!(s->cur_func->func_kind & JS_FUNC_ASYNC))
+            return js_parse_error(s, "unexpected 'await' keyword");
+        if (!s->cur_func->in_function_body)
+            return js_parse_error(s, "await in default expression");
+        if (next_token(s))
+            return -1;
+        if (js_parse_unary(s, -1))
+            return -1;
+        emit_op(s, OP_await);
+        exponentiation_flag = 0;
+        break;
+    default:
+        if (js_parse_postfix_expr(s, TRUE))
+            return -1;
+        break;
+    }
+    if (exponentiation_flag) {
+#ifdef CONFIG_BIGNUM
+        if (s->token.val == TOK_POW || s->token.val == TOK_MATH_POW) {
+            /* Extended exponentiation syntax rules: we extend the ES7
+               grammar in order to have more intuitive semantics:
+               -2**2 evaluates to -4. */
+            if (!(s->cur_func->js_mode & JS_MODE_MATH)) {
+                if (exponentiation_flag < 0) {
+                    JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'");
+                    return -1;
+                }
+            }
+            if (next_token(s))
+                return -1;
+            if (js_parse_unary(s, 1))
+                return -1;
+            if (s->cur_func->js_mode & JS_MODE_MATH)
+                emit_op(s, OP_math_pow);
+            else
+                emit_op(s, OP_pow);
+        }
+#else
+        if (s->token.val == TOK_POW) {
+            /* Strict ES7 exponentiation syntax rules: To solve
+               conficting semantics between different implementations
+               regarding the precedence of prefix operators and the
+               postifx exponential, ES7 specifies that -2**2 is a
+               syntax error. */
+            if (exponentiation_flag < 0) {
+                JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'");
+                return -1;
+            }
+            if (next_token(s))
+                return -1;
+            if (js_parse_unary(s, 1))
+                return -1;
+            emit_op(s, OP_pow);
+        }
+#endif
+    }
+    return 0;
+}
+
+static __exception int js_parse_expr_binary(JSParseState *s, int level,
+                                            BOOL in_accepted)
+{
+    int op, opcode;
+
+    if (level == 0) {
+        return js_parse_unary(s, 1);
+    }
+    if (js_parse_expr_binary(s, level - 1, in_accepted))
+        return -1;
+    for(;;) {
+        op = s->token.val;
+        switch(level) {
+        case 1:
+            switch(op) {
+            case '*':
+                opcode = OP_mul;
+                break;
+            case '/':
+#ifdef CONFIG_BIGNUM
+                if (s->cur_func->js_mode & JS_MODE_MATH)
+                    opcode = OP_math_div;
+                else
+#endif
+                    opcode = OP_div;
+                break;
+            case '%':
+#ifdef CONFIG_BIGNUM
+                if (s->cur_func->js_mode & JS_MODE_MATH)
+                    opcode = OP_math_mod;
+                else
+#endif
+                    opcode = OP_mod;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 2:
+            switch(op) {
+            case '+':
+                opcode = OP_add;
+                break;
+            case '-':
+                opcode = OP_sub;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 3:
+            switch(op) {
+            case TOK_SHL:
+                opcode = OP_shl;
+                break;
+            case TOK_SAR:
+                opcode = OP_sar;
+                break;
+            case TOK_SHR:
+                opcode = OP_shr;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 4:
+            switch(op) {
+            case '<':
+                opcode = OP_lt;
+                break;
+            case '>':
+                opcode = OP_gt;
+                break;
+            case TOK_LTE:
+                opcode = OP_lte;
+                break;
+            case TOK_GTE:
+                opcode = OP_gte;
+                break;
+            case TOK_INSTANCEOF:
+                opcode = OP_instanceof;
+                break;
+            case TOK_IN:
+                if (in_accepted) {
+                    opcode = OP_in;
+                } else {
+                    return 0;
+                }
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 5:
+            switch(op) {
+            case TOK_EQ:
+                opcode = OP_eq;
+                break;
+            case TOK_NEQ:
+                opcode = OP_neq;
+                break;
+            case TOK_STRICT_EQ:
+                opcode = OP_strict_eq;
+                break;
+            case TOK_STRICT_NEQ:
+                opcode = OP_strict_neq;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 6:
+            switch(op) {
+            case '&':
+                opcode = OP_and;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 7:
+            switch(op) {
+            case '^':
+                opcode = OP_xor;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        case 8:
+            switch(op) {
+            case '|':
+                opcode = OP_or;
+                break;
+            default:
+                return 0;
+            }
+            break;
+        default:
+            abort();
+        }
+        if (next_token(s))
+            return -1;
+        if (js_parse_expr_binary(s, level - 1, in_accepted))
+            return -1;
+        emit_op(s, opcode);
+    }
+    return 0;
+}
+
+static __exception int js_parse_logical_and_or(JSParseState *s, int op,
+                                               BOOL in_accepted)
+{
+    int label1;
+
+    if (op == TOK_LAND) {
+        if (js_parse_expr_binary(s, 8, in_accepted))
+            return -1;
+    } else {
+        if (js_parse_logical_and_or(s, TOK_LAND, in_accepted))
+            return -1;
+    }
+    if (s->token.val == op) {
+        label1 = new_label(s);
+
+        for(;;) {
+            if (next_token(s))
+                return -1;
+            emit_op(s, OP_dup);
+            emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, label1);
+            emit_op(s, OP_drop);
+
+            if (op == TOK_LAND) {
+                if (js_parse_expr_binary(s, 8, in_accepted))
+                    return -1;
+            } else {
+                if (js_parse_logical_and_or(s, TOK_LAND, in_accepted))
+                    return -1;
+            }
+            if (s->token.val != op)
+                break;
+        }
+
+        emit_label(s, label1);
+    }
+    return 0;
+}
+
+static __exception int js_parse_cond_expr(JSParseState *s, BOOL in_accepted)
+{
+    int label1, label2;
+
+    if (js_parse_logical_and_or(s, TOK_LOR, in_accepted))
+        return -1;
+    if (s->token.val == '?') {
+        if (next_token(s))
+            return -1;
+        label1 = emit_goto(s, OP_if_false, -1);
+
+        if (js_parse_assign_expr(s, TRUE))
+            return -1;
+        if (js_parse_expect(s, ':'))
+            return -1;
+
+        label2 = emit_goto(s, OP_goto, -1);
+
+        emit_label(s, label1);
+
+        if (js_parse_assign_expr(s, in_accepted))
+            return -1;
+
+        emit_label(s, label2);
+    }
+    return 0;
+}
+
+static void emit_return(JSParseState *s, BOOL hasval);
+
+static __exception int js_parse_assign_expr(JSParseState *s, BOOL in_accepted)
+{
+    int opcode, op, scope;
+    JSAtom name0 = JS_ATOM_NULL;
+    JSAtom name;
+
+    if (s->token.val == TOK_YIELD) {
+        BOOL is_star = FALSE;
+        if (!(s->cur_func->func_kind & JS_FUNC_GENERATOR))
+            return js_parse_error(s, "unexpected 'yield' keyword");
+        if (!s->cur_func->in_function_body)
+            return js_parse_error(s, "yield in default expression");
+        if (next_token(s))
+            return -1;
+        /* XXX: is there a better method to detect 'yield' without
+           parameters ? */
+        if (s->token.val != ';' && s->token.val != ')' &&
+            s->token.val != ']' && s->token.val != '}' &&
+            s->token.val != ',' && s->token.val != ':' && !s->got_lf) {
+            if (s->token.val == '*') {
+                is_star = TRUE;
+                if (next_token(s))
+                    return -1;
+            }
+            if (js_parse_assign_expr(s, in_accepted))
+                return -1;
+        } else {
+            emit_op(s, OP_undefined);
+        }
+        if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
+            int label_loop, label_return, label_next;
+            int label_return1, label_yield, label_throw, label_throw1;
+            int label_throw2;
+
+            if (is_star) {
+                label_loop = new_label(s);
+                label_yield = new_label(s);
+
+                emit_op(s, OP_for_await_of_start);
+
+                /* remove the catch offset (XXX: could avoid pushing back
+                   undefined) */
+                emit_op(s, OP_drop);
+                emit_op(s, OP_undefined);
+
+                emit_op(s, OP_undefined); /* initial value */
+
+                emit_label(s, label_loop);
+                emit_op(s, OP_async_iterator_next);
+                emit_op(s, OP_await);
+                emit_op(s, OP_iterator_get_value_done);
+                label_next = emit_goto(s, OP_if_true, -1); /* end of loop */
+                emit_op(s, OP_await);
+                emit_label(s, label_yield);
+                emit_op(s, OP_async_yield_star);
+                emit_op(s, OP_dup);
+                label_return = emit_goto(s, OP_if_true, -1);
+                emit_op(s, OP_drop);
+                emit_goto(s, OP_goto, label_loop);
+
+                emit_label(s, label_return);
+                emit_op(s, OP_push_i32);
+                emit_u32(s, 2);
+                emit_op(s, OP_strict_eq);
+                label_throw = emit_goto(s, OP_if_true, -1);
+
+                /* return handling */
+                emit_op(s, OP_await);
+                emit_op(s, OP_async_iterator_get);
+                emit_u8(s, 0);
+                label_return1 = emit_goto(s, OP_if_true, -1);
+                emit_op(s, OP_await);
+                emit_op(s, OP_iterator_get_value_done);
+                /* XXX: the spec does not indicate that an await should be
+                   performed in case done = true, but the tests assume it */
+                emit_goto(s, OP_if_false, label_yield);
+
+                emit_label(s, label_return1);
+                emit_op(s, OP_nip);
+                emit_op(s, OP_nip);
+                emit_op(s, OP_nip);
+                emit_return(s, TRUE);
+
+                /* throw handling */
+                emit_label(s, label_throw);
+                emit_op(s, OP_async_iterator_get);
+                emit_u8(s, 1);
+                label_throw1 = emit_goto(s, OP_if_true, -1);
+                emit_op(s, OP_await);
+                emit_op(s, OP_iterator_get_value_done);
+                emit_goto(s, OP_if_false, label_yield);
+                /* XXX: the spec does not indicate that an await should be
+                   performed in case done = true, but the tests assume it */
+                emit_op(s, OP_await);
+                emit_goto(s, OP_goto, label_next);
+                /* close the iterator and throw a type error exception */
+                emit_label(s, label_throw1);
+                emit_op(s, OP_async_iterator_get);
+                emit_u8(s, 0);
+                label_throw2 = emit_goto(s, OP_if_true, -1);
+                emit_op(s, OP_await);
+                emit_label(s, label_throw2);
+                emit_op(s, OP_async_iterator_get);
+                emit_u8(s, 2); /* throw the type error exception */
+                emit_op(s, OP_drop); /* never reached */
+
+                emit_label(s, label_next);
+                emit_op(s, OP_nip); /* keep the value associated with
+                                       done = true */
+                emit_op(s, OP_nip);
+                emit_op(s, OP_nip);
+            } else {
+                emit_op(s, OP_await);
+                emit_op(s, OP_yield);
+                label_next = emit_goto(s, OP_if_false, -1);
+                emit_return(s, TRUE);
+                emit_label(s, label_next);
+            }
+        } else {
+            int label_next;
+            if (is_star) {
+                emit_op(s, OP_for_of_start);
+                emit_op(s, OP_drop);    /* drop the catch offset */
+                emit_op(s, OP_yield_star);
+            } else {
+                emit_op(s, OP_yield);
+            }
+            label_next = emit_goto(s, OP_if_false, -1);
+            emit_return(s, TRUE);
+            emit_label(s, label_next);
+        }
+        return 0;
+    }
+    if (s->token.val == TOK_IDENT) {
+        /* name0 is used to check for OP_set_name pattern, not duplicated */
+        name0 = s->token.u.ident.atom;
+    }
+    if (js_parse_cond_expr(s, in_accepted))
+        return -1;
+
+    op = s->token.val;
+    if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) {
+        int label;
+        if (next_token(s))
+            return -1;
+        if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0)
+            return -1;
+
+        if (js_parse_assign_expr(s, in_accepted)) {
+            JS_FreeAtom(s->ctx, name);
+            return -1;
+        }
+
+        if (op == '=') {
+            if (opcode == OP_get_ref_value && name == name0) {
+                set_object_name(s, name);
+            }
+        } else {
+            static const uint8_t assign_opcodes[] = {
+                OP_mul, OP_div, OP_mod, OP_add, OP_sub,
+                OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or,
+#ifdef CONFIG_BIGNUM
+                OP_pow,
+#endif
+                OP_pow,
+            };
+            op = assign_opcodes[op - TOK_MUL_ASSIGN];
+#ifdef CONFIG_BIGNUM
+            if (s->cur_func->js_mode & JS_MODE_MATH) {
+                if (op == OP_div)
+                    op = OP_math_div;
+                else if (op == OP_mod)
+                    op = OP_math_mod;
+                else if (op == OP_pow)
+                    op = OP_math_pow;
+            }
+#endif
+            emit_op(s, op);
+        }
+        put_lvalue(s, opcode, scope, name, label, FALSE);
+    }
+    return 0;
+}
+
+static __exception int js_parse_expr2(JSParseState *s, BOOL in_accepted)
+{
+    BOOL comma = FALSE;
+    for(;;) {
+        if (js_parse_assign_expr(s, in_accepted))
+            return -1;
+        if (comma) {
+            /* prevent get_lvalue from using the last expression
+               as an lvalue. This also prevents the conversion of
+               of get_var to get_ref for method lookup in function
+               call inside `with` statement.
+             */
+            s->cur_func->last_opcode_pos = -1;
+        }
+        if (s->token.val != ',')
+            break;
+        comma = TRUE;
+        if (next_token(s))
+            return -1;
+        emit_op(s, OP_drop);
+    }
+    return 0;
+}
+
+static __exception int js_parse_expr(JSParseState *s)
+{
+    return js_parse_expr2(s, TRUE);
+}
+
+static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
+                             JSAtom label_name,
+                             int label_break, int label_cont,
+                             int drop_count)
+{
+    be->prev = fd->top_break;
+    fd->top_break = be;
+    be->label_name = label_name;
+    be->label_break = label_break;
+    be->label_cont = label_cont;
+    be->drop_count = drop_count;
+    be->label_finally = -1;
+    be->scope_level = fd->scope_level;
+    be->has_iterator = FALSE;
+}
+
+static void pop_break_entry(JSFunctionDef *fd)
+{
+    BlockEnv *be;
+    be = fd->top_break;
+    fd->top_break = be->prev;
+}
+
+static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
+{
+    BlockEnv *top;
+    int i, scope_level;
+
+    scope_level = s->cur_func->scope_level;
+    top = s->cur_func->top_break;
+    while (top != NULL) {
+        close_scopes(s, scope_level, top->scope_level);
+        scope_level = top->scope_level;
+        if (is_cont &&
+            top->label_cont != -1 &&
+            (name == JS_ATOM_NULL || top->label_name == name)) {
+            /* continue stays inside the same block */
+            emit_goto(s, OP_goto, top->label_cont);
+            return 0;
+        }
+        if (!is_cont &&
+            top->label_break != -1 &&
+            (name == JS_ATOM_NULL || top->label_name == name)) {
+            emit_goto(s, OP_goto, top->label_break);
+            return 0;
+        }
+        i = 0;
+        if (top->has_iterator) {
+            emit_op(s, OP_iterator_close);
+            i += 3;
+        }
+        for(; i < top->drop_count; i++)
+            emit_op(s, OP_drop);
+        if (top->label_finally != -1) {
+            /* must push dummy value to keep same stack depth */
+            emit_op(s, OP_undefined);
+            emit_goto(s, OP_gosub, top->label_finally);
+            emit_op(s, OP_drop);
+        }
+        top = top->prev;
+    }
+    if (name == JS_ATOM_NULL) {
+        if (is_cont)
+            return js_parse_error(s, "continue must be inside loop");
+        else
+            return js_parse_error(s, "break must be inside loop or switch");
+    } else {
+        return js_parse_error(s, "break/continue label not found");
+    }
+}
+
+/* execute the finally blocks before return */
+static void emit_return(JSParseState *s, BOOL hasval)
+{
+    BlockEnv *top;
+    int drop_count;
+
+    drop_count = 0;
+    top = s->cur_func->top_break;
+    while (top != NULL) {
+        /* XXX: emit the appropriate OP_leave_scope opcodes? Probably not
+           required as all local variables will be closed upon returning
+           from JS_CallInternal, but not in the same order. */
+        if (top->has_iterator) {
+            /* with 'yield', the exact number of OP_drop to emit is
+               unknown, so we use a specific operation to look for
+               the catch offset */
+            if (!hasval) {
+                emit_op(s, OP_undefined);
+                hasval = TRUE;
+            }
+            emit_op(s, OP_iterator_close_return);
+            if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
+                int label_next;
+                emit_op(s, OP_async_iterator_close);
+                label_next = emit_goto(s, OP_if_true, -1);
+                emit_op(s, OP_await);
+                emit_label(s, label_next);
+                emit_op(s, OP_drop);
+            } else {
+                emit_op(s, OP_iterator_close);
+            }
+            drop_count = -3;
+        }
+        drop_count += top->drop_count;
+        if (top->label_finally != -1) {
+            while(drop_count) {
+                /* must keep the stack top if hasval */
+                emit_op(s, hasval ? OP_nip : OP_drop);
+                drop_count--;
+            }
+            if (!hasval) {
+                /* must push return value to keep same stack size */
+                emit_op(s, OP_undefined);
+                hasval = TRUE;
+            }
+            emit_goto(s, OP_gosub, top->label_finally);
+        }
+        top = top->prev;
+    }
+    if (s->cur_func->is_derived_class_constructor) {
+        int label_return;
+
+        /* 'this' can be uninitialized, so it may be accessed only if
+           the derived class constructor does not return an object */
+        if (hasval) {
+            emit_op(s, OP_check_ctor_return);
+            label_return = emit_goto(s, OP_if_false, -1);
+            emit_op(s, OP_drop);
+        } else {
+            label_return = -1;
+        }
+
+        emit_op(s, OP_scope_get_var);
+        emit_atom(s, JS_ATOM_this);
+        emit_u16(s, 0);
+
+        emit_label(s, label_return);
+        emit_op(s, OP_return);
+    } else if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
+        if (!hasval) {
+            emit_op(s, OP_undefined);
+        } else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
+            emit_op(s, OP_await);
+        }
+        emit_op(s, OP_return_async);
+    } else {
+        emit_op(s, hasval ? OP_return : OP_return_undef);
+    }
+}
+
+#define DECL_MASK_FUNC  (1 << 0) /* allow normal function declaration */
+#define DECL_MASK_LABEL (1 << 1) /* allow labelled statement */
+#define DECL_MASK_OTHER (1 << 2) /* all other declarations */
+#define DECL_MASK_ALL   (DECL_MASK_FUNC | DECL_MASK_LABEL | DECL_MASK_OTHER)
+
+static __exception int js_parse_statement_or_decl(JSParseState *s,
+                                                  int decl_mask);
+
+static __exception int js_parse_statement(JSParseState *s)
+{
+    return js_parse_statement_or_decl(s, 0);
+}
+
+static __exception int js_parse_block(JSParseState *s)
+{
+    if (js_parse_expect(s, '{'))
+        return -1;
+    if (s->token.val != '}') {
+        push_scope(s);
+        for(;;) {
+            if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
+                return -1;
+            if (s->token.val == '}')
+                break;
+        }
+        pop_scope(s);
+    }
+    if (next_token(s))
+        return -1;
+    return 0;
+}
+
+static __exception int js_parse_var(JSParseState *s, BOOL in_accepted, int tok,
+                                    BOOL export_flag)
+{
+    JSContext *ctx = s->ctx;
+    JSFunctionDef *fd = s->cur_func;
+    JSAtom name = JS_ATOM_NULL;
+
+    for (;;) {
+        if (s->token.val == TOK_IDENT) {
+            if (s->token.u.ident.is_reserved) {
+                return js_parse_error_reserved_identifier(s);
+            }
+            name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) {
+                js_parse_error(s, "'let' is not a valid lexical identifier");
+                goto var_error;
+            }
+            if (next_token(s))
+                goto var_error;
+            if (js_define_var(s, name, tok))
+                goto var_error;
+            if (export_flag) {
+                if (!add_export_entry(s, s->cur_func->module, name, name,
+                                      JS_EXPORT_TYPE_LOCAL))
+                    goto var_error;
+            }
+
+            if (s->token.val == '=') {
+                if (next_token(s))
+                    goto var_error;
+                if (tok == TOK_VAR) {
+                    /* Must make a reference for proper `with` semantics */
+                    int opcode, scope, label;
+                    JSAtom name1;
+
+                    emit_op(s, OP_scope_get_var);
+                    emit_atom(s, name);
+                    emit_u16(s, fd->scope_level);
+                    if (get_lvalue(s, &opcode, &scope, &name1, &label, NULL, FALSE, '=') < 0)
+                        goto var_error;
+                    if (js_parse_assign_expr(s, in_accepted)) {
+                        JS_FreeAtom(ctx, name1);
+                        goto var_error;
+                    }
+                    set_object_name(s, name);
+                    put_lvalue(s, opcode, scope, name1, label, FALSE);
+                    emit_op(s, OP_drop);
+                } else {
+                    if (js_parse_assign_expr(s, in_accepted))
+                        goto var_error;
+                    set_object_name(s, name);
+                    emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ?
+                        OP_scope_put_var_init : OP_scope_put_var);
+                    emit_atom(s, name);
+                    emit_u16(s, fd->scope_level);
+                }
+            } else {
+                if (tok == TOK_CONST) {
+                    js_parse_error(s, "missing initializer for const variable");
+                    goto var_error;
+                }
+                if (tok == TOK_LET) {
+                    /* initialize lexical variable upon entering its scope */
+                    emit_op(s, OP_undefined);
+                    emit_op(s, OP_scope_put_var_init);
+                    emit_atom(s, name);
+                    emit_u16(s, fd->scope_level);
+                }
+            }
+            JS_FreeAtom(ctx, name);
+        } else {
+            int skip_bits;
+            if ((s->token.val == '[' || s->token.val == '{')
+            &&  js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
+                emit_op(s, OP_undefined);
+                if (js_parse_destructing_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE))
+                    return -1;
+            } else {
+                return js_parse_error(s, "variable name expected");
+            }
+        }
+        if (s->token.val != ',')
+            break;
+        if (next_token(s))
+            return -1;
+    }
+    return 0;
+
+ var_error:
+    JS_FreeAtom(ctx, name);
+    return -1;
+}
+
+/* test if the current token is a label. Use simplistic look-ahead scanner */
+static BOOL is_label(JSParseState *s)
+{
+    return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
+            peek_token(s, FALSE) == ':');
+}
+
+/* test if the current token is a let keyword. Use simplistic look-ahead scanner */
+static int is_let(JSParseState *s, int decl_mask)
+{
+    int res = FALSE;
+
+    if (token_is_pseudo_keyword(s, JS_ATOM_let)) {
+#if 1
+        JSParsePos pos;
+        js_parse_get_pos(s, &pos);
+        for (;;) {
+            if (next_token(s)) {
+                res = -1;
+                break;
+            }
+            if (s->token.val == '[') {
+                /* let [ is a syntax restriction:
+                   it never introduces an ExpressionStatement */
+                res = TRUE;
+                break;
+            }
+            if (s->token.val == '{' ||
+                (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) ||
+                s->token.val == TOK_LET ||
+                s->token.val == TOK_YIELD ||
+                s->token.val == TOK_AWAIT) {
+                /* Check for possible ASI if not scanning for Declaration */
+                /* XXX: should also check that `{` introduces a BindingPattern,
+                   but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */
+                if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) {
+                    res = TRUE;
+                    break;
+                }
+                break;
+            }
+            break;
+        }
+        if (js_parse_seek_token(s, &pos)) {
+            res = -1;
+        }
+#else
+        int tok = peek_token(s, TRUE);
+        if (tok == '{' || tok == TOK_IDENT || peek_token(s, FALSE) == '[') {
+            res = TRUE;
+        }
+#endif
+    }
+    return res;
+}
+
+/* XXX: handle IteratorClose when exiting the loop before the
+   enumeration is done */
+static __exception int js_parse_for_in_of(JSParseState *s, int label_name,
+                                          BOOL is_async)
+{
+    JSContext *ctx = s->ctx;
+    JSFunctionDef *fd = s->cur_func;
+    JSAtom var_name;
+    BOOL has_initializer, is_for_of, has_destructuring;
+    int tok, tok1, opcode, scope, block_scope_level;
+    int label_next, label_expr, label_cont, label_body, label_break;
+    int pos_next, pos_expr;
+    BlockEnv break_entry;
+
+    has_initializer = FALSE;
+    has_destructuring = FALSE;
+    is_for_of = FALSE;
+    block_scope_level = fd->scope_level;
+    label_cont = new_label(s);
+    label_body = new_label(s);
+    label_break = new_label(s);
+    label_next = new_label(s);
+
+    /* create scope for the lexical variables declared in the enumeration
+       expressions. XXX: Not completely correct because of weird capturing
+       semantics in `for (i of o) a.push(function(){return i})` */
+    push_scope(s);
+
+    /* local for_in scope starts here so individual elements
+       can be closed in statement. */
+    push_break_entry(s->cur_func, &break_entry,
+                     label_name, label_break, label_cont, 1);
+    break_entry.scope_level = block_scope_level;
+
+    label_expr = emit_goto(s, OP_goto, -1);
+
+    pos_next = s->cur_func->byte_code.size;
+    emit_label(s, label_next);
+
+    tok = s->token.val;
+    switch (is_let(s, DECL_MASK_OTHER)) {
+    case TRUE:
+        tok = TOK_LET;
+        break;
+    case FALSE:
+        break;
+    default:
+        return -1;
+    }
+    if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
+        if (next_token(s))
+            return -1;
+
+        if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
+            if (s->token.val == '[' || s->token.val == '{') {
+                if (js_parse_destructing_element(s, tok, 0, TRUE, -1, FALSE))
+                    return -1;
+                has_destructuring = TRUE;
+            } else {
+                return js_parse_error(s, "variable name expected");
+            }
+            var_name = JS_ATOM_NULL;
+        } else {
+            var_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            if (next_token(s)) {
+                JS_FreeAtom(s->ctx, var_name);
+                return -1;
+            }
+            if (js_define_var(s, var_name, tok)) {
+                JS_FreeAtom(s->ctx, var_name);
+                return -1;
+            }
+            emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ?
+                    OP_scope_put_var_init : OP_scope_put_var);
+            emit_atom(s, var_name);
+            emit_u16(s, fd->scope_level);
+        }
+    } else {
+        int skip_bits;
+        if ((s->token.val == '[' || s->token.val == '{')
+        &&  ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN || tok1 == TOK_OF)) {
+            if (js_parse_destructing_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE))
+                return -1;
+        } else {
+            int lvalue_label, depth;
+            if (js_parse_postfix_expr(s, TRUE))
+                return -1;
+            if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label,
+                           &depth, FALSE, TOK_FOR))
+                return -1;
+            /* swap value and lvalue object and store it into lvalue object */
+            /* enum_rec val [depth] -- enum_rec */
+            switch(depth) {
+            case 1:
+                emit_op(s, OP_swap);
+                break;
+            case 2:
+                emit_op(s, OP_rot3l);
+                break;
+            case 3:
+                emit_op(s, OP_rot4l);
+                break;
+            default:
+                abort();
+            }
+            put_lvalue_nokeep(s, opcode, scope, var_name, lvalue_label,
+                              TOK_FOR /* not used */);
+        }
+        var_name = JS_ATOM_NULL;
+    }
+    emit_goto(s, OP_goto, label_body);
+
+    pos_expr = s->cur_func->byte_code.size;
+    emit_label(s, label_expr);
+    if (s->token.val == '=') {
+        /* XXX: potential scoping issue if inside `with` statement */
+        has_initializer = TRUE;
+        /* parse and evaluate initializer prior to evaluating the
+           object (only used with "for in" with a non lexical variable
+           in non strict mode */
+        if (next_token(s) || js_parse_assign_expr(s, FALSE)) {
+            JS_FreeAtom(ctx, var_name);
+            return -1;
+        }
+        if (var_name != JS_ATOM_NULL) {
+            emit_op(s, OP_scope_put_var);
+            emit_atom(s, var_name);
+            emit_u16(s, fd->scope_level);
+        }
+    }
+    JS_FreeAtom(ctx, var_name);
+
+    if (token_is_pseudo_keyword(s, JS_ATOM_of)) {
+        break_entry.has_iterator = is_for_of = TRUE;
+        break_entry.drop_count += 2;
+        if (has_initializer)
+            goto initializer_error;
+    } else if (s->token.val == TOK_IN) {
+        if (is_async)
+            return js_parse_error(s, "'for await' loop should be used with 'of'");
+        if (has_initializer &&
+            (tok != TOK_VAR || (fd->js_mode & JS_MODE_STRICT) ||
+             has_destructuring)) {
+        initializer_error:
+            return js_parse_error(s, "a declaration in the head of a for-%s loop can't have an initializer",
+                                  is_for_of ? "of" : "in");
+        }
+    } else {
+        return js_parse_error(s, "expected 'of' or 'in' in for control expression");
+    }
+    if (next_token(s))
+        return -1;
+    if (is_for_of) {
+        if (js_parse_assign_expr(s, TRUE))
+            return -1;
+    } else {
+        if (js_parse_expr(s))
+            return -1;
+    }
+    /* close the scope after having evaluated the expression so that
+       the TDZ values are in the closures */
+    close_scopes(s, s->cur_func->scope_level, block_scope_level);
+    if (is_for_of) {
+        if (is_async)
+            emit_op(s, OP_for_await_of_start);
+        else
+            emit_op(s, OP_for_of_start);
+        /* on stack: enum_rec */
+    } else {
+        emit_op(s, OP_for_in_start);
+        /* on stack: enum_obj */
+    }
+    emit_goto(s, OP_goto, label_cont);
+
+    if (js_parse_expect(s, ')'))
+        return -1;
+
+    if (OPTIMIZE) {
+        /* move the `next` code here */
+        DynBuf *bc = &s->cur_func->byte_code;
+        int chunk_size = pos_expr - pos_next;
+        int offset = bc->size - pos_next;
+        int i;
+        dbuf_realloc(bc, bc->size + chunk_size);
+        dbuf_put(bc, bc->buf + pos_next, chunk_size);
+        memset(bc->buf + pos_next, OP_nop, chunk_size);
+        /* `next` part ends with a goto */
+        s->cur_func->last_opcode_pos = bc->size - 5;
+        /* relocate labels */
+        for (i = label_cont; i < s->cur_func->label_count; i++) {
+            LabelSlot *ls = &s->cur_func->label_slots[i];
+            if (ls->pos >= pos_next && ls->pos < pos_expr)
+                ls->pos += offset;
+        }
+    }
+
+    emit_label(s, label_body);
+    if (js_parse_statement(s))
+        return -1;
+
+    close_scopes(s, s->cur_func->scope_level, block_scope_level);
+
+    emit_label(s, label_cont);
+    if (is_for_of) {
+        if (is_async) {
+            /* call the next method */
+            emit_op(s, OP_for_await_of_next);
+            /* get the result of the promise */
+            emit_op(s, OP_await);
+            /* unwrap the value and done values */
+            emit_op(s, OP_iterator_get_value_done);
+        } else {
+            emit_op(s, OP_for_of_next);
+            emit_u8(s, 0);
+        }
+    } else {
+        emit_op(s, OP_for_in_next);
+    }
+    /* on stack: enum_rec / enum_obj value bool */
+    emit_goto(s, OP_if_false, label_next);
+    /* drop the undefined value from for_xx_next */
+    emit_op(s, OP_drop);
+
+    emit_label(s, label_break);
+    if (is_for_of) {
+        /* close and drop enum_rec */
+        emit_op(s, OP_iterator_close);
+    } else {
+        emit_op(s, OP_drop);
+    }
+    pop_break_entry(s->cur_func);
+    pop_scope(s);
+    return 0;
+}
+
+static void set_eval_ret_undefined(JSParseState *s)
+{
+    if (s->cur_func->eval_ret_idx >= 0) {
+        emit_op(s, OP_undefined);
+        emit_op(s, OP_put_loc);
+        emit_u16(s, s->cur_func->eval_ret_idx);
+    }
+}
+
+static __exception int js_parse_statement_or_decl(JSParseState *s,
+                                                  int decl_mask)
+{
+    JSContext *ctx = s->ctx;
+    JSAtom label_name;
+    int tok;
+
+    /* specific label handling */
+    /* XXX: support multiple labels on loop statements */
+    label_name = JS_ATOM_NULL;
+    if (is_label(s)) {
+        BlockEnv *be;
+
+        if (!(decl_mask & DECL_MASK_LABEL)) {
+            js_parse_error(s, "functions can only be labelled inside blocks");
+            goto fail;
+        }
+        label_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+
+        for (be = s->cur_func->top_break; be; be = be->prev) {
+            if (be->label_name == label_name) {
+                js_parse_error(s, "duplicate label name");
+                goto fail;
+            }
+        }
+
+        if (next_token(s))
+            goto fail;
+        if (js_parse_expect(s, ':'))
+            goto fail;
+        if (s->token.val != TOK_FOR
+        &&  s->token.val != TOK_DO
+        &&  s->token.val != TOK_WHILE) {
+            /* labelled regular statement */
+            int label_break, mask;
+            BlockEnv break_entry;
+
+            label_break = new_label(s);
+            push_break_entry(s->cur_func, &break_entry,
+                             label_name, label_break, -1, 0);
+            if (s->cur_func->js_mode & JS_MODE_STRICT)
+                mask = 0;
+            else
+                mask = DECL_MASK_FUNC;
+            mask |= DECL_MASK_LABEL;
+            if (js_parse_statement_or_decl(s, mask))
+                goto fail;
+            emit_label(s, label_break);
+            pop_break_entry(s->cur_func);
+            goto done;
+        }
+    }
+
+    switch(tok = s->token.val) {
+    case '{':
+        if (js_parse_block(s))
+            goto fail;
+        break;
+    case TOK_RETURN:
+        if (s->cur_func->is_eval) {
+            js_parse_error(s, "return not in a function");
+            goto fail;
+        }
+        if (next_token(s))
+            goto fail;
+        if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
+            if (js_parse_expr(s))
+                goto fail;
+            emit_return(s, TRUE);
+        } else {
+            emit_return(s, FALSE);
+        }
+        if (js_parse_expect_semi(s))
+            goto fail;
+        break;
+    case TOK_THROW:
+        if (next_token(s))
+            goto fail;
+        if (s->got_lf) {
+            js_parse_error(s, "line terminator not allowed after throw");
+            goto fail;
+        }
+        if (js_parse_expr(s))
+            goto fail;
+        emit_op(s, OP_throw);
+        if (js_parse_expect_semi(s))
+            goto fail;
+        break;
+    case TOK_LET:
+    case TOK_CONST:
+    haslet:
+        if (!(decl_mask & DECL_MASK_OTHER)) {
+            js_parse_error(s, "lexical declarations can't appear in single-statement context");
+            goto fail;
+        }
+        /* fall thru */
+    case TOK_VAR:
+        if (next_token(s))
+            goto fail;
+        if (js_parse_var(s, TRUE, tok, FALSE))
+            goto fail;
+        if (js_parse_expect_semi(s))
+            goto fail;
+        break;
+    case TOK_IF:
+        {
+            int label1, label2, mask;
+            if (next_token(s))
+                goto fail;
+            /* create a new scope for `let f;if(1) function f(){}` */
+            push_scope(s);
+            set_eval_ret_undefined(s);
+            if (js_parse_expr_paren(s))
+                goto fail;
+            label1 = emit_goto(s, OP_if_false, -1);
+            if (s->cur_func->js_mode & JS_MODE_STRICT)
+                mask = 0;
+            else
+                mask = DECL_MASK_FUNC; /* Annex B.3.4 */
+
+            if (js_parse_statement_or_decl(s, mask))
+                goto fail;
+
+            if (s->token.val == TOK_ELSE) {
+                label2 = emit_goto(s, OP_goto, -1);
+                if (next_token(s))
+                    goto fail;
+
+                emit_label(s, label1);
+                if (js_parse_statement_or_decl(s, mask))
+                    goto fail;
+
+                label1 = label2;
+            }
+            emit_label(s, label1);
+            pop_scope(s);
+        }
+        break;
+    case TOK_WHILE:
+        {
+            int label_cont, label_break;
+            BlockEnv break_entry;
+
+            label_cont = new_label(s);
+            label_break = new_label(s);
+
+            push_break_entry(s->cur_func, &break_entry,
+                             label_name, label_break, label_cont, 0);
+
+            if (next_token(s))
+                goto fail;
+
+            set_eval_ret_undefined(s);
+
+            emit_label(s, label_cont);
+            if (js_parse_expr_paren(s))
+                goto fail;
+            emit_goto(s, OP_if_false, label_break);
+
+            if (js_parse_statement(s))
+                goto fail;
+            emit_goto(s, OP_goto, label_cont);
+
+            emit_label(s, label_break);
+
+            pop_break_entry(s->cur_func);
+        }
+        break;
+    case TOK_DO:
+        {
+            int label_cont, label_break, label1;
+            BlockEnv break_entry;
+
+            label_cont = new_label(s);
+            label_break = new_label(s);
+            label1 = new_label(s);
+
+            push_break_entry(s->cur_func, &break_entry,
+                             label_name, label_break, label_cont, 0);
+
+            if (next_token(s))
+                goto fail;
+
+            emit_label(s, label1);
+
+            set_eval_ret_undefined(s);
+
+            if (js_parse_statement(s))
+                goto fail;
+
+            emit_label(s, label_cont);
+            if (js_parse_expect(s, TOK_WHILE))
+                goto fail;
+            if (js_parse_expr_paren(s))
+                goto fail;
+            /* Insert semicolon if missing */
+            if (s->token.val == ';') {
+                if (next_token(s))
+                    goto fail;
+            }
+            emit_goto(s, OP_if_true, label1);
+
+            emit_label(s, label_break);
+
+            pop_break_entry(s->cur_func);
+        }
+        break;
+    case TOK_FOR:
+        {
+            int label_cont, label_break, label_body, label_test;
+            int pos_cont, pos_body, block_scope_level;
+            BlockEnv break_entry;
+            int tok, bits;
+            BOOL is_async;
+
+            if (next_token(s))
+                goto fail;
+
+            set_eval_ret_undefined(s);
+            bits = 0;
+            is_async = FALSE;
+            if (s->token.val == '(') {
+                js_parse_skip_parens_token(s, &bits, FALSE);
+            } else if (s->token.val == TOK_AWAIT) {
+                if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) {
+                    js_parse_error(s, "for await is only valid in asynchronous functions");
+                    goto fail;
+                }
+                is_async = TRUE;
+                if (next_token(s))
+                    goto fail;
+            }
+            if (js_parse_expect(s, '('))
+                goto fail;
+
+            if (!(bits & SKIP_HAS_SEMI)) {
+                /* parse for/in or for/of */
+                if (js_parse_for_in_of(s, label_name, is_async))
+                    goto fail;
+                break;
+            }
+            block_scope_level = s->cur_func->scope_level;
+
+            /* create scope for the lexical variables declared in the initial,
+               test and increment expressions */
+            push_scope(s);
+            /* initial expression */
+            tok = s->token.val;
+            if (tok != ';') {
+                switch (is_let(s, DECL_MASK_OTHER)) {
+                case TRUE:
+                    tok = TOK_LET;
+                    break;
+                case FALSE:
+                    break;
+                default:
+                    goto fail;
+                }
+                if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
+                    if (next_token(s))
+                        goto fail;
+                    if (js_parse_var(s, FALSE, tok, FALSE))
+                        goto fail;
+                } else {
+                    if (js_parse_expr2(s, FALSE))
+                        goto fail;
+                    emit_op(s, OP_drop);
+                }
+
+                /* close the closures before the first iteration */
+                close_scopes(s, s->cur_func->scope_level, block_scope_level);
+            }
+            if (js_parse_expect(s, ';'))
+                goto fail;
+
+            label_test = new_label(s);
+            label_cont = new_label(s);
+            label_body = new_label(s);
+            label_break = new_label(s);
+
+            push_break_entry(s->cur_func, &break_entry,
+                             label_name, label_break, label_cont, 0);
+
+            /* test expression */
+            if (s->token.val == ';') {
+                /* no test expression */
+                label_test = label_body;
+            } else {
+                emit_label(s, label_test);
+                if (js_parse_expr(s))
+                    goto fail;
+                emit_goto(s, OP_if_false, label_break);
+            }
+            if (js_parse_expect(s, ';'))
+                goto fail;
+
+            if (s->token.val == ')') {
+                /* no end expression */
+                break_entry.label_cont = label_cont = label_test;
+                pos_cont = 0; /* avoid warning */
+            } else {
+                /* skip the end expression */
+                emit_goto(s, OP_goto, label_body);
+
+                pos_cont = s->cur_func->byte_code.size;
+                emit_label(s, label_cont);
+                if (js_parse_expr(s))
+                    goto fail;
+                emit_op(s, OP_drop);
+                if (label_test != label_body)
+                    emit_goto(s, OP_goto, label_test);
+            }
+            if (js_parse_expect(s, ')'))
+                goto fail;
+
+            pos_body = s->cur_func->byte_code.size;
+            emit_label(s, label_body);
+            if (js_parse_statement(s))
+                goto fail;
+
+            /* close the closures before the next iteration */
+            /* XXX: check continue case */
+            close_scopes(s, s->cur_func->scope_level, block_scope_level);
+
+            if (OPTIMIZE && label_test != label_body && label_cont != label_test) {
+                /* move the increment code here */
+                DynBuf *bc = &s->cur_func->byte_code;
+                int chunk_size = pos_body - pos_cont;
+                int offset = bc->size - pos_cont;
+                int i;
+                dbuf_realloc(bc, bc->size + chunk_size);
+                dbuf_put(bc, bc->buf + pos_cont, chunk_size);
+                memset(bc->buf + pos_cont, OP_nop, chunk_size);
+                /* increment part ends with a goto */
+                s->cur_func->last_opcode_pos = bc->size - 5;
+                /* relocate labels */
+                for (i = label_cont; i < s->cur_func->label_count; i++) {
+                    LabelSlot *ls = &s->cur_func->label_slots[i];
+                    if (ls->pos >= pos_cont && ls->pos < pos_body)
+                        ls->pos += offset;
+                }
+            } else {
+                emit_goto(s, OP_goto, label_cont);
+            }
+
+            emit_label(s, label_break);
+
+            pop_break_entry(s->cur_func);
+            pop_scope(s);
+        }
+        break;
+    case TOK_BREAK:
+    case TOK_CONTINUE:
+        {
+            int is_cont = s->token.val - TOK_BREAK;
+            int label;
+
+            if (next_token(s))
+                goto fail;
+            if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
+                label = s->token.u.ident.atom;
+            else
+                label = JS_ATOM_NULL;
+            if (emit_break(s, label, is_cont))
+                goto fail;
+            if (label != JS_ATOM_NULL) {
+                if (next_token(s))
+                    goto fail;
+            }
+            if (js_parse_expect_semi(s))
+                goto fail;
+        }
+        break;
+    case TOK_SWITCH:
+        {
+            int label_case, label_break, label1;
+            int default_label_pos;
+            BlockEnv break_entry;
+
+            if (next_token(s))
+                goto fail;
+
+            set_eval_ret_undefined(s);
+            if (js_parse_expr_paren(s))
+                goto fail;
+
+            push_scope(s);
+            label_break = new_label(s);
+            push_break_entry(s->cur_func, &break_entry,
+                             label_name, label_break, -1, 1);
+
+            if (js_parse_expect(s, '{'))
+                goto fail;
+
+            default_label_pos = -1;
+            label_case = -1;
+            while (s->token.val != '}') {
+                if (s->token.val == TOK_CASE) {
+                    label1 = -1;
+                    if (label_case >= 0) {
+                        /* skip the case if needed */
+                        label1 = emit_goto(s, OP_goto, -1);
+                    }
+                    emit_label(s, label_case);
+                    label_case = -1;
+                    for (;;) {
+                        /* parse a sequence of case clauses */
+                        if (next_token(s))
+                            goto fail;
+                        emit_op(s, OP_dup);
+                        if (js_parse_expr(s))
+                            goto fail;
+                        if (js_parse_expect(s, ':'))
+                            goto fail;
+                        emit_op(s, OP_strict_eq);
+                        if (s->token.val == TOK_CASE) {
+                            label1 = emit_goto(s, OP_if_true, label1);
+                        } else {
+                            label_case = emit_goto(s, OP_if_false, -1);
+                            emit_label(s, label1);
+                            break;
+                        }
+                    }
+                } else if (s->token.val == TOK_DEFAULT) {
+                    if (next_token(s))
+                        goto fail;
+                    if (js_parse_expect(s, ':'))
+                        goto fail;
+                    if (default_label_pos >= 0) {
+                        js_parse_error(s, "duplicate default");
+                        goto fail;
+                    }
+                    if (label_case < 0) {
+                        /* falling thru direct from switch expression */
+                        label_case = emit_goto(s, OP_goto, -1);
+                    }
+                    /* Emit a dummy label opcode. Label will be patched after
+                       the end of the switch body. Do not use emit_label(s, 0)
+                       because it would clobber label 0 address, preventing
+                       proper optimizer operation.
+                     */
+                    emit_op(s, OP_label);
+                    emit_u32(s, 0);
+                    default_label_pos = s->cur_func->byte_code.size - 4;
+                } else {
+                    if (label_case < 0) {
+                        /* falling thru direct from switch expression */
+                        js_parse_error(s, "invalid switch statement");
+                        goto fail;
+                    }
+                    if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
+                        goto fail;
+                }
+            }
+            if (js_parse_expect(s, '}'))
+                goto fail;
+            if (default_label_pos >= 0) {
+                /* Ugly patch for the the `default` label, shameful and risky */
+                put_u32(s->cur_func->byte_code.buf + default_label_pos,
+                        label_case);
+                s->cur_func->label_slots[label_case].pos = default_label_pos + 4;
+            } else {
+                emit_label(s, label_case);
+            }
+            emit_label(s, label_break);
+            emit_op(s, OP_drop); /* drop the switch expression */
+
+            pop_break_entry(s->cur_func);
+            pop_scope(s);
+        }
+        break;
+    case TOK_TRY:
+        {
+            int label_catch, label_catch2, label_finally, label_end;
+            JSAtom name;
+            BlockEnv block_env;
+
+            set_eval_ret_undefined(s);
+            if (next_token(s))
+                goto fail;
+            label_catch = new_label(s);
+            label_catch2 = new_label(s);
+            label_finally = new_label(s);
+            label_end = new_label(s);
+
+            emit_goto(s, OP_catch, label_catch);
+
+            push_break_entry(s->cur_func, &block_env,
+                             JS_ATOM_NULL, -1, -1, 1);
+            block_env.label_finally = label_finally;
+
+            if (js_parse_block(s))
+                goto fail;
+
+            pop_break_entry(s->cur_func);
+
+            if (js_is_live_code(s)) {
+                /* drop the catch offset */
+                emit_op(s, OP_drop);
+                /* must push dummy value to keep same stack size */
+                emit_op(s, OP_undefined);
+                emit_goto(s, OP_gosub, label_finally);
+                emit_op(s, OP_drop);
+
+                emit_goto(s, OP_goto, label_end);
+            }
+
+            if (s->token.val == TOK_CATCH) {
+                if (next_token(s))
+                    goto fail;
+
+                push_scope(s);  /* catch variable */
+                emit_label(s, label_catch);
+
+                if (s->token.val == '{') {
+                    /* support optional-catch-binding feature */
+                    emit_op(s, OP_drop);    /* pop the exception object */
+                } else {
+                    if (js_parse_expect(s, '('))
+                        goto fail;
+                    if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
+                        if (s->token.val == '[' || s->token.val == '{') {
+                            /* XXX: TOK_LET is not completely correct */
+                            if (js_parse_destructing_element(s, TOK_LET, 0, TRUE, -1, TRUE))
+                                goto fail;
+                        } else {
+                            js_parse_error(s, "identifier expected");
+                            goto fail;
+                        }
+                    } else {
+                        name = JS_DupAtom(ctx, s->token.u.ident.atom);
+                        if (next_token(s)
+                        ||  js_define_var(s, name, TOK_CATCH) < 0) {
+                            JS_FreeAtom(ctx, name);
+                            goto fail;
+                        }
+                        /* store the exception value in the catch variable */
+                        emit_op(s, OP_scope_put_var);
+                        emit_u32(s, name);
+                        emit_u16(s, s->cur_func->scope_level);
+                    }
+                    if (js_parse_expect(s, ')'))
+                        goto fail;
+                }
+                /* XXX: should keep the address to nop it out if there is no finally block */
+                emit_goto(s, OP_catch, label_catch2);
+
+                push_scope(s);  /* catch block */
+                push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL,
+                                 -1, -1, 1);
+                block_env.label_finally = label_finally;
+
+                if (js_parse_block(s))
+                    goto fail;
+
+                pop_break_entry(s->cur_func);
+                pop_scope(s);  /* catch block */
+                pop_scope(s);  /* catch variable */
+
+                if (js_is_live_code(s)) {
+                    /* drop the catch2 offset */
+                    emit_op(s, OP_drop);
+                    /* XXX: should keep the address to nop it out if there is no finally block */
+                    /* must push dummy value to keep same stack size */
+                    emit_op(s, OP_undefined);
+                    emit_goto(s, OP_gosub, label_finally);
+                    emit_op(s, OP_drop);
+                    emit_goto(s, OP_goto, label_end);
+                }
+                /* catch exceptions thrown in the catch block to execute the
+                 * finally clause and rethrow the exception */
+                emit_label(s, label_catch2);
+                /* catch value is at TOS, no need to push undefined */
+                emit_goto(s, OP_gosub, label_finally);
+                emit_op(s, OP_throw);
+
+            } else if (s->token.val == TOK_FINALLY) {
+                /* finally without catch : execute the finally clause
+                 * and rethrow the exception */
+                emit_label(s, label_catch);
+                /* catch value is at TOS, no need to push undefined */
+                emit_goto(s, OP_gosub, label_finally);
+                emit_op(s, OP_throw);
+            } else {
+                js_parse_error(s, "expecting catch or finally");
+                goto fail;
+            }
+            emit_label(s, label_finally);
+            if (s->token.val == TOK_FINALLY) {
+                int saved_eval_ret_idx;
+                if (next_token(s))
+                    goto fail;
+                /* on the stack: ret_value gosub_ret_value */
+                push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL,
+                                 -1, -1, 2);
+                saved_eval_ret_idx = s->cur_func->eval_ret_idx;
+                s->cur_func->eval_ret_idx = -1;
+                /* 'finally' does not update eval_ret */
+                if (js_parse_block(s))
+                    goto fail;
+                s->cur_func->eval_ret_idx = saved_eval_ret_idx;
+                pop_break_entry(s->cur_func);
+            }
+            emit_op(s, OP_ret);
+            emit_label(s, label_end);
+        }
+        break;
+    case ';':
+        /* empty statement */
+        if (next_token(s))
+            goto fail;
+        break;
+    case TOK_WITH:
+        if (s->cur_func->js_mode & JS_MODE_STRICT) {
+            js_parse_error(s, "invalid keyword: with");
+            goto fail;
+        } else {
+            int with_idx;
+
+            if (next_token(s))
+                goto fail;
+
+            if (js_parse_expr_paren(s))
+                goto fail;
+
+            push_scope(s);
+            with_idx = define_var(s, s->cur_func, JS_ATOM__with_,
+                                  JS_VAR_DEF_WITH);
+            if (with_idx < 0)
+                goto fail;
+            emit_op(s, OP_to_object);
+            emit_op(s, OP_put_loc);
+            emit_u16(s, with_idx);
+
+            set_eval_ret_undefined(s);
+            if (js_parse_statement(s))
+                goto fail;
+
+            /* Popping scope drops lexical context for the with object variable */
+            pop_scope(s);
+        }
+        break;
+    case TOK_FUNCTION:
+        /* ES6 Annex B.3.2 and B.3.3 semantics */
+        if (!(decl_mask & DECL_MASK_FUNC))
+            goto func_decl_error;
+        if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*')
+            goto func_decl_error;
+        goto parse_func_var;
+    case TOK_IDENT:
+        if (s->token.u.ident.is_reserved) {
+            js_parse_error_reserved_identifier(s);
+            goto fail;
+        }
+        /* Determine if `let` introduces a Declaration or an ExpressionStatement */
+        switch (is_let(s, decl_mask)) {
+        case TRUE:
+            tok = TOK_LET;
+            goto haslet;
+        case FALSE:
+            break;
+        default:
+            goto fail;
+        }
+        if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
+            peek_token(s, TRUE) == TOK_FUNCTION) {
+            if (!(decl_mask & DECL_MASK_OTHER)) {
+            func_decl_error:
+                js_parse_error(s, "function declarations can't appear in single-statement context");
+                goto fail;
+            }
+        parse_func_var:
+            if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR,
+                                       JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                       s->token.ptr, s->token.line_num))
+                goto fail;
+            break;
+        }
+        goto hasexpr;
+
+    case TOK_CLASS:
+        if (!(decl_mask & DECL_MASK_OTHER)) {
+            js_parse_error(s, "class declarations can't appear in single-statement context");
+            goto fail;
+        }
+        if (js_parse_class(s, FALSE, JS_PARSE_EXPORT_NONE))
+            return -1;
+        break;
+
+    case TOK_DEBUGGER:
+        /* currently no debugger, so just skip the keyword */
+        if (next_token(s))
+            goto fail;
+        if (js_parse_expect_semi(s))
+            goto fail;
+        break;
+        
+    case TOK_ENUM:
+    case TOK_EXPORT:
+    case TOK_EXTENDS:
+        js_unsupported_keyword(s, s->token.u.ident.atom);
+        goto fail;
+
+    default:
+    hasexpr:
+        if (js_parse_expr(s))
+            goto fail;
+        if (s->cur_func->eval_ret_idx >= 0) {
+            /* store the expression value so that it can be returned
+               by eval() */
+            emit_op(s, OP_put_loc);
+            emit_u16(s, s->cur_func->eval_ret_idx);
+        } else {
+            emit_op(s, OP_drop); /* drop the result */
+        }
+        if (js_parse_expect_semi(s))
+            goto fail;
+        break;
+    }
+done:
+    JS_FreeAtom(ctx, label_name);
+    return 0;
+fail:
+    JS_FreeAtom(ctx, label_name);
+    return -1;
+}
+
+/* 'name' is freed */
+static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
+{
+    JSModuleDef *m;
+    m = js_mallocz(ctx, sizeof(*m));
+    if (!m) {
+        JS_FreeAtom(ctx, name);
+        return NULL;
+    }
+    m->header.ref_count = 1;
+    m->module_name = name;
+    m->module_ns = JS_UNDEFINED;
+    m->func_obj = JS_UNDEFINED;
+    m->eval_exception = JS_UNDEFINED;
+    m->meta_obj = JS_UNDEFINED;
+    list_add_tail(&m->link, &ctx->loaded_modules);
+    return m;
+}
+
+static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
+{
+    int i;
+
+    JS_FreeAtom(ctx, m->module_name);
+
+    for(i = 0; i < m->req_module_entries_count; i++) {
+        JSReqModuleEntry *rme = &m->req_module_entries[i];
+        JS_FreeAtom(ctx, rme->module_name);
+    }
+    js_free(ctx, m->req_module_entries);
+
+    for(i = 0; i < m->export_entries_count; i++) {
+        JSExportEntry *me = &m->export_entries[i];
+        if (me->export_type == JS_EXPORT_TYPE_LOCAL)
+            free_var_ref(ctx->rt, me->u.local.var_ref);
+        JS_FreeAtom(ctx, me->export_name);
+        JS_FreeAtom(ctx, me->local_name);
+    }
+    js_free(ctx, m->export_entries);
+
+    js_free(ctx, m->star_export_entries);
+
+    for(i = 0; i < m->import_entries_count; i++) {
+        JSImportEntry *mi = &m->import_entries[i];
+        JS_FreeAtom(ctx, mi->import_name);
+    }
+    js_free(ctx, m->import_entries);
+
+    JS_FreeValue(ctx, m->module_ns);
+    JS_FreeValue(ctx, m->func_obj);
+    JS_FreeValue(ctx, m->eval_exception);
+    JS_FreeValue(ctx, m->meta_obj);
+    list_del(&m->link);
+    js_free(ctx, m);
+}
+
+static int js_resize_array(JSContext *ctx, void **parray, int elem_size,
+                           int *psize, int *pcount, int new_count)
+{
+    if (unlikely(new_count > *psize)) {
+        int new_size;
+        size_t slack;
+        void *new_array;
+        /* XXX: potential arithmetic overflow */
+        new_size = max_int(new_count, *psize * 3 / 2);
+        new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack);
+        if (!new_array)
+            return -1;
+        new_size += slack / elem_size;
+        *psize = new_size;
+        *parray = new_array;
+    }
+    *pcount = new_count;
+    return 0;
+}
+
+static int add_req_module_entry(JSContext *ctx, JSModuleDef *m,
+                                JSAtom module_name)
+{
+    JSReqModuleEntry *rme;
+    int i;
+
+    /* no need to add the module request if it is already present */
+    for(i = 0; i < m->req_module_entries_count; i++) {
+        rme = &m->req_module_entries[i];
+        if (rme->module_name == module_name)
+            return i;
+    }
+
+    if (js_resize_array(ctx, (void **)&m->req_module_entries,
+                        sizeof(JSReqModuleEntry),
+                        &m->req_module_entries_size, &m->req_module_entries_count,
+                        m->req_module_entries_count + 1))
+        return -1;
+    i = m->req_module_entries_count - 1;
+    rme = &m->req_module_entries[i];
+    rme->module_name = JS_DupAtom(ctx, module_name);
+    rme->module = NULL;
+    return i;
+}
+
+static JSExportEntry *find_export_entry(JSContext *ctx, JSModuleDef *m,
+                                        JSAtom export_name)
+{
+    JSExportEntry *me;
+    int i;
+    for(i = 0; i < m->export_entries_count; i++) {
+        me = &m->export_entries[i];
+        if (me->export_name == export_name)
+            return me;
+    }
+    return NULL;
+}
+
+static JSExportEntry *add_export_entry2(JSContext *ctx,
+                                        JSParseState *s, JSModuleDef *m,
+                                       JSAtom local_name, JSAtom export_name,
+                                       JSExportTypeEnum export_type)
+{
+    JSExportEntry *me;
+
+    if (find_export_entry(ctx, m, export_name)) {
+        char buf1[ATOM_GET_STR_BUF_SIZE];
+        if (s) {
+            js_parse_error(s, "duplicate exported name '%s'",
+                           JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name));
+        } else {
+            JS_ThrowSyntaxError(ctx, "duplicate exported name '%s'",
+                                JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name));
+        }
+        return NULL;
+    }
+
+    if (js_resize_array(ctx, (void **)&m->export_entries,
+                        sizeof(JSExportEntry),
+                        &m->export_entries_size, &m->export_entries_count,
+                        m->export_entries_count + 1))
+        return NULL;
+    me = &m->export_entries[m->export_entries_count - 1];
+    memset(me, 0, sizeof(*me));
+    me->local_name = JS_DupAtom(ctx, local_name);
+    me->export_name = JS_DupAtom(ctx, export_name);
+    me->export_type = export_type;
+    return me;
+}
+
+static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m,
+                                       JSAtom local_name, JSAtom export_name,
+                                       JSExportTypeEnum export_type)
+{
+    return add_export_entry2(s->ctx, s, m, local_name, export_name,
+                             export_type);
+}
+
+static int add_star_export_entry(JSContext *ctx, JSModuleDef *m,
+                                 int req_module_idx)
+{
+    JSStarExportEntry *se;
+
+    if (js_resize_array(ctx, (void **)&m->star_export_entries,
+                        sizeof(JSStarExportEntry),
+                        &m->star_export_entries_size, &m->star_export_entries_count,
+                        m->star_export_entries_count + 1))
+        return -1;
+    se = &m->star_export_entries[m->star_export_entries_count - 1];
+    se->req_module_idx = req_module_idx;
+    return 0;
+}
+
+/* create a C module */
+JSModuleDef *JS_NewCModule(JSContext *ctx, const char *name_str,
+                           JSModuleInitFunc *func)
+{
+    JSModuleDef *m;
+    JSAtom name;
+    name = JS_NewAtom(ctx, name_str);
+    if (name == JS_ATOM_NULL)
+        return NULL;
+    m = js_new_module_def(ctx, name);
+    m->init_func = func;
+    return m;
+}
+
+int JS_AddModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name)
+{
+    JSExportEntry *me;
+    JSAtom name;
+    name = JS_NewAtom(ctx, export_name);
+    if (name == JS_ATOM_NULL)
+        return -1;
+    me = add_export_entry2(ctx, NULL, m, JS_ATOM_NULL, name,
+                           JS_EXPORT_TYPE_LOCAL);
+    JS_FreeAtom(ctx, name);
+    if (!me)
+        return -1;
+    else
+        return 0;
+}
+
+int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name,
+                       JSValue val)
+{
+    JSExportEntry *me;
+    JSAtom name;
+    name = JS_NewAtom(ctx, export_name);
+    if (name == JS_ATOM_NULL)
+        goto fail;
+    me = find_export_entry(ctx, m, name);
+    JS_FreeAtom(ctx, name);
+    if (!me)
+        goto fail;
+    set_value(ctx, me->u.local.var_ref->pvalue, val);
+    return 0;
+ fail:
+    JS_FreeValue(ctx, val);
+    return -1;
+}
+
+void JS_SetModuleLoaderFunc(JSRuntime *rt,
+                            JSModuleNormalizeFunc *module_normalize,
+                            JSModuleLoaderFunc *module_loader, void *opaque)
+{
+    rt->module_normalize_func = module_normalize;
+    rt->module_loader_func = module_loader;
+    rt->module_loader_opaque = opaque;
+}
+
+/* default module filename normalizer */
+static char *js_default_module_normalize_name(JSContext *ctx,
+                                              const char *base_name,
+                                              const char *name)
+{
+    char *filename, *p;
+    const char *r;
+    int len;
+
+    if (name[0] != '.') {
+        /* if no initial dot, the module name is not modified */
+        return js_strdup(ctx, name);
+    }
+
+    p = strrchr(base_name, '/');
+    if (p)
+        len = p - base_name;
+    else
+        len = 0;
+
+    filename = js_malloc(ctx, len + strlen(name) + 1 + 1);
+    if (!filename)
+        return NULL;
+    memcpy(filename, base_name, len);
+    filename[len] = '\0';
+
+    /* we only normalize the leading '..' or '.' */
+    r = name;
+    for(;;) {
+        if (r[0] == '.' && r[1] == '/') {
+            r += 2;
+        } else if (r[0] == '.' && r[1] == '.' && r[2] == '/') {
+            /* remove the last path element of filename, except if "."
+               or ".." */
+            if (filename[0] == '\0')
+                break;
+            p = strrchr(filename, '/');
+            if (!p)
+                p = filename;
+            else
+                p++;
+            if (!strcmp(p, ".") || !strcmp(p, ".."))
+                break;
+            if (p > filename)
+                p--;
+            *p = '\0';
+            r += 3;
+        } else {
+            break;
+        }
+    }
+    if (filename[0] != '\0')
+        strcat(filename, "/");
+    strcat(filename, r);
+    //    printf("normalize: %s %s -> %s\n", base_name, name, filename);
+    return filename;
+}
+
+static JSModuleDef *js_find_loaded_module(JSContext *ctx, JSAtom name)
+{
+    struct list_head *el;
+    JSModuleDef *m;
+    
+    /* first look at the loaded modules */
+    list_for_each(el, &ctx->loaded_modules) {
+        m = list_entry(el, JSModuleDef, link);
+        if (m->module_name == name)
+            return m;
+    }
+    return NULL;
+}
+
+/* return NULL in case of exception (e.g. module could not be loaded) */
+static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx,
+                                                    JSAtom base_module_name,
+                                                    JSAtom module_name1)
+{
+    JSRuntime *rt = ctx->rt;
+    JSModuleDef *m;
+    char *cname;
+    const char *base_cname, *cname1;
+    JSAtom module_name;
+
+    base_cname = JS_AtomToCString(ctx, base_module_name);
+    if (!base_cname)
+        return NULL;
+    cname1 = JS_AtomToCString(ctx, module_name1);
+    if (!cname1) {
+        JS_FreeCString(ctx, base_cname);
+        return NULL;
+    }
+
+    if (!rt->module_normalize_func) {
+        cname = js_default_module_normalize_name(ctx, base_cname, cname1);
+    } else {
+        cname = rt->module_normalize_func(ctx, base_cname, cname1,
+                                          rt->module_loader_opaque);
+    }
+    JS_FreeCString(ctx, base_cname);
+    JS_FreeCString(ctx, cname1);
+    if (!cname)
+        return NULL;
+
+    module_name = JS_NewAtom(ctx, cname);
+    if (module_name == JS_ATOM_NULL) {
+        js_free(ctx, cname);
+        return NULL;
+    }
+
+    /* first look at the loaded modules */
+    m = js_find_loaded_module(ctx, module_name);
+    if (m) {
+        js_free(ctx, cname);
+        JS_FreeAtom(ctx, module_name);
+        return m;
+    }
+
+    JS_FreeAtom(ctx, module_name);
+
+    /* load the module */
+    if (!rt->module_loader_func) {
+        /* XXX: use a syntax error ? */
+        JS_ThrowReferenceError(ctx, "could not load module '%s'",
+                               cname);
+        js_free(ctx, cname);
+        return NULL;
+    }
+
+    m = rt->module_loader_func(ctx, cname, rt->module_loader_opaque);
+    js_free(ctx, cname);
+    return m;
+}
+
+typedef struct JSResolveEntry {
+    JSModuleDef *module;
+    JSAtom name;
+} JSResolveEntry;
+
+typedef struct JSResolveState {
+    JSResolveEntry *array;
+    int size;
+    int count;
+} JSResolveState;
+
+static int find_resolve_entry(JSResolveState *s,
+                              JSModuleDef *m, JSAtom name)
+{
+    int i;
+    for(i = 0; i < s->count; i++) {
+        JSResolveEntry *re = &s->array[i];
+        if (re->module == m && re->name == name)
+            return i;
+    }
+    return -1;
+}
+
+static int add_resolve_entry(JSContext *ctx, JSResolveState *s,
+                             JSModuleDef *m, JSAtom name)
+{
+    JSResolveEntry *re;
+
+    if (js_resize_array(ctx, (void **)&s->array,
+                        sizeof(JSResolveEntry),
+                        &s->size, &s->count,
+                        s->count + 1))
+        return -1;
+    re = &s->array[s->count - 1];
+    re->module = m;
+    re->name = JS_DupAtom(ctx, name);
+    return 0;
+}
+
+typedef enum JSResolveResultEnum {
+    JS_RESOLVE_RES_EXCEPTION = -1, /* memory alloc error */
+    JS_RESOLVE_RES_FOUND = 0,
+    JS_RESOLVE_RES_NOT_FOUND,
+    JS_RESOLVE_RES_CIRCULAR,
+    JS_RESOLVE_RES_AMBIGUOUS,
+} JSResolveResultEnum;
+
+static JSResolveResultEnum js_resolve_export1(JSContext *ctx,
+                                              JSModuleDef **pmodule,
+                                              JSExportEntry **pme,
+                                              JSModuleDef *m,
+                                              JSAtom export_name,
+                                              JSResolveState *s)
+{
+    JSExportEntry *me;
+
+    *pmodule = NULL;
+    *pme = NULL;
+    if (find_resolve_entry(s, m, export_name) >= 0)
+        return JS_RESOLVE_RES_CIRCULAR;
+    if (add_resolve_entry(ctx, s, m, export_name) < 0)
+        return JS_RESOLVE_RES_EXCEPTION;
+    me = find_export_entry(ctx, m, export_name);
+    if (me) {
+        if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
+            /* local export */
+            *pmodule = m;
+            *pme = me;
+            return JS_RESOLVE_RES_FOUND;
+        } else {
+            /* indirect export */
+            JSModuleDef *m1;
+            m1 = m->req_module_entries[me->u.req_module_idx].module;
+            if (me->local_name == JS_ATOM__star_) {
+                /* export ns from */
+                *pmodule = m;
+                *pme = me;
+                return JS_RESOLVE_RES_FOUND;
+            } else {
+                return js_resolve_export1(ctx, pmodule, pme, m1,
+                                          me->local_name, s);
+            }
+        }
+    } else {
+        if (export_name != JS_ATOM_default) {
+            /* not found in direct or indirect exports: try star exports */
+            int i;
+
+            for(i = 0; i < m->star_export_entries_count; i++) {
+                JSStarExportEntry *se = &m->star_export_entries[i];
+                JSModuleDef *m1, *res_m;
+                JSExportEntry *res_me;
+                JSResolveResultEnum ret;
+
+                m1 = m->req_module_entries[se->req_module_idx].module;
+                ret = js_resolve_export1(ctx, &res_m, &res_me, m1,
+                                         export_name, s);
+                if (ret == JS_RESOLVE_RES_AMBIGUOUS ||
+                    ret == JS_RESOLVE_RES_EXCEPTION) {
+                    return ret;
+                } else if (ret == JS_RESOLVE_RES_FOUND) {
+                    if (*pme != NULL) {
+                        if (*pmodule != res_m ||
+                            res_me->local_name != (*pme)->local_name) {
+                            *pmodule = NULL;
+                            *pme = NULL;
+                            return JS_RESOLVE_RES_AMBIGUOUS;
+                        }
+                    } else {
+                        *pmodule = res_m;
+                        *pme = res_me;
+                    }
+                }
+            }
+            if (*pme != NULL)
+                return JS_RESOLVE_RES_FOUND;
+        }
+        return JS_RESOLVE_RES_NOT_FOUND;
+    }
+}
+
+/* If the return value is JS_RESOLVE_RES_FOUND, return the module
+  (*pmodule) and the corresponding local export entry
+  (*pme). Otherwise return (NULL, NULL) */
+static JSResolveResultEnum js_resolve_export(JSContext *ctx,
+                                             JSModuleDef **pmodule,
+                                             JSExportEntry **pme,
+                                             JSModuleDef *m,
+                                             JSAtom export_name)
+{
+    JSResolveState ss, *s = &ss;
+    int i;
+    JSResolveResultEnum ret;
+
+    s->array = NULL;
+    s->size = 0;
+    s->count = 0;
+
+    ret = js_resolve_export1(ctx, pmodule, pme, m, export_name, s);
+
+    for(i = 0; i < s->count; i++)
+        JS_FreeAtom(ctx, s->array[i].name);
+    js_free(ctx, s->array);
+
+    return ret;
+}
+
+static void js_resolve_export_throw_error(JSContext *ctx,
+                                          JSResolveResultEnum res,
+                                          JSModuleDef *m, JSAtom export_name)
+{
+    char buf1[ATOM_GET_STR_BUF_SIZE];
+    char buf2[ATOM_GET_STR_BUF_SIZE];
+    switch(res) {
+    case JS_RESOLVE_RES_EXCEPTION:
+        break;
+    default:
+    case JS_RESOLVE_RES_NOT_FOUND:
+        JS_ThrowSyntaxError(ctx, "export '%s' in module '%s' is ambiguous",
+                            JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name),
+                            JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name));
+        break;
+    case JS_RESOLVE_RES_CIRCULAR:
+        JS_ThrowSyntaxError(ctx, "Could not find export '%s' in module '%s'",
+                            JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name),
+                            JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name));
+        break;
+    case JS_RESOLVE_RES_AMBIGUOUS:
+        JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%s' in module '%s'",
+                            JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name),
+                            JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name));
+        break;
+    }
+}
+
+
+typedef enum {
+    EXPORTED_NAME_AMBIGUOUS,
+    EXPORTED_NAME_NORMAL,
+    EXPORTED_NAME_NS,
+} ExportedNameEntryEnum;
+
+typedef struct ExportedNameEntry {
+    JSAtom export_name;
+    ExportedNameEntryEnum export_type;
+    union {
+        JSExportEntry *me; /* using when the list is built */
+        JSVarRef *var_ref; /* EXPORTED_NAME_NORMAL */
+        JSModuleDef *module; /* for EXPORTED_NAME_NS */
+    } u;
+} ExportedNameEntry;
+
+typedef struct GetExportNamesState {
+    JSModuleDef **modules;
+    int modules_size;
+    int modules_count;
+
+    ExportedNameEntry *exported_names;
+    int exported_names_size;
+    int exported_names_count;
+} GetExportNamesState;
+
+static int find_exported_name(GetExportNamesState *s, JSAtom name)
+{
+    int i;
+    for(i = 0; i < s->exported_names_count; i++) {
+        if (s->exported_names[i].export_name == name)
+            return i;
+    }
+    return -1;
+}
+
+static __exception int get_exported_names(JSContext *ctx,
+                                          GetExportNamesState *s,
+                                          JSModuleDef *m, BOOL from_star)
+{
+    ExportedNameEntry *en;
+    int i, j;
+
+    /* check circular reference */
+    for(i = 0; i < s->modules_count; i++) {
+        if (s->modules[i] == m)
+            return 0;
+    }
+    if (js_resize_array(ctx, (void **)&s->modules, sizeof(s->modules[0]),
+                        &s->modules_size, &s->modules_count, s->modules_count + 1))
+        return -1;
+    s->modules[s->modules_count - 1] = m;
+
+    for(i = 0; i < m->export_entries_count; i++) {
+        JSExportEntry *me = &m->export_entries[i];
+        if (from_star && me->export_name == JS_ATOM_default)
+            continue;
+        j = find_exported_name(s, me->export_name);
+        if (j < 0) {
+            if (js_resize_array(ctx, (void **)&s->exported_names, sizeof(s->exported_names[0]),
+                                &s->exported_names_size, &s->exported_names_count, s->exported_names_count + 1))
+                return -1;
+            en = &s->exported_names[s->exported_names_count - 1];
+            en->export_name = me->export_name;
+            /* avoid a second lookup for simple module exports */
+            if (from_star || me->export_type != JS_EXPORT_TYPE_LOCAL)
+                en->u.me = NULL;
+            else
+                en->u.me = me;
+        } else {
+            en = &s->exported_names[j];
+            en->u.me = NULL;
+        }
+    }
+    for(i = 0; i < m->star_export_entries_count; i++) {
+        JSStarExportEntry *se = &m->star_export_entries[i];
+        JSModuleDef *m1;
+        m1 = m->req_module_entries[se->req_module_idx].module;
+        if (get_exported_names(ctx, s, m1, TRUE))
+            return -1;
+    }
+    return 0;
+}
+
+/* Unfortunately, the spec gives a different behavior from GetOwnProperty ! */
+static int js_module_ns_has(JSContext *ctx, JSValueConst obj, JSAtom atom)
+{
+    return (find_own_property1(JS_VALUE_GET_OBJ(obj), atom) != NULL);
+}
+
+static const JSClassExoticMethods js_module_ns_exotic_methods = {
+    .has_property = js_module_ns_has,
+};
+
+static int exported_names_cmp(const void *p1, const void *p2, void *opaque)
+{
+    JSContext *ctx = opaque;
+    const ExportedNameEntry *me1 = p1;
+    const ExportedNameEntry *me2 = p2;
+    JSValue str1, str2;
+    int ret;
+
+    /* XXX: should avoid allocation memory in atom comparison */
+    str1 = JS_AtomToString(ctx, me1->export_name);
+    str2 = JS_AtomToString(ctx, me2->export_name);
+    if (JS_IsException(str1) || JS_IsException(str2)) {
+        /* XXX: raise an error ? */
+        ret = 0;
+    } else {
+        ret = js_string_compare(ctx, JS_VALUE_GET_STRING(str1),
+                                JS_VALUE_GET_STRING(str2));
+    }
+    JS_FreeValue(ctx, str1);
+    JS_FreeValue(ctx, str2);
+    return ret;
+}
+
+static JSValue js_get_module_ns(JSContext *ctx, JSModuleDef *m);
+
+static int js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom,
+                                 void *opaque)
+{
+    JSModuleDef *m = opaque;
+    JSValue val, this_val = JS_MKPTR(JS_TAG_OBJECT, p);
+    
+    val = js_get_module_ns(ctx, m);
+    if (JS_IsException(val))
+        return -1;
+    if (JS_DefinePropertyValue(ctx, this_val, atom, val,
+                               JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0)
+        return -1;
+    return 0;
+}
+
+static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m)
+{
+    JSValue obj;
+    JSObject *p;
+    GetExportNamesState s_s, *s = &s_s;
+    int i, ret;
+    JSProperty *pr;
+
+    obj = JS_NewObjectClass(ctx, JS_CLASS_MODULE_NS);
+    if (JS_IsException(obj))
+        return obj;
+    p = JS_VALUE_GET_OBJ(obj);
+
+    memset(s, 0, sizeof(*s));
+    ret = get_exported_names(ctx, s, m, FALSE);
+    js_free(ctx, s->modules);
+    if (ret)
+        goto fail;
+
+    /* Resolve the exported names. The ambiguous exports are removed */
+    for(i = 0; i < s->exported_names_count; i++) {
+        ExportedNameEntry *en = &s->exported_names[i];
+        JSResolveResultEnum res;
+        JSExportEntry *res_me;
+        JSModuleDef *res_m;
+
+        if (en->u.me) {
+            res_me = en->u.me; /* fast case: no resolution needed */
+            res_m = m;
+            res = JS_RESOLVE_RES_FOUND;
+        } else {
+            res = js_resolve_export(ctx, &res_m, &res_me, m,
+                                    en->export_name);
+        }
+        if (res != JS_RESOLVE_RES_FOUND) {
+            if (res != JS_RESOLVE_RES_AMBIGUOUS) {
+                js_resolve_export_throw_error(ctx, res, m, en->export_name);
+                goto fail;
+            }
+            en->export_type = EXPORTED_NAME_AMBIGUOUS;
+        } else {
+            if (res_me->local_name == JS_ATOM__star_) {
+                en->export_type = EXPORTED_NAME_NS;
+                en->u.module = res_m->req_module_entries[res_me->u.req_module_idx].module;
+            } else {
+                en->export_type = EXPORTED_NAME_NORMAL;
+                if (res_me->u.local.var_ref) {
+                    en->u.var_ref = res_me->u.local.var_ref;
+                } else {
+                    JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj);
+                    p1 = JS_VALUE_GET_OBJ(res_m->func_obj);
+                    en->u.var_ref = p1->u.func.var_refs[res_me->u.local.var_idx];
+                }
+            }
+        }
+    }
+
+    /* sort the exported names */
+    rqsort(s->exported_names, s->exported_names_count,
+           sizeof(s->exported_names[0]), exported_names_cmp, ctx);
+
+    for(i = 0; i < s->exported_names_count; i++) {
+        ExportedNameEntry *en = &s->exported_names[i];
+        switch(en->export_type) {
+        case EXPORTED_NAME_NORMAL:
+            {
+                JSVarRef *var_ref = en->u.var_ref;
+                pr = add_property(ctx, p, en->export_name,
+                                  JS_PROP_ENUMERABLE | JS_PROP_WRITABLE |
+                                  JS_PROP_VARREF);
+                if (!pr)
+                    goto fail;
+                var_ref->header.ref_count++;
+                pr->u.var_ref = var_ref;
+            }
+            break;
+        case EXPORTED_NAME_NS:
+            /* the exported namespace must be created on demand */
+            if (JS_DefineAutoInitProperty(ctx, obj,
+                                          en->export_name,
+                                          js_module_ns_autoinit,
+                                          en->u.module, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0)
+                goto fail;
+            break;
+        default:
+            break;
+        }
+    }
+
+    js_free(ctx, s->exported_names);
+
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_Symbol_toStringTag,
+                           JS_AtomToString(ctx, JS_ATOM_Module),
+                           0);
+
+    p->extensible = FALSE;
+    return obj;
+ fail:
+    js_free(ctx, s->exported_names);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_get_module_ns(JSContext *ctx, JSModuleDef *m)
+{
+    if (JS_IsUndefined(m->module_ns)) {
+        JSValue val;
+        val = js_build_module_ns(ctx, m);
+        if (JS_IsException(val))
+            return JS_EXCEPTION;
+        m->module_ns = val;
+    }
+    return JS_DupValue(ctx, m->module_ns);
+}
+
+/* Load all the required modules for module 'm' */
+static int js_resolve_module(JSContext *ctx, JSModuleDef *m)
+{
+    int i;
+    JSModuleDef *m1;
+
+    if (m->resolved)
+        return 0;
+#ifdef DUMP_MODULE_RESOLVE
+    {
+        char buf1[ATOM_GET_STR_BUF_SIZE];
+        printf("resolving module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
+    }
+#endif
+    m->resolved = TRUE;
+    /* resolve each requested module */
+    for(i = 0; i < m->req_module_entries_count; i++) {
+        JSReqModuleEntry *rme = &m->req_module_entries[i];
+        m1 = js_host_resolve_imported_module(ctx, m->module_name,
+                                             rme->module_name);
+        if (!m1)
+            return -1;
+        rme->module = m1;
+        /* already done in js_host_resolve_imported_module() except if
+           the module was loaded with JS_EvalBinary() */
+        if (js_resolve_module(ctx, m1) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+static JSVarRef *js_create_module_var(JSContext *ctx, BOOL is_lexical)
+{
+    JSVarRef *var_ref;
+    var_ref = js_malloc(ctx, sizeof(JSVarRef));
+    if (!var_ref)
+        return NULL;
+    var_ref->header.ref_count = 1;
+    var_ref->gc_header.mark = 0;
+    var_ref->link.prev = NULL;
+    var_ref->link.next = NULL;
+    if (is_lexical)
+        var_ref->value = JS_UNINITIALIZED;
+    else
+        var_ref->value = JS_UNDEFINED;
+    var_ref->pvalue = &var_ref->value;
+    return var_ref;
+}
+
+/* Create the <eval> function associated with the module */
+static int js_create_module_function(JSContext *ctx, JSModuleDef *m)
+{
+    JSFunctionBytecode *b;
+    int i;
+    JSVarRef **var_refs;
+    JSValue func_obj, bfunc;
+    JSObject *p;
+
+    bfunc = m->func_obj;
+    func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
+                                      JS_CLASS_BYTECODE_FUNCTION);
+
+    if (JS_IsException(func_obj))
+        return -1;
+    b = JS_VALUE_GET_PTR(bfunc);
+
+    p = JS_VALUE_GET_OBJ(func_obj);
+    p->u.func.function_bytecode = b;
+    b->header.ref_count++;
+    p->u.func.home_object = NULL;
+    p->u.func.var_refs = NULL;
+    if (b->closure_var_count) {
+        var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count);
+        if (!var_refs)
+            goto fail;
+        p->u.func.var_refs = var_refs;
+
+        /* create the global variables. The other variables are
+           imported from other modules */
+        for(i = 0; i < b->closure_var_count; i++) {
+            JSClosureVar *cv = &b->closure_var[i];
+            JSVarRef *var_ref;
+            if (cv->is_local) {
+                var_ref = js_create_module_var(ctx, cv->is_lexical);
+                if (!var_ref)
+                    goto fail;
+#ifdef DUMP_MODULE_RESOLVE
+                printf("local %d: %p\n", i, var_ref);
+#endif
+                var_refs[i] = var_ref;
+            }
+        }
+    }
+    m->func_obj = func_obj;
+    JS_FreeValue(ctx, bfunc);
+    return 0;
+ fail:
+    JS_FreeValue(ctx, func_obj);
+    return -1;
+}
+
+/* Prepare a module to be executed by resolving all the imported
+   variables. */
+static int js_instantiate_module(JSContext *ctx, JSModuleDef *m)
+{
+    int i;
+    JSImportEntry *mi;
+    JSModuleDef *m1;
+    JSVarRef **var_refs, *var_ref;
+    JSObject *p;
+    BOOL is_c_module;
+
+    if (m->instantiated)
+        return 0;
+    m->instantiated = TRUE;
+
+    is_c_module = (m->init_func != NULL);
+
+    if (is_c_module) {
+        /* initialize the exported variables */
+        for(i = 0; i < m->export_entries_count; i++) {
+            JSExportEntry *me = &m->export_entries[i];
+            if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
+                var_ref = js_create_module_var(ctx, FALSE);
+                if (!var_ref)
+                    goto fail;
+                me->u.local.var_ref = var_ref;
+            }
+        }
+    } else {
+        if (js_create_module_function(ctx, m) < 0)
+            goto fail;
+    }
+
+    for(i = 0; i < m->req_module_entries_count; i++) {
+        JSReqModuleEntry *rme = &m->req_module_entries[i];
+        if (js_instantiate_module(ctx, rme->module) < 0)
+            goto fail;
+    }
+
+#ifdef DUMP_MODULE_RESOLVE
+    {
+        char buf1[ATOM_GET_STR_BUF_SIZE];
+        printf("instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
+    }
+#endif
+    /* check the indirect exports */
+    for(i = 0; i < m->export_entries_count; i++) {
+        JSExportEntry *me = &m->export_entries[i];
+        if (me->export_type == JS_EXPORT_TYPE_INDIRECT &&
+            me->local_name != JS_ATOM__star_) {
+            JSResolveResultEnum ret;
+            JSExportEntry *res_me;
+            JSModuleDef *res_m, *m1;
+            m1 = m->req_module_entries[me->u.req_module_idx].module;
+            ret = js_resolve_export(ctx, &res_m, &res_me, m1, me->local_name);
+            if (ret != JS_RESOLVE_RES_FOUND) {
+                js_resolve_export_throw_error(ctx, ret, m, me->export_name);
+                goto fail;
+            }
+        }
+    }
+
+#ifdef DUMP_MODULE_RESOLVE
+    {
+        printf("exported bindings:\n");
+        for(i = 0; i < m->export_entries_count; i++) {
+            JSExportEntry *me = &m->export_entries[i];
+            printf(" name="); print_atom(ctx, me->export_name);
+            printf(" local="); print_atom(ctx, me->local_name);
+            printf(" type=%d idx=%d\n", me->export_type, me->u.local.var_idx);
+        }
+    }
+#endif
+
+    if (!is_c_module) {
+        p = JS_VALUE_GET_OBJ(m->func_obj);
+        var_refs = p->u.func.var_refs;
+
+        for(i = 0; i < m->import_entries_count; i++) {
+            mi = &m->import_entries[i];
+#ifdef DUMP_MODULE_RESOLVE
+            printf("import var_idx=%d name=", mi->var_idx);
+            print_atom(ctx, mi->import_name);
+            printf(": ");
+#endif
+            m1 = m->req_module_entries[mi->req_module_idx].module;
+            if (mi->import_name == JS_ATOM__star_) {
+                JSValue val;
+                /* name space import */
+                val = js_get_module_ns(ctx, m1);
+                if (JS_IsException(val))
+                    goto fail;
+                set_value(ctx, &var_refs[mi->var_idx]->value, val);
+#ifdef DUMP_MODULE_RESOLVE
+                printf("namespace\n");
+#endif
+            } else {
+                JSResolveResultEnum ret;
+                JSExportEntry *res_me;
+                JSModuleDef *res_m;
+                JSObject *p1;
+
+                ret = js_resolve_export(ctx, &res_m,
+                                        &res_me, m1, mi->import_name);
+                if (ret != JS_RESOLVE_RES_FOUND) {
+                    js_resolve_export_throw_error(ctx, ret, m1, mi->import_name);
+                    goto fail;
+                }
+                if (res_me->local_name == JS_ATOM__star_) {
+                    JSValue val;
+                    JSModuleDef *m2;
+                    /* name space import from */
+                    m2 = res_m->req_module_entries[res_me->u.req_module_idx].module;
+                    val = js_get_module_ns(ctx, m2);
+                    if (JS_IsException(val))
+                        goto fail;
+                    var_ref = js_create_module_var(ctx, TRUE);
+                    if (!var_ref) {
+                        JS_FreeValue(ctx, val);
+                        goto fail;
+                    }
+                    set_value(ctx, &var_ref->value, val);
+                    var_refs[mi->var_idx] = var_ref;
+#ifdef DUMP_MODULE_RESOLVE
+                    printf("namespace from\n");
+#endif
+                } else {
+                    var_ref = res_me->u.local.var_ref;
+                    if (!var_ref) {
+                        p1 = JS_VALUE_GET_OBJ(res_m->func_obj);
+                        var_ref = p1->u.func.var_refs[res_me->u.local.var_idx];
+                    }
+                    var_ref->header.ref_count++;
+                    var_refs[mi->var_idx] = var_ref;
+#ifdef DUMP_MODULE_RESOLVE
+                    printf("local export (var_ref=%p)\n", var_ref);
+#endif
+                }
+            }
+        }
+
+        /* keep the exported variables in the module export entries (they
+           are used when the eval function is deleted and cannot be
+           initialized before in case imports are exported) */
+        for(i = 0; i < m->export_entries_count; i++) {
+            JSExportEntry *me = &m->export_entries[i];
+            if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
+                var_ref = var_refs[me->u.local.var_idx];
+                var_ref->header.ref_count++;
+                me->u.local.var_ref = var_ref;
+            }
+        }
+    }
+
+#ifdef DUMP_MODULE_RESOLVE
+    printf("done instantiate\n");
+#endif
+    return 0;
+ fail:
+    return -1;
+}
+
+/* warning: the returned atom is not allocated */
+static JSAtom js_get_script_or_module_name(JSContext *ctx)
+{
+    JSStackFrame *sf;
+    JSFunctionBytecode *b;
+    JSObject *p;
+    /* XXX: currently we just use the filename of the englobing
+       function. It does not work for eval(). Need to add a
+       ScriptOrModule info in JSFunctionBytecode */
+    sf = ctx->current_stack_frame;
+    assert(sf != NULL);
+    assert(JS_VALUE_GET_TAG(sf->cur_func) == JS_TAG_OBJECT);
+    p = JS_VALUE_GET_OBJ(sf->cur_func);
+    assert(js_class_has_bytecode(p->class_id));
+    b = p->u.func.function_bytecode;
+    if (!b->has_debug)
+        return JS_ATOM_NULL;
+    return b->debug.filename;
+}
+
+JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m)
+{
+    return JS_DupAtom(ctx, m->module_name);
+}
+
+JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m)
+{
+    JSValue obj;
+    /* allocate meta_obj only if requested to save memory */
+    obj = m->meta_obj;
+    if (JS_IsUndefined(obj)) {
+        obj = JS_NewObjectProto(ctx, JS_NULL);
+        if (JS_IsException(obj))
+            return JS_EXCEPTION;
+        m->meta_obj = obj;
+    }
+    return JS_DupValue(ctx, obj);
+}
+
+static JSValue js_import_meta(JSContext *ctx)
+{
+    JSAtom filename;
+    JSModuleDef *m;
+    
+    filename = js_get_script_or_module_name(ctx);
+    if (filename == JS_ATOM_NULL)
+        goto fail;
+
+    /* XXX: inefficient, need to add a module or script pointer in
+       JSFunctionBytecode */
+    m = js_find_loaded_module(ctx, filename);
+    if (!m) {
+    fail:
+        JS_ThrowTypeError(ctx, "import.meta not supported in this context");
+        return JS_EXCEPTION;
+    }
+    return JS_GetImportMeta(ctx, m);
+}
+
+static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
+{
+    JSModuleDef *m;
+    JSAtom basename, filename;
+    JSValue promise, resolving_funcs[2];
+    JSValue specifierString, ret, func_obj, err, ns;
+    
+    promise = JS_NewPromiseCapability(ctx, resolving_funcs);
+    if (JS_IsException(promise))
+        return promise;
+    
+    basename = js_get_script_or_module_name(ctx);
+    if (basename == JS_ATOM_NULL) {
+        JS_ThrowTypeError(ctx, "no function filename for import()");
+        goto exception;
+    }
+
+    specifierString = JS_ToString(ctx, specifier);
+    if (JS_IsException(specifierString))
+        goto exception;
+    filename = JS_ValueToAtom(ctx, specifierString);
+    JS_FreeValue(ctx, specifierString);
+    if (filename == JS_ATOM_NULL)
+        goto exception;
+                     
+    m = js_host_resolve_imported_module(ctx, basename, filename);
+    JS_FreeAtom(ctx, filename);
+    if (!m) {
+        goto exception;
+    }
+    
+    if (js_resolve_module(ctx, m) < 0) {
+        js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED);
+        goto exception;
+    }
+
+    /* Evaluate the module code */
+    func_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
+    ret = JS_EvalFunction(ctx, func_obj);
+    if (JS_IsException(ret))
+        goto exception;
+    JS_FreeValue(ctx, ret);
+
+    /* return the module namespace */
+    ns = js_get_module_ns(ctx, m);
+    if (JS_IsException(ret))
+        goto exception;
+
+    ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
+                   1, (JSValueConst *)&ns);
+    JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
+    JS_FreeValue(ctx, ns);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    return promise;
+
+ exception:
+    err = JS_GetException(ctx);
+    ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
+                   1, (JSValueConst *)&err);
+    JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
+    JS_FreeValue(ctx, err);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    return promise;
+}
+
+/* Run the <eval> function of the module and of all its requested
+   modules. */
+static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
+{
+    JSModuleDef *m1;
+    int i;
+    JSValue ret_val;
+
+    if (m->eval_mark)
+        return JS_UNDEFINED; /* avoid cycles */
+
+    if (m->evaluated) {
+        /* if the module was already evaluated, rethrow the exception
+           it raised */
+        if (m->eval_has_exception) {
+            return JS_Throw(ctx, JS_DupValue(ctx, m->eval_exception));
+        } else {
+            return JS_UNDEFINED;
+        }
+    }
+
+    m->eval_mark = TRUE;
+
+    for(i = 0; i < m->req_module_entries_count; i++) {
+        JSReqModuleEntry *rme = &m->req_module_entries[i];
+        m1 = rme->module;
+        if (!m1->eval_mark) {
+            ret_val = js_evaluate_module(ctx, m1);
+            if (JS_IsException(ret_val)) {
+                m->eval_mark = FALSE;
+                return ret_val;
+            }
+            JS_FreeValue(ctx, ret_val);
+        }
+    }
+
+    if (m->init_func) {
+        /* C module init */
+        if (m->init_func(ctx, m) < 0)
+            ret_val = JS_EXCEPTION;
+        else
+            ret_val = JS_UNDEFINED;
+    } else {
+        ret_val = JS_CallFree(ctx, m->func_obj, JS_UNDEFINED, 0, NULL);
+        m->func_obj = JS_UNDEFINED;
+    }
+    if (JS_IsException(ret_val)) {
+        /* save the thrown exception value */
+        m->eval_has_exception = TRUE;
+        m->eval_exception = JS_DupValue(ctx, ctx->current_exception);
+    }
+    m->eval_mark = FALSE;
+    m->evaluated = TRUE;
+    return ret_val;
+}
+
+static __exception JSAtom js_parse_from_clause(JSParseState *s)
+{
+    JSAtom module_name;
+    if (!token_is_pseudo_keyword(s, JS_ATOM_from)) {
+        js_parse_error(s, "from clause expected");
+        return JS_ATOM_NULL;
+    }
+    if (next_token(s))
+        return JS_ATOM_NULL;
+    if (s->token.val != TOK_STRING) {
+        js_parse_error(s, "string expected");
+        return JS_ATOM_NULL;
+    }
+    module_name = JS_ValueToAtom(s->ctx, s->token.u.str.str);
+    if (module_name == JS_ATOM_NULL)
+        return JS_ATOM_NULL;
+    if (next_token(s)) {
+        JS_FreeAtom(s->ctx, module_name);
+        return JS_ATOM_NULL;
+    }
+    return module_name;
+}
+
+static __exception int js_parse_export(JSParseState *s)
+{
+    JSContext *ctx = s->ctx;
+    JSModuleDef *m = s->cur_func->module;
+    JSAtom local_name, export_name;
+    int first_export, idx, i, tok;
+    JSAtom module_name;
+    JSExportEntry *me;
+
+    if (next_token(s))
+        return -1;
+
+    tok = s->token.val;
+    if (tok == TOK_CLASS) {
+        return js_parse_class(s, FALSE, JS_PARSE_EXPORT_NAMED);
+    } else if (tok == TOK_FUNCTION ||
+               (token_is_pseudo_keyword(s, JS_ATOM_async) &&
+                peek_token(s, TRUE) == TOK_FUNCTION)) {
+        return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT,
+                                       JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                       s->token.ptr, s->token.line_num,
+                                       JS_PARSE_EXPORT_NAMED, NULL);
+    }
+
+    if (next_token(s))
+        return -1;
+
+    switch(tok) {
+    case '{':
+        first_export = m->export_entries_count;
+        while (s->token.val != '}') {
+            if (!token_is_ident(s->token.val)) {
+                js_parse_error(s, "identifier expected");
+                return -1;
+            }
+            local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            export_name = JS_ATOM_NULL;
+            if (next_token(s))
+                goto fail;
+            if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
+                if (next_token(s))
+                    goto fail;
+                if (!token_is_ident(s->token.val)) {
+                    js_parse_error(s, "identifier expected");
+                    goto fail;
+                }
+                export_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+                if (next_token(s)) {
+                fail:
+                    JS_FreeAtom(ctx, local_name);
+                fail1:
+                    JS_FreeAtom(ctx, export_name);
+                    return -1;
+                }
+            } else {
+                export_name = JS_DupAtom(ctx, local_name);
+            }
+            me = add_export_entry(s, m, local_name, export_name,
+                                  JS_EXPORT_TYPE_LOCAL);
+            JS_FreeAtom(ctx, local_name);
+            JS_FreeAtom(ctx, export_name);
+            if (!me)
+                return -1;
+            if (s->token.val != ',')
+                break;
+            if (next_token(s))
+                return -1;
+        }
+        if (js_parse_expect(s, '}'))
+            return -1;
+        if (token_is_pseudo_keyword(s, JS_ATOM_from)) {
+            module_name = js_parse_from_clause(s);
+            if (module_name == JS_ATOM_NULL)
+                return -1;
+            idx = add_req_module_entry(ctx, m, module_name);
+            JS_FreeAtom(ctx, module_name);
+            if (idx < 0)
+                return -1;
+            for(i = first_export; i < m->export_entries_count; i++) {
+                me = &m->export_entries[i];
+                me->export_type = JS_EXPORT_TYPE_INDIRECT;
+                me->u.req_module_idx = idx;
+            }
+        }
+        break;
+    case '*':
+        if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
+            /* export ns from */
+            if (next_token(s))
+                return -1;
+            if (!token_is_ident(s->token.val)) {
+                js_parse_error(s, "identifier expected");
+                return -1;
+            }
+            export_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            if (next_token(s))
+                goto fail1;
+            module_name = js_parse_from_clause(s);
+            if (module_name == JS_ATOM_NULL)
+                goto fail1;
+            idx = add_req_module_entry(ctx, m, module_name);
+            JS_FreeAtom(ctx, module_name);
+            if (idx < 0)
+                goto fail1;
+            me = add_export_entry(s, m, JS_ATOM__star_, export_name,
+                                  JS_EXPORT_TYPE_INDIRECT);
+            JS_FreeAtom(ctx, export_name);
+            if (!me)
+                return -1;
+            me->u.req_module_idx = idx;
+        } else {
+            module_name = js_parse_from_clause(s);
+            if (module_name == JS_ATOM_NULL)
+                return -1;
+            idx = add_req_module_entry(ctx, m, module_name);
+            JS_FreeAtom(ctx, module_name);
+            if (idx < 0)
+                return -1;
+            if (add_star_export_entry(ctx, m, idx) < 0)
+                return -1;
+        }
+        break;
+    case TOK_DEFAULT:
+        if (s->token.val == TOK_CLASS) {
+            return js_parse_class(s, FALSE, JS_PARSE_EXPORT_DEFAULT);
+        } else if (s->token.val == TOK_FUNCTION ||
+                   (token_is_pseudo_keyword(s, JS_ATOM_async) &&
+                    peek_token(s, TRUE) == TOK_FUNCTION)) {
+            return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT,
+                                           JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                           s->token.ptr, s->token.line_num,
+                                           JS_PARSE_EXPORT_DEFAULT, NULL);
+        } else {
+            if (js_parse_assign_expr(s, TRUE))
+                return -1;
+        }
+        /* set the name of anonymous functions */
+        set_object_name(s, JS_ATOM_default);
+
+        /* store the value in the _default_ global variable and export
+           it */
+        local_name = JS_ATOM__default_;
+        if (define_var(s, s->cur_func, local_name, JS_VAR_DEF_LET) < 0)
+            return -1;
+        emit_op(s, OP_scope_put_var_init);
+        emit_atom(s, local_name);
+        emit_u16(s, 0);
+
+        if (add_export_entry(s, m, local_name, JS_ATOM_default,
+                             JS_EXPORT_TYPE_LOCAL) < 0)
+            return -1;
+        break;
+    case TOK_VAR:
+    case TOK_LET:
+    case TOK_CONST:
+        return js_parse_var(s, TRUE, tok, TRUE);
+    default:
+        return js_parse_error(s, "invalid export syntax");
+    }
+    return js_parse_expect_semi(s);
+}
+
+static int add_closure_var(JSContext *ctx, JSFunctionDef *s,
+                           BOOL is_local, BOOL is_arg,
+                           int var_idx, JSAtom var_name,
+                           BOOL is_const, BOOL is_lexical,
+                           JSVarKindEnum var_kind);
+
+static int add_import(JSParseState *s, JSModuleDef *m,
+                      JSAtom local_name, JSAtom import_name)
+{
+    JSContext *ctx = s->ctx;
+    int i, var_idx;
+    JSImportEntry *mi;
+    BOOL is_local;
+
+    if (local_name == JS_ATOM_arguments || local_name == JS_ATOM_eval)
+        return js_parse_error(s, "invalid import binding");
+
+    if (local_name != JS_ATOM_default) {
+        for (i = 0; i < s->cur_func->closure_var_count; i++) {
+            if (s->cur_func->closure_var[i].var_name == local_name)
+                return js_parse_error(s, "duplicate import binding");
+        }
+    }
+
+    is_local = (import_name == JS_ATOM__star_);
+    var_idx = add_closure_var(ctx, s->cur_func, is_local, FALSE,
+                              m->import_entries_count,
+                              local_name, TRUE, TRUE, FALSE);
+    if (var_idx < 0)
+        return -1;
+    if (js_resize_array(ctx, (void **)&m->import_entries,
+                        sizeof(JSImportEntry),
+                        &m->import_entries_size, &m->import_entries_count,
+                        m->import_entries_count + 1))
+        return -1;
+    mi = &m->import_entries[m->import_entries_count - 1];
+    mi->import_name = JS_DupAtom(ctx, import_name);
+    mi->var_idx = var_idx;
+    return 0;
+}
+
+static __exception int js_parse_import(JSParseState *s)
+{
+    JSContext *ctx = s->ctx;
+    JSModuleDef *m = s->cur_func->module;
+    JSAtom local_name, import_name, module_name;
+    int first_import, i, idx;
+
+    if (next_token(s))
+        return -1;
+
+    first_import = m->import_entries_count;
+    if (s->token.val == TOK_STRING) {
+        module_name = JS_ValueToAtom(ctx, s->token.u.str.str);
+        if (module_name == JS_ATOM_NULL)
+            return -1;
+        if (next_token(s)) {
+            JS_FreeAtom(ctx, module_name);
+            return -1;
+        }
+    } else {
+        if (s->token.val == TOK_IDENT) {
+            if (s->token.u.ident.is_reserved) {
+                return js_parse_error_reserved_identifier(s);
+            }
+            /* "default" import */
+            local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            import_name = JS_ATOM_default;
+            if (next_token(s))
+                goto fail;
+            if (add_import(s, m, local_name, import_name))
+                goto fail;
+            JS_FreeAtom(ctx, local_name);
+
+            if (s->token.val != ',')
+                goto end_import_clause;
+            if (next_token(s))
+                return -1;
+        }
+
+        if (s->token.val == '*') {
+            /* name space import */
+            if (next_token(s))
+                return -1;
+            if (!token_is_pseudo_keyword(s, JS_ATOM_as))
+                return js_parse_error(s, "expecting 'as'");
+            if (next_token(s))
+                return -1;
+            local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            import_name = JS_ATOM__star_;
+            if (next_token(s))
+                goto fail;
+            if (add_import(s, m, local_name, import_name))
+                goto fail;
+            JS_FreeAtom(ctx, local_name);
+        } else if (s->token.val == '{') {
+            if (next_token(s))
+                return -1;
+
+            while (s->token.val != '}') {
+                if (!token_is_ident(s->token.val)) {
+                    js_parse_error(s, "identifier expected");
+                    return -1;
+                }
+                import_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+                local_name = JS_ATOM_NULL;
+                if (next_token(s))
+                    goto fail;
+                if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
+                    if (next_token(s))
+                        goto fail;
+                    if (!token_is_ident(s->token.val)) {
+                        js_parse_error(s, "identifier expected");
+                        goto fail;
+                    }
+                    local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+                    if (next_token(s)) {
+                    fail:
+                        JS_FreeAtom(ctx, local_name);
+                        JS_FreeAtom(ctx, import_name);
+                        return -1;
+                    }
+                } else {
+                    local_name = JS_DupAtom(ctx, import_name);
+                }
+                if (add_import(s, m, local_name, import_name))
+                    goto fail;
+                JS_FreeAtom(ctx, local_name);
+                JS_FreeAtom(ctx, import_name);
+                if (s->token.val != ',')
+                    break;
+                if (next_token(s))
+                    return -1;
+            }
+            if (js_parse_expect(s, '}'))
+                return -1;
+        }
+    end_import_clause:
+        module_name = js_parse_from_clause(s);
+        if (module_name == JS_ATOM_NULL)
+            return -1;
+    }
+    idx = add_req_module_entry(ctx, m, module_name);
+    JS_FreeAtom(ctx, module_name);
+    if (idx < 0)
+        return -1;
+    for(i = first_import; i < m->import_entries_count; i++)
+        m->import_entries[i].req_module_idx = idx;
+
+    return js_parse_expect_semi(s);
+}
+
+static __exception int js_parse_source_element(JSParseState *s)
+{
+    JSFunctionDef *fd = s->cur_func;
+    int tok;
+    
+    if (s->token.val == TOK_FUNCTION ||
+        (token_is_pseudo_keyword(s, JS_ATOM_async) &&
+         peek_token(s, TRUE) == TOK_FUNCTION)) {
+        if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT,
+                                   JS_FUNC_NORMAL, JS_ATOM_NULL,
+                                   s->token.ptr, s->token.line_num))
+            return -1;
+    } else if (s->token.val == TOK_EXPORT && fd->module) {
+        if (js_parse_export(s))
+            return -1;
+    } else if (s->token.val == TOK_IMPORT && fd->module &&
+               ((tok = peek_token(s, FALSE)) != '(' && tok != '.'))  {
+        /* the peek_token is needed to avoid confusion with ImportCall
+           (dynamic import) or import.meta */
+        if (js_parse_import(s))
+            return -1;
+    } else {
+        if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
+            return -1;
+    }
+    return 0;
+}
+
+static JSFunctionDef *js_new_function_def(JSContext *ctx,
+                                          JSFunctionDef *parent,
+                                          BOOL is_eval,
+                                          BOOL is_func_expr,
+                                          const char *filename, int line_num)
+{
+    JSFunctionDef *fd;
+
+    fd = js_mallocz(ctx, sizeof(*fd));
+    if (!fd)
+        return NULL;
+
+    fd->ctx = ctx;
+    init_list_head(&fd->child_list);
+
+    /* insert in parent list */
+    fd->parent = parent;
+    fd->parent_cpool_idx = -1;
+    if (parent) {
+        list_add_tail(&fd->link, &parent->child_list);
+        fd->js_mode = parent->js_mode;
+        fd->parent_scope_level = parent->scope_level;
+    }
+
+    fd->is_eval = is_eval;
+    fd->is_func_expr = is_func_expr;
+    js_dbuf_init(ctx, &fd->byte_code);
+    fd->last_opcode_pos = -1;
+    fd->func_name = JS_ATOM_NULL;
+    fd->var_object_idx = -1;
+    fd->arguments_var_idx = -1;
+    fd->func_var_idx = -1;
+    fd->eval_ret_idx = -1;
+    fd->this_var_idx = -1;
+    fd->new_target_var_idx = -1;
+    fd->this_active_func_var_idx = -1;
+    fd->home_object_var_idx = -1;
+
+    /* XXX: should distinguish arg, var and var object and body scopes */
+    fd->scope_level = 0;  /* 0: var/arg scope, 1:body scope */
+    fd->scope_first = -1;
+    fd->scopes = fd->def_scope_array;
+    fd->scope_size = countof(fd->def_scope_array);
+    fd->scope_count = 1;
+    fd->scopes[0].first = -1;
+    fd->scopes[0].parent = -1;
+
+    fd->filename = JS_NewAtom(ctx, filename);
+    fd->line_num = line_num;
+
+    js_dbuf_init(ctx, &fd->pc2line);
+    //fd->pc2line_last_line_num = line_num;
+    //fd->pc2line_last_pc = 0;
+    fd->last_opcode_line_num = line_num;
+
+    return fd;
+}
+
+static void free_bytecode_atoms(JSRuntime *rt,
+                                const uint8_t *bc_buf, int bc_len,
+                                BOOL use_short_opcodes)
+{
+    int pos, len, op;
+    JSAtom atom;
+    const JSOpCode *oi;
+    
+    pos = 0;
+    while (pos < bc_len) {
+        op = bc_buf[pos];
+        if (use_short_opcodes)
+            oi = &short_opcode_info(op);
+        else
+            oi = &opcode_info[op];
+            
+        len = oi->size;
+        switch(oi->fmt) {
+        case OP_FMT_atom:
+        case OP_FMT_atom_u8:
+        case OP_FMT_atom_u16:
+        case OP_FMT_atom_label_u8:
+        case OP_FMT_atom_label_u16:
+            atom = get_u32(bc_buf + pos + 1);
+            JS_FreeAtomRT(rt, atom);
+            break;
+        default:
+            break;
+        }
+        pos += len;
+    }
+}
+
+static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd)
+{
+    int i;
+    struct list_head *el, *el1;
+
+    /* free the child functions */
+    list_for_each_safe(el, el1, &fd->child_list) {
+        JSFunctionDef *fd1;
+        fd1 = list_entry(el, JSFunctionDef, link);
+        js_free_function_def(ctx, fd1);
+    }
+
+    free_bytecode_atoms(ctx->rt, fd->byte_code.buf, fd->byte_code.size,
+                        fd->use_short_opcodes);
+    dbuf_free(&fd->byte_code);
+    js_free(ctx, fd->jump_slots);
+    js_free(ctx, fd->label_slots);
+    js_free(ctx, fd->line_number_slots);
+
+    for(i = 0; i < fd->cpool_count; i++) {
+        JS_FreeValue(ctx, fd->cpool[i]);
+    }
+    js_free(ctx, fd->cpool);
+
+    JS_FreeAtom(ctx, fd->func_name);
+
+    for(i = 0; i < fd->var_count; i++) {
+        JS_FreeAtom(ctx, fd->vars[i].var_name);
+    }
+    js_free(ctx, fd->vars);
+    for(i = 0; i < fd->arg_count; i++) {
+        JS_FreeAtom(ctx, fd->args[i].var_name);
+    }
+    js_free(ctx, fd->args);
+
+    for(i = 0; i < fd->hoisted_def_count; i++) {
+        JS_FreeAtom(ctx, fd->hoisted_def[i].var_name);
+    }
+    js_free(ctx, fd->hoisted_def);
+
+    for(i = 0; i < fd->closure_var_count; i++) {
+        JSClosureVar *cv = &fd->closure_var[i];
+        JS_FreeAtom(ctx, cv->var_name);
+    }
+    js_free(ctx, fd->closure_var);
+
+    if (fd->scopes != fd->def_scope_array)
+        js_free(ctx, fd->scopes);
+
+    JS_FreeAtom(ctx, fd->filename);
+    dbuf_free(&fd->pc2line);
+
+    js_free(ctx, fd->source);
+
+    if (fd->parent) {
+        /* remove in parent list */
+        list_del(&fd->link);
+    }
+    js_free(ctx, fd);
+}
+
+#ifdef DUMP_BYTECODE
+static const char *skip_lines(const char *p, int n) {
+    while (n-- > 0 && *p) {
+        while (*p && *p++ != '\n')
+            continue;
+    }
+    return p;
+}
+
+static void print_lines(const char *source, int line, int line1) {
+    const char *s = source;
+    const char *p = skip_lines(s, line);
+    if (*p) {
+        while (line++ < line1) {
+            p = skip_lines(s = p, 1);
+            printf(";; %.*s", (int)(p - s), s);
+            if (!*p) {
+                if (p[-1] != '\n')
+                    printf("\n");
+                break;
+            }
+        }
+    }
+}
+
+static void dump_byte_code(JSContext *ctx, int pass,
+                           const uint8_t *tab, int len,
+                           const JSVarDef *args, int arg_count,
+                           const JSVarDef *vars, int var_count,
+                           const JSClosureVar *closure_var, int closure_var_count,
+                           const JSValue *cpool, uint32_t cpool_count,
+                           const char *source, int line_num,
+                           const LabelSlot *label_slots, JSFunctionBytecode *b)
+{
+    const JSOpCode *oi;
+    int pos, pos_next, op, size, idx, addr, line, line1, in_source;
+    uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits));
+    BOOL use_short_opcodes = (b != NULL);
+
+    /* scan for jump targets */
+    for (pos = 0; pos < len; pos = pos_next) {
+        op = tab[pos];
+        if (use_short_opcodes)
+            oi = &short_opcode_info(op);
+        else
+            oi = &opcode_info[op];
+        pos_next = pos + oi->size;
+        if (op < OP_COUNT) {
+            switch (oi->fmt) {
+#if SHORT_OPCODES
+            case OP_FMT_label8:
+                pos++;
+                addr = (int8_t)tab[pos];
+                goto has_addr;
+            case OP_FMT_label16:
+                pos++;
+                addr = (int16_t)get_u16(tab + pos);
+                goto has_addr;
+#endif
+            case OP_FMT_atom_label_u8:
+            case OP_FMT_atom_label_u16:
+                pos += 4;
+                /* fall thru */
+            case OP_FMT_label:
+            case OP_FMT_label_u16:
+                pos++;
+                addr = get_u32(tab + pos);
+                goto has_addr;
+            has_addr:
+                if (pass == 1)
+                    addr = label_slots[addr].pos;
+                if (pass == 2)
+                    addr = label_slots[addr].pos2;
+                if (pass == 3)
+                    addr += pos;
+                if (addr >= 0 && addr < len)
+                    bits[addr] |= 1;
+                break;
+            }
+        }
+    }
+    in_source = 0;
+    if (source) {
+        /* Always print first line: needed if single line */
+        print_lines(source, 0, 1);
+        in_source = 1;
+    }
+    line1 = line = 1;
+    pos = 0;
+    while (pos < len) {
+        op = tab[pos];
+        if (source) {
+            if (b) {
+                line1 = find_line_num(ctx, b, pos) - line_num + 1;
+            } else if (op == OP_line_num) {
+                line1 = get_u32(tab + pos + 1) - line_num + 1;
+            }
+            if (line1 > line) {
+                if (!in_source)
+                    printf("\n");
+                in_source = 1;
+                print_lines(source, line, line1);
+                line = line1;
+                //bits[pos] |= 2;
+            }
+        }
+        if (in_source)
+            printf("\n");
+        in_source = 0;
+        if (op >= OP_COUNT) {
+            printf("invalid opcode (0x%02x)\n", op);
+            pos++;
+            continue;
+        }
+        if (use_short_opcodes)
+            oi = &short_opcode_info(op);
+        else
+            oi = &opcode_info[op];
+        size = oi->size;
+        if (pos + size > len) {
+            printf("truncated opcode (0x%02x)\n", op);
+            break;
+        }
+#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 16)
+        {
+            int i, x, x0;
+            x = x0 = printf("%5d ", pos);
+            for (i = 0; i < size; i++) {
+                if (i == 6) {
+                    printf("\n%*s", x = x0, "");
+                }
+                x += printf(" %02X", tab[pos + i]);
+            }
+            printf("%*s", x0 + 20 - x, "");
+        }
+#endif
+        if (bits[pos]) {
+            printf("%5d:  ", pos);
+        } else {
+            printf("        ");
+        }
+        printf("%s", oi->name);
+        pos++;
+        switch(oi->fmt) {
+        case OP_FMT_none_int:
+            printf(" %d", op - OP_push_0);
+            break;
+        case OP_FMT_npopx:
+            printf(" %d", op - OP_call0);
+            break;
+        case OP_FMT_u8:
+            printf(" %u", get_u8(tab + pos));
+            break;
+        case OP_FMT_i8:
+            printf(" %d", get_i8(tab + pos));
+            break;
+        case OP_FMT_u16:
+        case OP_FMT_npop:
+            printf(" %u", get_u16(tab + pos));
+            break;
+        case OP_FMT_npop_u16:
+            printf(" %u,%u", get_u16(tab + pos), get_u16(tab + pos + 2));
+            break;
+        case OP_FMT_i16:
+            printf(" %d", get_i16(tab + pos));
+            break;
+        case OP_FMT_i32:
+            printf(" %d", get_i32(tab + pos));
+            break;
+        case OP_FMT_u32:
+            printf(" %u", get_u32(tab + pos));
+            break;
+#if SHORT_OPCODES
+        case OP_FMT_label8:
+            addr = get_i8(tab + pos);
+            goto has_addr1;
+        case OP_FMT_label16:
+            addr = get_i16(tab + pos);
+            goto has_addr1;
+#endif
+        case OP_FMT_label:
+            addr = get_u32(tab + pos);
+            goto has_addr1;
+        has_addr1:
+            if (pass == 1)
+                printf(" %u:%u", addr, label_slots[addr].pos);
+            if (pass == 2)
+                printf(" %u:%u", addr, label_slots[addr].pos2);
+            if (pass == 3)
+                printf(" %u", addr + pos);
+            break;
+        case OP_FMT_label_u16:
+            addr = get_u32(tab + pos);
+            if (pass == 1)
+                printf(" %u:%u", addr, label_slots[addr].pos);
+            if (pass == 2)
+                printf(" %u:%u", addr, label_slots[addr].pos2);
+            if (pass == 3)
+                printf(" %u", addr + pos);
+            printf(",%u", get_u16(tab + pos + 4));
+            break;
+#if SHORT_OPCODES
+        case OP_FMT_const8:
+            idx = get_u8(tab + pos);
+            goto has_pool_idx;
+#endif
+        case OP_FMT_const:
+            idx = get_u32(tab + pos);
+            goto has_pool_idx;
+        has_pool_idx:
+            printf(" %u: ", idx);
+            if (idx < cpool_count) {
+                JS_DumpValue(ctx, cpool[idx]);
+            }
+            break;
+        case OP_FMT_atom:
+            printf(" ");
+            print_atom(ctx, get_u32(tab + pos));
+            break;
+        case OP_FMT_atom_u8:
+            printf(" ");
+            print_atom(ctx, get_u32(tab + pos));
+            printf(",%d", get_u8(tab + pos + 4));
+            break;
+        case OP_FMT_atom_u16:
+            printf(" ");
+            print_atom(ctx, get_u32(tab + pos));
+            printf(",%d", get_u16(tab + pos + 4));
+            break;
+        case OP_FMT_atom_label_u8:
+        case OP_FMT_atom_label_u16:
+            printf(" ");
+            print_atom(ctx, get_u32(tab + pos));
+            addr = get_u32(tab + pos + 4);
+            if (pass == 1)
+                printf(",%u:%u", addr, label_slots[addr].pos);
+            if (pass == 2)
+                printf(",%u:%u", addr, label_slots[addr].pos2);
+            if (pass == 3)
+                printf(",%u", addr + pos + 4);
+            if (oi->fmt == OP_FMT_atom_label_u8)
+                printf(",%u", get_u8(tab + pos + 8));
+            else
+                printf(",%u", get_u16(tab + pos + 8));
+            break;
+        case OP_FMT_none_loc:
+            idx = (op - OP_get_loc0) % 4;
+            goto has_loc;
+        case OP_FMT_loc8:
+            idx = get_u8(tab + pos);
+            goto has_loc;
+        case OP_FMT_loc:
+            idx = get_u16(tab + pos);
+        has_loc:
+            printf(" %d: ", idx);
+            if (idx < var_count) {
+                print_atom(ctx, vars[idx].var_name);
+            }
+            break;
+        case OP_FMT_none_arg:
+            idx = (op - OP_get_arg0) % 4;
+            goto has_arg;
+        case OP_FMT_arg:
+            idx = get_u16(tab + pos);
+        has_arg:
+            printf(" %d: ", idx);
+            if (idx < arg_count) {
+                print_atom(ctx, args[idx].var_name);
+            }
+            break;
+        case OP_FMT_none_var_ref:
+            idx = (op - OP_get_var_ref0) % 4;
+            goto has_var_ref;
+        case OP_FMT_var_ref:
+            idx = get_u16(tab + pos);
+        has_var_ref:
+            printf(" %d: ", idx);
+            if (idx < closure_var_count) {
+                print_atom(ctx, closure_var[idx].var_name);
+            }
+            break;
+        default:
+            break;
+        }
+        printf("\n");
+        pos += oi->size - 1;
+    }
+    if (source) {
+        if (!in_source)
+            printf("\n");
+        print_lines(source, line, INT32_MAX);
+    }
+    js_free(ctx, bits);
+}
+
+static __maybe_unused void dump_pc2line(JSContext *ctx, const uint8_t *buf, int len,
+                                                 int line_num)
+{
+    const uint8_t *p_end, *p_next, *p;
+    int pc, v;
+    unsigned int op;
+
+    if (len <= 0)
+        return;
+
+    printf("%5s %5s\n", "PC", "LINE");
+
+    p = buf;
+    p_end = buf + len;
+    pc = 0;
+    while (p < p_end) {
+        op = *p++;
+        if (op == 0) {
+            v = unicode_from_utf8(p, p_end - p, &p_next);
+            if (v < 0)
+                goto fail;
+            pc += v;
+            p = p_next;
+            v = unicode_from_utf8(p, p_end - p, &p_next);
+            if (v < 0) {
+            fail:
+                printf("invalid pc2line encode pos=%d\n", (int)(p - buf));
+                return;
+            }
+            if (!(v & 1)) {
+                v = v >> 1;
+            } else {
+                v = -(v >> 1) - 1;
+            }
+            line_num += v;
+            p = p_next;
+        } else {
+            op -= PC2LINE_OP_FIRST;
+            pc += (op / PC2LINE_RANGE);
+            line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE;
+        }
+        printf("%5d %5d\n", pc, line_num);
+    }
+}
+
+static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b)
+{
+    int i;
+    char atom_buf[ATOM_GET_STR_BUF_SIZE];
+    const char *str;
+
+    if (b->has_debug && b->debug.filename != JS_ATOM_NULL) {
+        str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename);
+        printf("%s:%d: ", str, b->debug.line_num);
+    }
+
+    str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name);
+    printf("function: %s%s\n", &"*"[b->func_kind != JS_FUNC_GENERATOR], str);
+    if (b->js_mode) {
+        printf("  mode:");
+        if (b->js_mode & JS_MODE_STRICT)
+            printf(" strict");
+#ifdef CONFIG_BIGNUM
+        if (b->js_mode & JS_MODE_MATH)
+            printf(" math");
+#endif
+        printf("\n");
+    }
+    if (b->arg_count && b->vardefs) {
+        printf("  args:");
+        for(i = 0; i < b->arg_count; i++) {
+            printf(" %s", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf),
+                                        b->vardefs[i].var_name));
+        }
+        printf("\n");
+    }
+    if (b->var_count && b->vardefs) {
+        printf("  locals:\n");
+        for(i = 0; i < b->var_count; i++) {
+            JSVarDef *vd = &b->vardefs[b->arg_count + i];
+            printf("%5d: %s %s", i,
+                   vd->var_kind == JS_VAR_CATCH ? "catch" :
+                   (vd->var_kind == JS_VAR_FUNCTION_DECL ||
+                    vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" :
+                   vd->is_const ? "const" :
+                   vd->is_lexical ? "let" : "var",
+                   JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name));
+            if (vd->scope_level)
+                printf(" [level:%d next:%d]", vd->scope_level, vd->scope_next);
+            printf("\n");
+        }
+    }
+    if (b->closure_var_count) {
+        printf("  closure vars:\n");
+        for(i = 0; i < b->closure_var_count; i++) {
+            JSClosureVar *cv = &b->closure_var[i];
+            printf("%5d: %s %s:%s%d %s\n", i,
+                   JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name),
+                   cv->is_local ? "local" : "parent",
+                   cv->is_arg ? "arg" : "loc", cv->var_idx,
+                   cv->is_const ? "const" :
+                   cv->is_lexical ? "let" : "var");
+        }
+    }
+    printf("  stack_size: %d\n", b->stack_size);
+    printf("  opcodes:\n");
+    dump_byte_code(ctx, 3, b->byte_code_buf, b->byte_code_len,
+                   b->vardefs, b->arg_count,
+                   b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count,
+                   b->closure_var, b->closure_var_count,
+                   b->cpool, b->cpool_count,
+                   b->has_debug ? b->debug.source : NULL,
+                   b->has_debug ? b->debug.line_num : -1, NULL, b);
+#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32)
+    if (b->has_debug)
+        dump_pc2line(ctx, b->debug.pc2line_buf, b->debug.pc2line_len, b->debug.line_num);
+#endif
+    printf("\n");
+}
+#endif
+
+static int add_closure_var(JSContext *ctx, JSFunctionDef *s,
+                           BOOL is_local, BOOL is_arg,
+                           int var_idx, JSAtom var_name,
+                           BOOL is_const, BOOL is_lexical,
+                           JSVarKindEnum var_kind)
+{
+    JSClosureVar *cv;
+
+    /* the closure variable indexes are currently stored on 16 bits */
+    if (s->closure_var_count >= JS_MAX_LOCAL_VARS) {
+        JS_ThrowInternalError(ctx, "too many closure variables");
+        return -1;
+    }
+
+    if (s->closure_var_count >= s->closure_var_size) {
+        JSClosureVar *new_tab;
+        int new_size;
+        size_t slack;
+        new_size = max_int(s->closure_var_count + 1,
+                           s->closure_var_size * 3 / 2);
+        new_tab = js_realloc2(ctx, s->closure_var,
+                              new_size * sizeof(JSClosureVar), &slack);
+        if (!new_tab)
+            return -1;
+        new_size += slack / sizeof(*new_tab);
+        s->closure_var = new_tab;
+        s->closure_var_size = new_size;
+    }
+    cv = &s->closure_var[s->closure_var_count++];
+    cv->is_local = is_local;
+    cv->is_arg = is_arg;
+    cv->is_const = is_const;
+    cv->is_lexical = is_lexical;
+    cv->var_kind = var_kind;
+    cv->var_idx = var_idx;
+    cv->var_name = JS_DupAtom(ctx, var_name);
+    return s->closure_var_count - 1;
+}
+
+static int find_closure_var(JSContext *ctx, JSFunctionDef *s,
+                            JSAtom var_name)
+{
+    int i;
+    for(i = 0; i < s->closure_var_count; i++) {
+        JSClosureVar *cv = &s->closure_var[i];
+        if (cv->var_name == var_name)
+            return i;
+    }
+    return -1;
+}
+
+/* 'fd' must be a parent of 's'. Create in 's' a closure referencing a
+   local variable (is_local = TRUE) or a closure (is_local = FALSE) in
+   'fd' */
+static int get_closure_var2(JSContext *ctx, JSFunctionDef *s,
+                            JSFunctionDef *fd, BOOL is_local,
+                            BOOL is_arg, int var_idx, JSAtom var_name,
+                            BOOL is_const, BOOL is_lexical,
+                            JSVarKindEnum var_kind)
+{
+    int i;
+
+    if (fd != s->parent) {
+        var_idx = get_closure_var2(ctx, s->parent, fd, is_local,
+                                   is_arg, var_idx, var_name,
+                                   is_const, is_lexical, var_kind);
+        if (var_idx < 0)
+            return -1;
+        is_local = FALSE;
+    }
+    for(i = 0; i < s->closure_var_count; i++) {
+        JSClosureVar *cv = &s->closure_var[i];
+        if (cv->var_idx == var_idx && cv->is_arg == is_arg &&
+            cv->is_local == is_local)
+            return i;
+    }
+    return add_closure_var(ctx, s, is_local, is_arg, var_idx, var_name,
+                           is_const, is_lexical, var_kind);
+}
+
+static int get_closure_var(JSContext *ctx, JSFunctionDef *s,
+                           JSFunctionDef *fd, BOOL is_arg,
+                           int var_idx, JSAtom var_name,
+                           BOOL is_const, BOOL is_lexical,
+                           JSVarKindEnum var_kind)
+{
+    return get_closure_var2(ctx, s, fd, TRUE, is_arg,
+                            var_idx, var_name, is_const, is_lexical,
+                            var_kind);
+}
+
+static int get_with_scope_opcode(int op)
+{
+    if (op == OP_scope_get_var_undef)
+        return OP_with_get_var;
+    else
+        return OP_with_get_var + (op - OP_scope_get_var);
+}
+
+static BOOL can_opt_put_ref_value(const uint8_t *bc_buf, int pos)
+{
+    int opcode = bc_buf[pos];
+    return (bc_buf[pos + 1] == OP_put_ref_value &&
+            (opcode == OP_insert3 ||
+             opcode == OP_perm4 ||
+             //opcode == OP_nop ||
+             opcode == OP_rot3l));
+}
+
+static BOOL can_opt_put_global_ref_value(const uint8_t *bc_buf, int pos)
+{
+    int opcode = bc_buf[pos];
+    return (bc_buf[pos + 1] == OP_put_ref_value &&
+            (opcode == OP_insert3 ||
+             opcode == OP_perm4 ||
+             //opcode == OP_nop ||
+             opcode == OP_rot3l));
+}
+
+static int optimize_scope_make_ref(JSContext *ctx, JSFunctionDef *s,
+                                   DynBuf *bc, uint8_t *bc_buf,
+                                   LabelSlot *ls, int pos_next,
+                                   int get_op, int var_idx)
+{
+    int label_pos, end_pos, pos;
+
+    /* XXX: should optimize `loc(a) += expr` as `expr add_loc(a)`
+       but only if expr does not modify `a`.
+       should scan the code between pos_next and label_pos
+       for operations that can potentially change `a`:
+       OP_scope_make_ref(a), function calls, jumps and gosub.
+     */
+    /* replace the reference get/put with normal variable
+       accesses */
+    if (bc_buf[pos_next] == OP_get_ref_value) {
+        dbuf_putc(bc, get_op);
+        dbuf_put_u16(bc, var_idx);
+        pos_next++;
+    }
+    /* remove the OP_label to make room for replacement */
+    /* label should have a refcount of 0 anyway */
+    /* XXX: should avoid this patch by inserting nops in phase 1 */
+    label_pos = ls->pos;
+    pos = label_pos - 5;
+    assert(bc_buf[pos] == OP_label);
+    /* label points to an instruction pair:
+       - insert3 / put_ref_value
+       - perm4 / put_ref_value
+       - rot3l / put_ref_value
+       - nop / put_ref_value
+     */
+    end_pos = label_pos + 2;
+    if (bc_buf[label_pos] == OP_insert3)
+        bc_buf[pos++] = OP_dup;
+    bc_buf[pos] = get_op + 1;
+    put_u16(bc_buf + pos + 1, var_idx);
+    pos += 3;
+    /* pad with OP_nop */
+    while (pos < end_pos)
+        bc_buf[pos++] = OP_nop;
+    return pos_next;
+}
+
+static int optimize_scope_make_global_ref(JSContext *ctx, JSFunctionDef *s,
+                                          DynBuf *bc, uint8_t *bc_buf,
+                                          LabelSlot *ls, int pos_next,
+                                          JSAtom var_name)
+{
+    int label_pos, end_pos, pos, op;
+    BOOL is_strict;
+    is_strict = ((s->js_mode & JS_MODE_STRICT) != 0);
+
+    /* replace the reference get/put with normal variable
+       accesses */
+    if (is_strict) {
+        /* need to check if the variable exists before evaluating the right
+           expression */
+        /* XXX: need an extra OP_true if destructuring an array */
+        dbuf_putc(bc, OP_check_var);
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+    } else {
+        /* XXX: need 2 extra OP_true if destructuring an array */
+    }
+    if (bc_buf[pos_next] == OP_get_ref_value) {
+        dbuf_putc(bc, OP_get_var);
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        pos_next++;
+    }
+    /* remove the OP_label to make room for replacement */
+    /* label should have a refcount of 0 anyway */
+    /* XXX: should have emitted several OP_nop to avoid this kludge */
+    label_pos = ls->pos;
+    pos = label_pos - 5;
+    assert(bc_buf[pos] == OP_label);
+    end_pos = label_pos + 2;
+    op = bc_buf[label_pos];
+    if (is_strict) {
+        switch(op) {
+        case OP_insert3:
+            op = OP_insert2;
+            break;
+        case OP_perm4:
+            op = OP_perm3;
+            break;
+        case OP_rot3l:
+            op = OP_swap;
+            break;
+        default:
+            abort();
+        }
+        bc_buf[pos++] = op;
+    } else {
+        if (op == OP_insert3)
+            bc_buf[pos++] = OP_dup;
+    }
+    if (is_strict) {
+        bc_buf[pos] = OP_put_var_strict;
+        /* XXX: need 1 extra OP_drop if destructuring an array */
+    } else {
+        bc_buf[pos] = OP_put_var;
+        /* XXX: need 2 extra OP_drop if destructuring an array */
+    }
+    put_u32(bc_buf + pos + 1, JS_DupAtom(ctx, var_name));
+    pos += 5;
+    /* pad with OP_nop */
+    while (pos < end_pos)
+        bc_buf[pos++] = OP_nop;
+    return pos_next;
+}
+
+static int add_var_this(JSContext *ctx, JSFunctionDef *fd)
+{
+    int idx;
+    idx = add_var(ctx, fd, JS_ATOM_this);
+    if (idx >= 0 && fd->is_derived_class_constructor) {
+        JSVarDef *vd = &fd->vars[idx];
+        /* XXX: should have is_this flag or var type */
+        vd->is_lexical = 1; /* used to trigger 'uninitialized' checks
+                               in a derived class constructor */
+    }
+    return idx;
+}
+
+static int resolve_pseudo_var(JSContext *ctx, JSFunctionDef *s,
+                               JSAtom var_name)
+{
+    int var_idx;
+
+    if (!s->has_this_binding)
+        return -1;
+    switch(var_name) {
+    case JS_ATOM_home_object:
+        /* 'home_object' pseudo variable */
+        var_idx = s->home_object_var_idx = add_var(ctx, s, var_name);
+        break;
+    case JS_ATOM_this_active_func:
+        /* 'this.active_func' pseudo variable */
+        var_idx = s->this_active_func_var_idx = add_var(ctx, s, var_name);
+        break;
+    case JS_ATOM_new_target:
+        /* 'new.target' pseudo variable */
+        var_idx = s->new_target_var_idx = add_var(ctx, s, var_name);
+        break;
+    case JS_ATOM_this:
+        /* 'this' pseudo variable */
+        var_idx = s->this_var_idx = add_var_this(ctx, s);
+        break;
+    default:
+        var_idx = -1;
+        break;
+    }
+    return var_idx;
+}
+
+/* return the position of the next opcode */
+static int resolve_scope_var(JSContext *ctx, JSFunctionDef *s,
+                             JSAtom var_name, int scope_level, int op,
+                             DynBuf *bc, uint8_t *bc_buf,
+                             LabelSlot *ls, int pos_next, int arg_valid)
+{
+    int idx, var_idx, is_put;
+    int label_done;
+    BOOL is_func_var = FALSE;
+    JSFunctionDef *fd;
+    JSVarDef *vd;
+    BOOL is_pseudo_var;
+
+    label_done = -1;
+
+    /* XXX: could be simpler to use a specific function to
+       resolve the pseudo variables */
+    is_pseudo_var = (var_name == JS_ATOM_home_object ||
+                     var_name == JS_ATOM_this_active_func ||
+                     var_name == JS_ATOM_new_target ||
+                     var_name == JS_ATOM_this);
+
+    /* resolve local scoped variables */
+    var_idx = -1;
+    for (idx = s->scopes[scope_level].first; idx >= 0;) {
+        vd = &s->vars[idx];
+        if (vd->var_name == var_name) {
+            if (op == OP_scope_put_var || op == OP_scope_make_ref) {
+                if (vd->is_const) {
+                    dbuf_putc(bc, OP_throw_var);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                    dbuf_putc(bc, JS_THROW_VAR_RO);
+                    goto done;
+                }
+                is_func_var = vd->is_func_var;
+            }
+            var_idx = idx;
+            break;
+        } else
+        if (vd->var_name == JS_ATOM__with_ && !is_pseudo_var) {
+            dbuf_putc(bc, OP_get_loc);
+            dbuf_put_u16(bc, idx);
+            dbuf_putc(bc, get_with_scope_opcode(op));
+            dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+            label_done = new_label_fd(s, label_done);
+            dbuf_put_u32(bc, label_done);
+            dbuf_putc(bc, 1);
+            update_label(s, label_done, 1);
+            s->jump_size++;
+        }
+        idx = vd->scope_next;
+    }
+    if (var_idx < 0) {
+        /* XXX: scoping issues:
+           should not resolve vars from the function body during argument parse,
+           `arguments` and function-name should not be hidden by later vars.
+         */
+        var_idx = find_var(ctx, s, var_name);
+        if (var_idx >= 0) {
+            if (scope_level == 0
+            &&  (var_idx & ARGUMENT_VAR_OFFSET)
+            &&  (var_idx - ARGUMENT_VAR_OFFSET) >= arg_valid) {
+                /* referring to an uninitialized argument */
+                dbuf_putc(bc, OP_throw_var);
+                dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                dbuf_putc(bc, JS_THROW_VAR_UNINITIALIZED);
+            }
+            if (!(var_idx & ARGUMENT_VAR_OFFSET))
+                is_func_var = s->vars[var_idx].is_func_var;
+        }
+
+        if (var_idx < 0 && is_pseudo_var)
+            var_idx = resolve_pseudo_var(ctx, s, var_name);
+
+        if (var_idx < 0 && var_name == JS_ATOM_arguments &&
+            s->has_arguments_binding) {
+            /* 'arguments' pseudo variable */
+            var_idx = add_arguments_var(ctx, s, var_name);
+        }
+        if (var_idx < 0 && s->is_func_expr && var_name == s->func_name) {
+            /* add a new variable with the function name */
+            var_idx = add_func_var(ctx, s, var_name);
+            is_func_var = TRUE;
+        }
+    }
+    if (var_idx >= 0) {
+        /* OP_scope_put_var_init is only used to initialize a
+           lexical variable, so it is never used in a with or var object. It
+           can be used with a closure (module global variable case). */
+        switch (op) {
+        case OP_scope_make_ref:
+            if (is_func_var) {
+                /* Create a dummy object reference for the func_var */
+                dbuf_putc(bc, OP_object);
+                dbuf_putc(bc, OP_get_loc);
+                dbuf_put_u16(bc, var_idx);
+                dbuf_putc(bc, OP_define_field);
+                dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                dbuf_putc(bc, OP_push_atom_value);
+                dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+            } else
+            if (label_done == -1 && can_opt_put_ref_value(bc_buf, ls->pos)) {
+                int get_op;
+                if (var_idx & ARGUMENT_VAR_OFFSET) {
+                    get_op = OP_get_arg;
+                    var_idx -= ARGUMENT_VAR_OFFSET;
+                } else {
+                    if (s->vars[var_idx].is_lexical)
+                        get_op = OP_get_loc_check;
+                    else
+                        get_op = OP_get_loc;
+                }
+                pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls,
+                                                   pos_next, get_op, var_idx);
+            } else {
+                /* Create a dummy object with a named slot that is
+                   a reference to the local variable */
+                if (var_idx & ARGUMENT_VAR_OFFSET) {
+                    dbuf_putc(bc, OP_make_arg_ref);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                    dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET);
+                } else {
+                    dbuf_putc(bc, OP_make_loc_ref);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                    dbuf_put_u16(bc, var_idx);
+                }
+            }
+            break;
+        case OP_scope_get_ref:
+            dbuf_putc(bc, OP_undefined);
+            /* fall thru */
+        case OP_scope_get_var_undef:
+        case OP_scope_get_var:
+        case OP_scope_put_var:
+        case OP_scope_put_var_init:
+            is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init);
+            if (var_idx & ARGUMENT_VAR_OFFSET) {
+                dbuf_putc(bc, OP_get_arg + is_put);
+                dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET);
+                /* XXX: should test if argument reference needs TDZ check */
+            } else {
+                if (is_put) {
+                    if (s->vars[var_idx].is_lexical) {
+                        if (op == OP_scope_put_var_init) {
+                            /* 'this' can only be initialized once */
+                            if (var_name == JS_ATOM_this)
+                                dbuf_putc(bc, OP_put_loc_check_init);
+                            else
+                                dbuf_putc(bc, OP_put_loc);
+                        } else {
+                            dbuf_putc(bc, OP_put_loc_check);
+                        }
+                    } else {
+                        dbuf_putc(bc, OP_put_loc);
+                    }
+                } else {
+                    if (s->vars[var_idx].is_lexical) {
+                        dbuf_putc(bc, OP_get_loc_check);
+                    } else {
+                        dbuf_putc(bc, OP_get_loc);
+                    }
+                }
+                dbuf_put_u16(bc, var_idx);
+            }
+            break;
+        case OP_scope_delete_var:
+            dbuf_putc(bc, OP_push_false);
+            break;
+        }
+        goto done;
+    }
+    /* check eval object */
+    if (s->var_object_idx >= 0 && !is_pseudo_var) {
+        dbuf_putc(bc, OP_get_loc);
+        dbuf_put_u16(bc, s->var_object_idx);
+        dbuf_putc(bc, get_with_scope_opcode(op));
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        label_done = new_label_fd(s, label_done);
+        dbuf_put_u32(bc, label_done);
+        dbuf_putc(bc, 0);
+        update_label(s, label_done, 1);
+        s->jump_size++;
+    }
+    /* check parent scopes */
+    for (fd = s; fd->parent;) {
+        scope_level = fd->parent_scope_level;
+        fd = fd->parent;
+        if (scope_level == 0) {
+            /* function is defined as part of the argument parsing: hide vars
+               from the function body.
+               XXX: variables created from argument destructuring might need
+               to be visible, should refine this method.
+             */
+            var_idx = find_arg(ctx, fd, var_name);
+            goto check_idx;
+        }
+        for (idx = fd->scopes[scope_level].first; idx >= 0;) {
+            vd = &fd->vars[idx];
+            if (vd->var_name == var_name) {
+                if (op == OP_scope_put_var || op == OP_scope_make_ref) {
+                    if (vd->is_const) {
+                        dbuf_putc(bc, OP_throw_var);
+                        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                        dbuf_putc(bc, JS_THROW_VAR_RO);
+                        goto done;
+                    }
+                    is_func_var = vd->is_func_var;
+                }
+                var_idx = idx;
+                break;
+            } else if (vd->var_name == JS_ATOM__with_ && !is_pseudo_var) {
+                vd->is_captured = 1;
+                idx = get_closure_var(ctx, s, fd, FALSE, idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL);
+                if (idx >= 0) {
+                    dbuf_putc(bc, OP_get_var_ref);
+                    dbuf_put_u16(bc, idx);
+                    dbuf_putc(bc, get_with_scope_opcode(op));
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                    label_done = new_label_fd(s, label_done);
+                    dbuf_put_u32(bc, label_done);
+                    dbuf_putc(bc, 1);
+                    update_label(s, label_done, 1);
+                    s->jump_size++;
+                }
+            }
+            idx = vd->scope_next;
+        }
+        if (var_idx >= 0)
+            break;
+
+        var_idx = find_var(ctx, fd, var_name);
+    check_idx:
+        if (var_idx >= 0) {
+            if (!(var_idx & ARGUMENT_VAR_OFFSET))
+                is_func_var = fd->vars[var_idx].is_func_var;
+            break;
+        }
+        if (is_pseudo_var) {
+            var_idx = resolve_pseudo_var(ctx, fd, var_name);
+            if (var_idx >= 0)
+                break;
+        }
+        if (var_name == JS_ATOM_arguments && fd->has_arguments_binding) {
+            var_idx = add_arguments_var(ctx, fd, var_name);
+            break;
+        }
+        if (fd->is_func_expr && fd->func_name == var_name) {
+            /* add a new variable with the function name */
+            var_idx = add_func_var(ctx, fd, var_name);
+            is_func_var = TRUE;
+            break;
+        }
+
+        /* check eval object */
+        if (fd->var_object_idx >= 0 && !is_pseudo_var) {
+            fd->vars[fd->var_object_idx].is_captured = 1;
+            idx = get_closure_var(ctx, s, fd, FALSE,
+                                  fd->var_object_idx, JS_ATOM__var_,
+                                  FALSE, FALSE, JS_VAR_NORMAL);
+            dbuf_putc(bc, OP_get_var_ref);
+            dbuf_put_u16(bc, idx);
+            dbuf_putc(bc, get_with_scope_opcode(op));
+            dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+            label_done = new_label_fd(s, label_done);
+            dbuf_put_u32(bc, label_done);
+            dbuf_putc(bc, 0);
+            update_label(s, label_done, 1);
+            s->jump_size++;
+        }
+
+        if (fd->is_eval)
+            break; /* it it necessarily the top level function */
+    }
+
+    /* check direct eval scope (in the closure of the eval function
+       which is necessarily at the top level) */
+    if (!fd)
+        fd = s;
+    if (var_idx < 0 && fd->is_eval) {
+        int idx1;
+        for (idx1 = 0; idx1 < fd->closure_var_count; idx1++) {
+            JSClosureVar *cv = &fd->closure_var[idx1];
+            if (var_name == cv->var_name) {
+                if (fd != s) {
+                    idx = get_closure_var2(ctx, s, fd,
+                                           FALSE,
+                                           cv->is_arg, idx1,
+                                           cv->var_name, cv->is_const,
+                                           cv->is_lexical, cv->var_kind);
+                } else {
+                    idx = idx1;
+                }
+                goto has_idx;
+            } else if ((cv->var_name == JS_ATOM__var_ ||
+                        cv->var_name == JS_ATOM__with_) && !is_pseudo_var) {
+                int is_with = (cv->var_name == JS_ATOM__with_);
+                if (fd != s) {
+                    idx = get_closure_var2(ctx, s, fd,
+                                           FALSE,
+                                           cv->is_arg, idx1,
+                                           cv->var_name, FALSE, FALSE,
+                                           JS_VAR_NORMAL);
+                } else {
+                    idx = idx1;
+                }
+                dbuf_putc(bc, OP_get_var_ref);
+                dbuf_put_u16(bc, idx);
+                dbuf_putc(bc, get_with_scope_opcode(op));
+                dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                label_done = new_label_fd(s, label_done);
+                dbuf_put_u32(bc, label_done);
+                dbuf_putc(bc, is_with);
+                update_label(s, label_done, 1);
+                s->jump_size++;
+            }
+        }
+    }
+
+    if (var_idx >= 0) {
+        /* find the corresponding closure variable */
+        if (var_idx & ARGUMENT_VAR_OFFSET) {
+            fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1;
+            idx = get_closure_var(ctx, s, fd,
+                                  TRUE, var_idx - ARGUMENT_VAR_OFFSET,
+                                  var_name, FALSE, FALSE, JS_VAR_NORMAL);
+        } else {
+            fd->vars[var_idx].is_captured = 1;
+            idx = get_closure_var(ctx, s, fd,
+                                  FALSE, var_idx,
+                                  var_name,
+                                  fd->vars[var_idx].is_const,
+                                  fd->vars[var_idx].is_lexical,
+                                  fd->vars[var_idx].var_kind);
+        }
+        if (idx >= 0) {
+        has_idx:
+            if ((op == OP_scope_put_var || op == OP_scope_make_ref) &&
+                s->closure_var[idx].is_const) {
+                dbuf_putc(bc, OP_throw_var);
+                dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                dbuf_putc(bc, JS_THROW_VAR_RO);
+                goto done;
+            }
+            switch (op) {
+            case OP_scope_make_ref:
+                if (is_func_var) {
+                    /* Create a dummy object reference for the func_var */
+                    dbuf_putc(bc, OP_object);
+                    dbuf_putc(bc, OP_get_var_ref);
+                    dbuf_put_u16(bc, idx);
+                    dbuf_putc(bc, OP_define_field);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                    dbuf_putc(bc, OP_push_atom_value);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                } else
+                if (label_done == -1 &&
+                    can_opt_put_ref_value(bc_buf, ls->pos)) {
+                    int get_op;
+                    if (s->closure_var[idx].is_lexical)
+                        get_op = OP_get_var_ref_check;
+                    else
+                        get_op = OP_get_var_ref;
+                    pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls,
+                                                       pos_next,
+                                                       get_op, idx);
+                } else {
+                    /* Create a dummy object with a named slot that is
+                       a reference to the closure variable */
+                    dbuf_putc(bc, OP_make_var_ref_ref);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+                    dbuf_put_u16(bc, idx);
+                }
+                break;
+            case OP_scope_get_ref:
+                /* XXX: should create a dummy object with a named slot that is
+                   a reference to the closure variable */
+                dbuf_putc(bc, OP_undefined);
+                /* fall thru */
+            case OP_scope_get_var_undef:
+            case OP_scope_get_var:
+            case OP_scope_put_var:
+            case OP_scope_put_var_init:
+                is_put = (op == OP_scope_put_var ||
+                          op == OP_scope_put_var_init);
+                if (is_put) {
+                    if (s->closure_var[idx].is_lexical) {
+                        if (op == OP_scope_put_var_init) {
+                            /* 'this' can only be initialized once */
+                            if (var_name == JS_ATOM_this)
+                                dbuf_putc(bc, OP_put_var_ref_check_init);
+                            else
+                                dbuf_putc(bc, OP_put_var_ref);
+                        } else {
+                            dbuf_putc(bc, OP_put_var_ref_check);
+                        }
+                    } else {
+                        dbuf_putc(bc, OP_put_var_ref);
+                    }
+                } else {
+                    if (s->closure_var[idx].is_lexical) {
+                        dbuf_putc(bc, OP_get_var_ref_check);
+                    } else {
+                        dbuf_putc(bc, OP_get_var_ref);
+                    }
+                }
+                dbuf_put_u16(bc, idx);
+                break;
+            case OP_scope_delete_var:
+                dbuf_putc(bc, OP_push_false);
+                break;
+            }
+            goto done;
+        }
+    }
+
+    /* global variable access */
+
+    switch (op) {
+    case OP_scope_make_ref:
+        if (label_done == -1 && can_opt_put_global_ref_value(bc_buf, ls->pos)) {
+            pos_next = optimize_scope_make_global_ref(ctx, s, bc, bc_buf, ls,
+                                                      pos_next, var_name);
+        } else {
+            dbuf_putc(bc, OP_make_var_ref);
+            dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        }
+        break;
+    case OP_scope_get_ref:
+        /* XXX: should create a dummy object with a named slot that is
+           a reference to the global variable */
+        dbuf_putc(bc, OP_undefined);
+        dbuf_putc(bc, OP_get_var);
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        break;
+    case OP_scope_get_var_undef:
+    case OP_scope_get_var:
+    case OP_scope_put_var:
+        dbuf_putc(bc, OP_get_var_undef + (op - OP_scope_get_var_undef));
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        break;
+    case OP_scope_put_var_init:
+        dbuf_putc(bc, OP_put_var_init);
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        break;
+    case OP_scope_delete_var:
+        dbuf_putc(bc, OP_delete_var);
+        dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+        break;
+    }
+done:
+    if (label_done >= 0) {
+        dbuf_putc(bc, OP_label);
+        dbuf_put_u32(bc, label_done);
+        s->label_slots[label_done].pos2 = bc->size;
+    }
+    return pos_next;
+}
+
+/* search in all scopes */
+static int find_private_class_field_all(JSContext *ctx, JSFunctionDef *fd,
+                                        JSAtom name, int scope_level)
+{
+    int idx;
+
+    idx = fd->scopes[scope_level].first;
+    while (idx >= 0) {
+        if (fd->vars[idx].var_name == name)
+            return idx;
+        idx = fd->vars[idx].scope_next;
+    }
+    return -1;
+}
+
+static void get_loc_or_ref(DynBuf *bc, BOOL is_ref, int idx)
+{
+    /* Note: the private field can be uninitialized, so the _check is
+       necessary */
+    if (is_ref) 
+        dbuf_putc(bc, OP_get_var_ref_check);
+    else
+        dbuf_putc(bc, OP_get_loc_check);
+    dbuf_put_u16(bc, idx);
+}
+
+static int resolve_scope_private_field1(JSContext *ctx,
+                                        BOOL *pis_ref, int *pvar_kind,
+                                        JSFunctionDef *s,
+                                        JSAtom var_name, int scope_level)
+{
+    int idx, var_kind;
+    JSFunctionDef *fd;
+    BOOL is_ref;
+    
+    fd = s;
+    is_ref = FALSE;
+    for(;;) {
+        idx = find_private_class_field_all(ctx, fd, var_name, scope_level);
+        if (idx >= 0) {
+            var_kind = fd->vars[idx].var_kind;
+            if (is_ref) {
+                idx = get_closure_var(ctx, s, fd, FALSE, idx, var_name,
+                                      TRUE, TRUE, JS_VAR_NORMAL);
+                if (idx < 0)
+                    return -1;
+            }
+            break;
+        }
+        scope_level = fd->parent_scope_level;
+        if (!fd->parent) {
+            char buf[ATOM_GET_STR_BUF_SIZE];
+
+            if (fd->is_eval) {
+                /* closure of the eval function (top level) */
+                for (idx = 0; idx < fd->closure_var_count; idx++) {
+                    JSClosureVar *cv = &fd->closure_var[idx];
+                    if (cv->var_name == var_name) {
+                        var_kind = cv->var_kind;
+                        is_ref = TRUE;
+                        if (fd != s) {
+                            idx = get_closure_var2(ctx, s, fd,
+                                                   FALSE,
+                                                   cv->is_arg, idx,
+                                                   cv->var_name, cv->is_const,
+                                                   cv->is_lexical,
+                                                   cv->var_kind);
+                            if (idx < 0)
+                                return -1;
+                        }
+                        goto done;
+                    }
+                }
+            }
+            /* XXX: no line number info */
+            JS_ThrowSyntaxError(ctx, "undefined private field %s",
+                                JS_AtomGetStr(ctx, buf, sizeof(buf), var_name));
+            return -1;
+        } else {
+            fd = fd->parent;
+        }
+        is_ref = TRUE;
+    }
+ done:
+    *pis_ref = is_ref;
+    *pvar_kind = var_kind;
+    return idx;
+}
+
+/* return 0 if OK or -1 if the private field could not be resolved */
+static int resolve_scope_private_field(JSContext *ctx, JSFunctionDef *s,
+                                       JSAtom var_name, int scope_level, int op,
+                                       DynBuf *bc)
+{
+    int idx, var_kind;
+    BOOL is_ref;
+
+    idx = resolve_scope_private_field1(ctx, &is_ref, &var_kind, s,
+                                       var_name, scope_level);
+    if (idx < 0)
+        return -1;
+    assert(var_kind != JS_VAR_NORMAL);
+    switch (op) {
+    case OP_scope_get_private_field:
+    case OP_scope_get_private_field2:
+        switch(var_kind) {
+        case JS_VAR_PRIVATE_FIELD:
+            if (op == OP_scope_get_private_field2)
+                dbuf_putc(bc, OP_dup);
+            get_loc_or_ref(bc, is_ref, idx);
+            dbuf_putc(bc, OP_get_private_field);
+            break;
+        case JS_VAR_PRIVATE_METHOD:
+            get_loc_or_ref(bc, is_ref, idx);
+            dbuf_putc(bc, OP_check_brand);
+            if (op != OP_scope_get_private_field2)
+                dbuf_putc(bc, OP_nip);
+            break;
+        case JS_VAR_PRIVATE_GETTER:
+        case JS_VAR_PRIVATE_GETTER_SETTER:
+            if (op == OP_scope_get_private_field2)
+                dbuf_putc(bc, OP_dup);
+            get_loc_or_ref(bc, is_ref, idx);
+            dbuf_putc(bc, OP_check_brand);
+            dbuf_putc(bc, OP_call_method);
+            dbuf_put_u16(bc, 0);
+            break;
+        case JS_VAR_PRIVATE_SETTER:
+            /* XXX: add clearer error message */
+            dbuf_putc(bc, OP_throw_var);
+            dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+            dbuf_putc(bc, JS_THROW_VAR_RO);
+            break;
+        default:
+            abort();
+        }
+        break;
+    case OP_scope_put_private_field:
+        switch(var_kind) {
+        case JS_VAR_PRIVATE_FIELD:
+            get_loc_or_ref(bc, is_ref, idx);
+            dbuf_putc(bc, OP_put_private_field);
+            break;
+        case JS_VAR_PRIVATE_METHOD:
+        case JS_VAR_PRIVATE_GETTER:
+            /* XXX: add clearer error message */
+            dbuf_putc(bc, OP_throw_var);
+            dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
+            dbuf_putc(bc, JS_THROW_VAR_RO);
+            break;
+        case JS_VAR_PRIVATE_SETTER:
+        case JS_VAR_PRIVATE_GETTER_SETTER:
+            {
+                JSAtom setter_name = get_private_setter_name(ctx, var_name);
+                if (setter_name == JS_ATOM_NULL)
+                    return -1;
+                idx = resolve_scope_private_field1(ctx, &is_ref,
+                                                   &var_kind, s,
+                                                   setter_name, scope_level);
+                JS_FreeAtom(ctx, setter_name);
+                if (idx < 0)
+                    return -1;
+                assert(var_kind == JS_VAR_PRIVATE_SETTER);
+                get_loc_or_ref(bc, is_ref, idx);
+                dbuf_putc(bc, OP_swap);
+                /* obj func value */
+                dbuf_putc(bc, OP_rot3r);
+                /* value obj func */
+                dbuf_putc(bc, OP_check_brand);
+                dbuf_putc(bc, OP_rot3l);
+                /* obj func value */
+                dbuf_putc(bc, OP_call_method);
+                dbuf_put_u16(bc, 1);
+            }
+            break;
+        default:
+            abort();
+        }
+        break;
+    default:
+        abort();
+    }
+    return 0;
+}
+
+static void mark_eval_captured_variables(JSContext *ctx, JSFunctionDef *s,
+                                         int scope_level)
+{
+    int idx;
+    JSVarDef *vd;
+
+    for (idx = s->scopes[scope_level].first; idx >= 0;) {
+        vd = &s->vars[idx];
+        vd->is_captured = 1;
+        idx = vd->scope_next;
+    }
+}
+
+static void add_eval_variables(JSContext *ctx, JSFunctionDef *s)
+{
+    JSFunctionDef *fd;
+    JSVarDef *vd;
+    int i, scope_level, scope_idx;
+    BOOL has_arguments_binding, has_this_binding;
+
+    /* in non strict mode, variables are created in the caller's
+       environment object */
+    if (!s->is_eval && !(s->js_mode & JS_MODE_STRICT)) {
+        s->var_object_idx = add_var(ctx, s, JS_ATOM__var_);
+    }
+
+    /* eval can potentially use 'arguments' so we must define it */
+    has_this_binding = s->has_this_binding;
+    if (has_this_binding) {
+        if (s->this_var_idx < 0)
+            s->this_var_idx = add_var_this(ctx, s);
+        if (s->new_target_var_idx < 0)
+            s->new_target_var_idx = add_var(ctx, s, JS_ATOM_new_target);
+        if (s->is_derived_class_constructor && s->this_active_func_var_idx < 0)
+            s->this_active_func_var_idx = add_var(ctx, s, JS_ATOM_this_active_func);
+        if (s->has_home_object && s->home_object_var_idx < 0)
+            s->home_object_var_idx = add_var(ctx, s, JS_ATOM_home_object);
+    }
+    has_arguments_binding = s->has_arguments_binding;
+    if (has_arguments_binding)
+        add_arguments_var(ctx, s, JS_ATOM_arguments);
+    if (s->is_func_expr && s->func_name != JS_ATOM_NULL)
+        add_func_var(ctx, s, s->func_name);
+
+    /* eval can use all the variables of the enclosing functions, so
+       they must be all put in the closure. The closure variables are
+       ordered by scope. It works only because no closure are created
+       before. */
+    assert(s->is_eval || s->closure_var_count == 0);
+
+    /* XXX: inefficient, but eval performance is less critical */
+    fd = s;
+    for(;;) {
+        scope_level = fd->parent_scope_level;
+        fd = fd->parent;
+        if (!fd)
+            break;
+        scope_idx = fd->scopes[scope_level].first;
+        /* add 'this' if it was not previously added */
+        if (!has_this_binding && fd->has_this_binding) {
+            if (fd->this_var_idx < 0)
+                fd->this_var_idx = add_var_this(ctx, fd);
+            if (fd->new_target_var_idx < 0)
+                fd->new_target_var_idx = add_var(ctx, fd, JS_ATOM_new_target);
+            if (fd->is_derived_class_constructor && fd->this_active_func_var_idx < 0)
+                fd->this_active_func_var_idx = add_var(ctx, fd, JS_ATOM_this_active_func);
+            if (fd->has_home_object && fd->home_object_var_idx < 0)
+                fd->home_object_var_idx = add_var(ctx, fd, JS_ATOM_home_object);
+            has_this_binding = TRUE;
+        }
+        /* add 'arguments' if it was not previously added */
+        if (!has_arguments_binding && fd->has_arguments_binding) {
+            add_arguments_var(ctx, fd, JS_ATOM_arguments);
+            has_arguments_binding = TRUE;
+        }
+        /* add function name */
+        if (fd->is_func_expr && fd->func_name != JS_ATOM_NULL)
+            add_func_var(ctx, fd, fd->func_name);
+
+        /* add lexical variables */
+        while (scope_idx >= 0) {
+            vd = &fd->vars[scope_idx];
+            vd->is_captured = 1;
+            get_closure_var(ctx, s, fd, FALSE, scope_idx,
+                            vd->var_name, vd->is_const, vd->is_lexical, vd->var_kind);
+            scope_idx = vd->scope_next;
+        }
+        /* add unscoped variables */
+        for(i = 0; i < fd->arg_count; i++) {
+            vd = &fd->args[i];
+            if (vd->var_name != JS_ATOM_NULL) {
+                get_closure_var(ctx, s, fd,
+                                TRUE, i, vd->var_name, FALSE, FALSE,
+                                JS_VAR_NORMAL);
+            }
+        }
+        for(i = 0; i < fd->var_count; i++) {
+            vd = &fd->vars[i];
+            /* do not close top level last result */
+            if (vd->scope_level == 0 &&
+                vd->var_name != JS_ATOM__ret_ &&
+                vd->var_name != JS_ATOM_NULL) {
+                get_closure_var(ctx, s, fd,
+                                FALSE, i, vd->var_name, FALSE, FALSE,
+                                JS_VAR_NORMAL);
+            }
+        }
+        if (fd->is_eval) {
+            int idx;
+            /* add direct eval variables (we are necessarily at the
+               top level) */
+            for (idx = 0; idx < fd->closure_var_count; idx++) {
+                JSClosureVar *cv = &fd->closure_var[idx];
+                get_closure_var2(ctx, s, fd,
+                                 FALSE, cv->is_arg,
+                                 idx, cv->var_name, cv->is_const,
+                                 cv->is_lexical, cv->var_kind);
+            }
+        }
+    }
+}
+
+/* for direct eval compilation: add references to the variables of the
+   calling function */
+static __exception int add_closure_variables(JSContext *ctx, JSFunctionDef *s,
+                                             JSFunctionBytecode *b, int scope_idx)
+{
+    int i, count;
+    JSVarDef *vd;
+
+    count = b->arg_count + b->var_count + b->closure_var_count;
+    s->closure_var = NULL;
+    s->closure_var_count = 0;
+    s->closure_var_size = count;
+    if (count == 0)
+        return 0;
+    s->closure_var = js_malloc(ctx, sizeof(s->closure_var[0]) * count);
+    if (!s->closure_var)
+        return -1;
+    /* Add lexical variables in scope at the point of evaluation */
+    for (i = scope_idx; i >= 0;) {
+        vd = &b->vardefs[b->arg_count + i];
+        if (vd->scope_level > 0) {
+            JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
+            cv->is_local = TRUE;
+            cv->is_arg = FALSE;
+            cv->is_const = vd->is_const;
+            cv->is_lexical = vd->is_lexical;
+            cv->var_kind = vd->var_kind;
+            cv->var_idx = i;
+            cv->var_name = JS_DupAtom(ctx, vd->var_name);
+        }
+        i = vd->scope_next;
+    }
+    /* Add argument variables */
+    for(i = 0; i < b->arg_count; i++) {
+        JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
+        vd = &b->vardefs[i];
+        cv->is_local = TRUE;
+        cv->is_arg = TRUE;
+        cv->is_const = FALSE;
+        cv->is_lexical = FALSE;
+        cv->var_kind = JS_VAR_NORMAL;
+        cv->var_idx = i;
+        cv->var_name = JS_DupAtom(ctx, vd->var_name);
+    }
+    /* Add local non lexical variables */
+    for(i = 0; i < b->var_count; i++) {
+        vd = &b->vardefs[b->arg_count + i];
+        if (vd->scope_level == 0 && vd->var_name != JS_ATOM__ret_) {
+            JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
+            cv->is_local = TRUE;
+            cv->is_arg = FALSE;
+            cv->is_const = FALSE;
+            cv->is_lexical = FALSE;
+            cv->var_kind = JS_VAR_NORMAL;
+            cv->var_idx = i;
+            cv->var_name = JS_DupAtom(ctx, vd->var_name);
+        }
+    }
+    for(i = 0; i < b->closure_var_count; i++) {
+        JSClosureVar *cv0 = &b->closure_var[i];
+        JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
+        cv->is_local = FALSE;
+        cv->is_arg = cv0->is_arg;
+        cv->is_const = cv0->is_const;
+        cv->is_lexical = cv0->is_lexical;
+        cv->var_kind = cv0->var_kind;
+        cv->var_idx = i;
+        cv->var_name = JS_DupAtom(ctx, cv0->var_name);
+    }
+    return 0;
+}
+
+typedef struct CodeContext {
+    const uint8_t *bc_buf; /* code buffer */
+    int bc_len;   /* length of the code buffer */
+    int pos;      /* position past the matched code pattern */
+    int line_num; /* last visited OP_line_num parameter or -1 */
+    int op;
+    int idx;
+    int label;
+    int val;
+    JSAtom atom;
+} CodeContext;
+
+#define M2(op1, op2)            ((op1) | ((op2) << 8))
+#define M3(op1, op2, op3)       ((op1) | ((op2) << 8) | ((op3) << 16))
+#define M4(op1, op2, op3, op4)  ((op1) | ((op2) << 8) | ((op3) << 16) | ((op4) << 24))
+
+static BOOL code_match(CodeContext *s, int pos, ...)
+{
+    const uint8_t *tab = s->bc_buf;
+    int op, len, op1, line_num, pos_next;
+    va_list ap;
+    BOOL ret = FALSE;
+
+    line_num = -1;
+    va_start(ap, pos);
+
+    for(;;) {
+        op1 = va_arg(ap, int);
+        if (op1 == -1) {
+            s->pos = pos;
+            s->line_num = line_num;
+            ret = TRUE;
+            break;
+        }
+        for (;;) {
+            if (pos >= s->bc_len)
+                goto done;
+            op = tab[pos];
+            len = opcode_info[op].size;
+            pos_next = pos + len;
+            if (pos_next > s->bc_len)
+                goto done;
+            if (op == OP_line_num) {
+                line_num = get_u32(tab + pos + 1);
+                pos = pos_next;
+            } else {
+                break;
+            }
+        }
+        if (op != op1) {
+            if (op1 == (uint8_t)op1 || !op)
+                break;
+            if (op != (uint8_t)op1
+            &&  op != (uint8_t)(op1 >> 8)
+            &&  op != (uint8_t)(op1 >> 16)
+            &&  op != (uint8_t)(op1 >> 24)) {
+                break;
+            }
+            s->op = op;
+        }
+
+        pos++;
+        switch(opcode_info[op].fmt) {
+        case OP_FMT_loc8:
+        case OP_FMT_u8:
+            {
+                int idx = tab[pos];
+                int arg = va_arg(ap, int);
+                if (arg == -1) {
+                    s->idx = idx;
+                } else {
+                    if (arg != idx)
+                        goto done;
+                }
+                break;
+            }
+        case OP_FMT_u16:
+        case OP_FMT_npop:
+        case OP_FMT_loc:
+        case OP_FMT_arg:
+        case OP_FMT_var_ref:
+            {
+                int idx = get_u16(tab + pos);
+                int arg = va_arg(ap, int);
+                if (arg == -1) {
+                    s->idx = idx;
+                } else {
+                    if (arg != idx)
+                        goto done;
+                }
+                break;
+            }
+        case OP_FMT_i32:
+        case OP_FMT_u32:
+        case OP_FMT_label:
+        case OP_FMT_const:
+            {
+                s->label = get_u32(tab + pos);
+                break;
+            }
+        case OP_FMT_label_u16:
+            {
+                s->label = get_u32(tab + pos);
+                s->val = get_u16(tab + pos + 4);
+                break;
+            }
+        case OP_FMT_atom:
+            {
+                s->atom = get_u32(tab + pos);
+                break;
+            }
+        case OP_FMT_atom_u8:
+            {
+                s->atom = get_u32(tab + pos);
+                s->val = get_u8(tab + pos + 4);
+                break;
+            }
+        case OP_FMT_atom_u16:
+            {
+                s->atom = get_u32(tab + pos);
+                s->val = get_u16(tab + pos + 4);
+                break;
+            }
+        case OP_FMT_atom_label_u8:
+            {
+                s->atom = get_u32(tab + pos);
+                s->label = get_u32(tab + pos + 4);
+                s->val = get_u8(tab + pos + 8);
+                break;
+            }
+        default:
+            break;
+        }
+        pos = pos_next;
+    }
+ done:
+    va_end(ap);
+    return ret;
+}
+
+static void instantiate_hoisted_definitions(JSContext *ctx, JSFunctionDef *s, DynBuf *bc)
+{
+    int i, idx, var_idx;
+
+    /* add the hoisted functions and variables */
+    for(i = 0; i < s->hoisted_def_count; i++) {
+        JSHoistedDef *hf = &s->hoisted_def[i];
+        int has_closure = 0;
+        BOOL force_init = hf->force_init;
+        if (s->is_global_var && hf->var_name != JS_ATOM_NULL) {
+            /* we are in an eval, so the closure contains all the
+               enclosing variables */
+            /* If the outer function has a variable environment,
+               create a property for the variable there */
+            for(idx = 0; idx < s->closure_var_count; idx++) {
+                JSClosureVar *cv = &s->closure_var[idx];
+                if (cv->var_name == hf->var_name) {
+                    has_closure = 2;
+                    force_init = FALSE;
+                    break;
+                }
+                if (cv->var_name == JS_ATOM__var_) {
+                    dbuf_putc(bc, OP_get_var_ref);
+                    dbuf_put_u16(bc, idx);
+                    has_closure = 1;
+                    force_init = TRUE;
+                    break;
+                }
+            }
+            if (!has_closure) {
+                int flags;
+
+                flags = 0;
+                if (s->eval_type != JS_EVAL_TYPE_GLOBAL)
+                    flags |= JS_PROP_CONFIGURABLE;
+                if (hf->cpool_idx >= 0 && !hf->is_lexical) {
+                    /* global function definitions need a specific handling */
+                    dbuf_putc(bc, OP_fclosure);
+                    dbuf_put_u32(bc, hf->cpool_idx);
+
+                    dbuf_putc(bc, OP_define_func);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
+                    dbuf_putc(bc, flags);
+
+                    goto done_hoisted_def;
+                } else {
+                    if (hf->is_lexical) {
+                        flags |= DEFINE_GLOBAL_LEX_VAR;
+                        if (!hf->is_const)
+                            flags |= JS_PROP_WRITABLE;
+                    }
+                    dbuf_putc(bc, OP_define_var);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
+                    dbuf_putc(bc, flags);
+                }
+            }
+        }
+        if (hf->cpool_idx >= 0 || force_init) {
+            if (hf->cpool_idx >= 0) {
+                dbuf_putc(bc, OP_fclosure);
+                dbuf_put_u32(bc, hf->cpool_idx);
+                if (hf->var_name == JS_ATOM__default_) {
+                    /* set default export function name */
+                    dbuf_putc(bc, OP_set_name);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, JS_ATOM_default));
+                }
+            } else {
+                dbuf_putc(bc, OP_undefined);
+            }
+            if (s->is_global_var) {
+                if (has_closure == 2) {
+                    dbuf_putc(bc, OP_put_var_ref);
+                    dbuf_put_u16(bc, idx);
+                } else if (has_closure == 1) {
+                    dbuf_putc(bc, OP_define_field);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
+                    dbuf_putc(bc, OP_drop);
+                } else {
+                    /* XXX: Check if variable is writable and enumerable */
+                    dbuf_putc(bc, OP_put_var);
+                    dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
+                }
+            } else {
+                var_idx = hf->var_idx;
+                if (var_idx & ARGUMENT_VAR_OFFSET) {
+                    dbuf_putc(bc, OP_put_arg);
+                    dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET);
+                } else {
+                    dbuf_putc(bc, OP_put_loc);
+                    dbuf_put_u16(bc, var_idx);
+                }
+            }
+        }
+    done_hoisted_def:
+        JS_FreeAtom(ctx, hf->var_name);
+    }
+    js_free(ctx, s->hoisted_def);
+    s->hoisted_def = NULL;
+    s->hoisted_def_count = 0;
+    s->hoisted_def_size = 0;
+}
+
+static int skip_dead_code(JSFunctionDef *s, const uint8_t *bc_buf, int bc_len,
+                          int pos, int *linep)
+{
+    int op, len, label;
+
+    for (; pos < bc_len; pos += len) {
+        op = bc_buf[pos];
+        len = opcode_info[op].size;
+        if (op == OP_line_num) {
+            *linep = get_u32(bc_buf + pos + 1);
+        } else
+        if (op == OP_label) {
+            label = get_u32(bc_buf + pos + 1);
+            if (update_label(s, label, 0) > 0)
+                break;
+#if 0
+            if (s->label_slots[label].first_reloc) {
+                printf("line %d: unreferenced label %d:%d has relocations\n",
+                       *linep, label, s->label_slots[label].pos2);
+            }
+#endif
+            assert(s->label_slots[label].first_reloc == NULL);
+        } else {
+            /* XXX: output a warning for unreachable code? */
+            JSAtom atom;
+            switch(opcode_info[op].fmt) {
+            case OP_FMT_label:
+            case OP_FMT_label_u16:
+                label = get_u32(bc_buf + pos + 1);
+                update_label(s, label, -1);
+                break;
+            case OP_FMT_atom_label_u8:
+            case OP_FMT_atom_label_u16:
+                label = get_u32(bc_buf + pos + 5);
+                update_label(s, label, -1);
+                /* fall thru */
+            case OP_FMT_atom:
+            case OP_FMT_atom_u8:
+            case OP_FMT_atom_u16:
+                atom = get_u32(bc_buf + pos + 1);
+                JS_FreeAtom(s->ctx, atom);
+                break;
+            default:
+                break;
+            }
+        }
+    }
+    return pos;
+}
+
+static int get_label_pos(JSFunctionDef *s, int label)
+{
+    int i, pos;
+    for (i = 0; i < 20; i++) {
+        pos = s->label_slots[label].pos;
+        for (;;) {
+            switch (s->byte_code.buf[pos]) {
+            case OP_line_num:
+            case OP_label:
+                pos += 5;
+                continue;
+            case OP_goto:
+                label = get_u32(s->byte_code.buf + pos + 1);
+                break;
+            default:
+                return pos;
+            }
+            break;
+        }
+    }
+    return pos;
+}
+
+/* convert global variable accesses to local variables or closure
+   variables when necessary */
+static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
+{
+    int pos, pos_next, bc_len, op, len, i, idx, arg_valid, line_num;
+    uint8_t *bc_buf;
+    JSAtom var_name;
+    DynBuf bc_out;
+    CodeContext cc;
+    int scope;
+
+    cc.bc_buf = bc_buf = s->byte_code.buf;
+    cc.bc_len = bc_len = s->byte_code.size;
+    js_dbuf_init(ctx, &bc_out);
+
+    /* first pass for runtime checks (must be done before the
+       variables are created) */
+    if (s->is_global_var) {
+        for(i = 0; i < s->hoisted_def_count; i++) {
+            JSHoistedDef *hf = &s->hoisted_def[i];
+            int flags;
+
+            if (hf->var_name != JS_ATOM_NULL) {
+                /* check if global variable (XXX: simplify) */
+                for(idx = 0; idx < s->closure_var_count; idx++) {
+                    JSClosureVar *cv = &s->closure_var[idx];
+                    if (cv->var_name == hf->var_name) {
+                        if (s->eval_type == JS_EVAL_TYPE_DIRECT &&
+                            cv->is_lexical) {
+                            /* Check if a lexical variable is
+                               redefined as 'var'. XXX: Could abort
+                               compilation here, but for consistency
+                               with the other checks, we delay the
+                               error generation. */
+                            dbuf_putc(&bc_out, OP_throw_var);
+                            dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name));
+                            dbuf_putc(&bc_out, JS_THROW_VAR_REDECL);
+                        }
+                        goto next;
+                    }
+                    if (cv->var_name == JS_ATOM__var_)
+                        goto next;
+                }
+
+                dbuf_putc(&bc_out, OP_check_define_var);
+                dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name));
+                flags = 0;
+                if (hf->is_lexical)
+                    flags |= DEFINE_GLOBAL_LEX_VAR;
+                if (hf->cpool_idx >= 0)
+                    flags |= DEFINE_GLOBAL_FUNC_VAR;
+                dbuf_putc(&bc_out, flags);
+            }
+        next: ;
+        }
+    }
+
+    arg_valid = 0;
+    line_num = 0; /* avoid warning */
+    for (pos = 0; pos < bc_len; pos = pos_next) {
+        op = bc_buf[pos];
+        len = opcode_info[op].size;
+        pos_next = pos + len;
+        switch(op) {
+        case OP_line_num:
+            line_num = get_u32(bc_buf + pos + 1);
+            s->line_number_size++;
+            goto no_change;
+
+        case OP_set_arg_valid_upto:
+            arg_valid = get_u16(bc_buf + pos + 1);
+            break;
+        case OP_eval: /* convert scope index to adjusted variable index */
+            {
+                int call_argc = get_u16(bc_buf + pos + 1);
+                scope = get_u16(bc_buf + pos + 1 + 2);
+                mark_eval_captured_variables(ctx, s, scope);
+                dbuf_putc(&bc_out, op);
+                dbuf_put_u16(&bc_out, call_argc);
+                dbuf_put_u16(&bc_out, s->scopes[scope].first + 1);
+            }
+            break;
+        case OP_apply_eval: /* convert scope index to adjusted variable index */
+            scope = get_u16(bc_buf + pos + 1);
+            mark_eval_captured_variables(ctx, s, scope);
+            dbuf_putc(&bc_out, op);
+            dbuf_put_u16(&bc_out, s->scopes[scope].first + 1);
+            break;
+        case OP_scope_get_var_undef:
+        case OP_scope_get_var:
+        case OP_scope_put_var:
+        case OP_scope_delete_var:
+        case OP_scope_get_ref:
+        case OP_scope_put_var_init:
+            var_name = get_u32(bc_buf + pos + 1);
+            scope = get_u16(bc_buf + pos + 5);
+            pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out,
+                                         NULL, NULL, pos_next, arg_valid);
+            JS_FreeAtom(ctx, var_name);
+            break;
+        case OP_scope_make_ref:
+            {
+                int label;
+                LabelSlot *ls;
+                var_name = get_u32(bc_buf + pos + 1);
+                label = get_u32(bc_buf + pos + 5);
+                scope = get_u16(bc_buf + pos + 9);
+                ls = &s->label_slots[label];
+                ls->ref_count--;  /* always remove label reference */
+                pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out,
+                                             bc_buf, ls, pos_next, arg_valid);
+                JS_FreeAtom(ctx, var_name);
+            }
+            break;
+        case OP_scope_get_private_field:
+        case OP_scope_get_private_field2:
+        case OP_scope_put_private_field:
+            {
+                int ret;
+                var_name = get_u32(bc_buf + pos + 1);
+                scope = get_u16(bc_buf + pos + 5);
+                ret = resolve_scope_private_field(ctx, s, var_name, scope, op, &bc_out);
+                if (ret < 0)
+                    goto fail;
+                JS_FreeAtom(ctx, var_name);
+            }
+            break;
+        case OP_gosub:
+            s->jump_size++;
+            if (OPTIMIZE) {
+                /* remove calls to empty finalizers  */
+                int label;
+                LabelSlot *ls;
+
+                label = get_u32(bc_buf + pos + 1);
+                assert(label >= 0 && label < s->label_count);
+                ls = &s->label_slots[label];
+                if (code_match(&cc, ls->pos, OP_ret, -1)) {
+                    ls->ref_count--;
+                    break;
+                }
+            }
+            goto no_change;
+        case OP_drop:
+            if (0) {
+                /* remove drops before return_undef */
+                /* do not perform this optimization in pass2 because
+                   it breaks patterns recognised in resolve_labels */
+                int pos1 = pos_next;
+                int line1 = line_num;
+                while (code_match(&cc, pos1, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line1 = cc.line_num;
+                    pos1 = cc.pos;
+                }
+                if (code_match(&cc, pos1, OP_return_undef, -1)) {
+                    pos_next = pos1;
+                    if (line1 != -1 && line1 != line_num) {
+                        line_num = line1;
+                        s->line_number_size++;
+                        dbuf_putc(&bc_out, OP_line_num);
+                        dbuf_put_u32(&bc_out, line_num);
+                    }
+                    break;
+                }
+            }
+            goto no_change;
+        case OP_insert3:
+            if (OPTIMIZE) {
+                /* Transformation: insert3 put_array_el|put_ref_value drop -> put_array_el|put_ref_value */
+                if (code_match(&cc, pos_next, M2(OP_put_array_el, OP_put_ref_value), OP_drop, -1)) {
+                    dbuf_putc(&bc_out, cc.op);
+                    pos_next = cc.pos;
+                    if (cc.line_num != -1 && cc.line_num != line_num) {
+                        line_num = cc.line_num;
+                        s->line_number_size++;
+                        dbuf_putc(&bc_out, OP_line_num);
+                        dbuf_put_u32(&bc_out, line_num);
+                    }
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_goto:
+            s->jump_size++;
+            /* fall thru */
+        case OP_tail_call:
+        case OP_tail_call_method:
+        case OP_return:
+        case OP_return_undef:
+        case OP_throw:
+        case OP_throw_var:
+        case OP_ret:
+            if (OPTIMIZE) {
+                /* remove dead code */
+                int line = -1;
+                dbuf_put(&bc_out, bc_buf + pos, len);
+                pos = skip_dead_code(s, bc_buf, bc_len, pos + len, &line);
+                pos_next = pos;
+                if (pos < bc_len && line >= 0 && line_num != line) {
+                    line_num = line;
+                    s->line_number_size++;
+                    dbuf_putc(&bc_out, OP_line_num);
+                    dbuf_put_u32(&bc_out, line_num);
+                }
+                break;
+            }
+            goto no_change;
+
+        case OP_label:
+            {
+                int label;
+                LabelSlot *ls;
+
+                label = get_u32(bc_buf + pos + 1);
+                assert(label >= 0 && label < s->label_count);
+                ls = &s->label_slots[label];
+                ls->pos2 = bc_out.size + opcode_info[op].size;
+            }
+            goto no_change;
+
+        case OP_enter_scope:
+            {
+                int scope_idx, scope = get_u16(bc_buf + pos + 1);
+
+                if (scope == 1) {
+                    instantiate_hoisted_definitions(ctx, s, &bc_out);
+                }
+
+                for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) {
+                    JSVarDef *vd = &s->vars[scope_idx];
+                    if (vd->scope_level == scope) {
+                        if (vd->var_kind == JS_VAR_FUNCTION_DECL ||
+                            vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) {
+                            /* Initialize lexical variable upon entering scope */
+                            dbuf_putc(&bc_out, OP_fclosure);
+                            dbuf_put_u32(&bc_out, vd->func_pool_or_scope_idx);
+                            dbuf_putc(&bc_out, OP_put_loc);
+                            dbuf_put_u16(&bc_out, scope_idx);
+                        } else {
+                            /* XXX: should check if variable can be used
+                               before initialization */
+                            dbuf_putc(&bc_out, OP_set_loc_uninitialized);
+                            dbuf_put_u16(&bc_out, scope_idx);
+                        }
+                        scope_idx = vd->scope_next;
+                    } else {
+                        break;
+                    }
+                }
+            }
+            break;
+
+        case OP_leave_scope:
+            {
+                int scope_idx, scope = get_u16(bc_buf + pos + 1);
+
+                for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) {
+                    JSVarDef *vd = &s->vars[scope_idx];
+                    if (vd->scope_level == scope) {
+                        if (vd->is_captured) {
+                            dbuf_putc(&bc_out, OP_close_loc);
+                            dbuf_put_u16(&bc_out, scope_idx);
+                        }
+                        scope_idx = vd->scope_next;
+                    } else {
+                        break;
+                    }
+                }
+            }
+            break;
+
+        case OP_set_name:
+            {
+                /* remove dummy set_name opcodes */
+                JSAtom name = get_u32(bc_buf + pos + 1);
+                if (name == JS_ATOM_NULL)
+                    break;
+            }
+            goto no_change;
+
+        case OP_if_false:
+        case OP_if_true:
+        case OP_catch:
+            s->jump_size++;
+            goto no_change;
+
+        case OP_dup:
+            if (OPTIMIZE) {
+                /* Transformation: dup if_false(l1) drop, l1: if_false(l2) -> if_false(l2) */
+                /* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2) */
+                if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), OP_drop, -1)) {
+                    int lab0, lab1, op1, pos1, line1, pos2;
+                    lab0 = lab1 = cc.label;
+                    assert(lab1 >= 0 && lab1 < s->label_count);
+                    op1 = cc.op;
+                    pos1 = cc.pos;
+                    line1 = cc.line_num;
+                    while (code_match(&cc, (pos2 = get_label_pos(s, lab1)), OP_dup, op1, OP_drop, -1)) {
+                        lab1 = cc.label;
+                    }
+                    if (code_match(&cc, pos2, op1, -1)) {
+                        s->jump_size++;
+                        update_label(s, lab0, -1);
+                        update_label(s, cc.label, +1);
+                        dbuf_putc(&bc_out, op1);
+                        dbuf_put_u32(&bc_out, cc.label);
+                        pos_next = pos1;
+                        if (line1 != -1 && line1 != line_num) {
+                            line_num = line1;
+                            s->line_number_size++;
+                            dbuf_putc(&bc_out, OP_line_num);
+                            dbuf_put_u32(&bc_out, line_num);
+                        }
+                        break;
+                    }
+                }
+            }
+            goto no_change;
+
+        case OP_nop:
+            /* remove erased code */
+            break;
+        case OP_set_class_name:
+            /* only used during parsing */
+            break;
+            
+        default:
+        no_change:
+            dbuf_put(&bc_out, bc_buf + pos, len);
+            break;
+        }
+    }
+
+    /* set the new byte code */
+    dbuf_free(&s->byte_code);
+    s->byte_code = bc_out;
+    if (dbuf_error(&s->byte_code)) {
+        JS_ThrowOutOfMemory(ctx);
+        return -1;
+    }
+    return 0;
+ fail:
+    /* continue the copy to keep the atom refcounts consistent */
+    /* XXX: find a better solution ? */
+    for (; pos < bc_len; pos = pos_next) {
+        op = bc_buf[pos];
+        len = opcode_info[op].size;
+        pos_next = pos + len;
+        dbuf_put(&bc_out, bc_buf + pos, len);
+    }
+    dbuf_free(&s->byte_code);
+    s->byte_code = bc_out;
+    return -1;
+}
+
+/* the pc2line table gives a line number for each PC value */
+static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, int line_num)
+{
+    if (s->line_number_slots != NULL
+    &&  s->line_number_count < s->line_number_size
+    &&  pc >= s->line_number_last_pc
+    &&  line_num != s->line_number_last) {
+        s->line_number_slots[s->line_number_count].pc = pc;
+        s->line_number_slots[s->line_number_count].line_num = line_num;
+        s->line_number_count++;
+        s->line_number_last_pc = pc;
+        s->line_number_last = line_num;
+    }
+}
+
+static void compute_pc2line_info(JSFunctionDef *s)
+{
+    if (!(s->js_mode & JS_MODE_STRIP) && s->line_number_slots) {
+        int last_line_num = s->line_num;
+        uint32_t last_pc = 0;
+        int i;
+
+        js_dbuf_init(s->ctx, &s->pc2line);
+        for (i = 0; i < s->line_number_count; i++) {
+            uint32_t pc = s->line_number_slots[i].pc;
+            int line_num = s->line_number_slots[i].line_num;
+            int diff_pc, diff_line;
+
+            if (line_num < 0)
+                continue;
+
+            diff_pc = pc - last_pc;
+            diff_line = line_num - last_line_num;
+            if (diff_line == 0 || diff_pc < 0)
+                continue;
+
+            if (diff_line >= PC2LINE_BASE &&
+                diff_line < PC2LINE_BASE + PC2LINE_RANGE &&
+                diff_pc <= PC2LINE_DIFF_PC_MAX) {
+                dbuf_putc(&s->pc2line, (diff_line - PC2LINE_BASE) +
+                          diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST);
+            } else {
+                /* longer encoding */
+                dbuf_putc(&s->pc2line, 0);
+                dbuf_put_leb128(&s->pc2line, diff_pc);
+                dbuf_put_sleb128(&s->pc2line, diff_line);
+            }
+            last_pc = pc;
+            last_line_num = line_num;
+        }
+    }
+}
+
+static RelocEntry *add_reloc(JSContext *ctx, LabelSlot *ls, uint32_t addr, int size)
+{
+    RelocEntry *re;
+    re = js_malloc(ctx, sizeof(*re));
+    if (!re)
+        return NULL;
+    re->addr = addr;
+    re->size = size;
+    re->next = ls->first_reloc;
+    ls->first_reloc = re;
+    return re;
+}
+
+static BOOL code_has_label(CodeContext *s, int pos, int label)
+{
+    while (pos < s->bc_len) {
+        int op = s->bc_buf[pos];
+        if (op == OP_line_num) {
+            pos += 5;
+            continue;
+        }
+        if (op == OP_label) {
+            int lab = get_u32(s->bc_buf + pos + 1);
+            if (lab == label)
+                return TRUE;
+            pos += 5;
+            continue;
+        }
+        if (op == OP_goto) {
+            int lab = get_u32(s->bc_buf + pos + 1);
+            if (lab == label)
+                return TRUE;
+        }
+        break;
+    }
+    return FALSE;
+}
+
+/* return the target label, following the OP_goto jumps
+   the first opcode at destination is stored in *pop
+ */
+static int find_jump_target(JSFunctionDef *s, int label, int *pop, int *pline)
+{
+    int i, pos, op;
+
+    update_label(s, label, -1);
+    for (i = 0; i < 10; i++) {
+        assert(label >= 0 && label < s->label_count);
+        pos = s->label_slots[label].pos2;
+        for (;;) {
+            switch(op = s->byte_code.buf[pos]) {
+            case OP_line_num:
+                if (pline)
+                    *pline = get_u32(s->byte_code.buf + pos + 1);
+                /* fall thru */
+            case OP_label:
+                pos += opcode_info[op].size;
+                continue;
+            case OP_goto:
+                label = get_u32(s->byte_code.buf + pos + 1);
+                break;
+            case OP_drop:
+                /* ignore drop opcodes if followed by OP_return_undef */
+                while (s->byte_code.buf[++pos] == OP_drop)
+                    continue;
+                if (s->byte_code.buf[pos] == OP_return_undef)
+                    op = OP_return_undef;
+                /* fall thru */
+            default:
+                goto done;
+            }
+            break;
+        }
+    }
+    /* cycle detected, could issue a warning */
+ done:
+    *pop = op;
+    update_label(s, label, +1);
+    return label;
+}
+
+static void push_short_int(DynBuf *bc_out, int val)
+{
+#if SHORT_OPCODES
+    if (val >= -1 && val <= 7) {
+        dbuf_putc(bc_out, OP_push_0 + val);
+        return;
+    }
+    if (val == (int8_t)val) {
+        dbuf_putc(bc_out, OP_push_i8);
+        dbuf_putc(bc_out, val);
+        return;
+    }
+    if (val == (int16_t)val) {
+        dbuf_putc(bc_out, OP_push_i16);
+        dbuf_put_u16(bc_out, val);
+        return;
+    }
+#endif
+    dbuf_putc(bc_out, OP_push_i32);
+    dbuf_put_u32(bc_out, val);
+}
+
+static void put_short_code(DynBuf *bc_out, int op, int idx)
+{
+#if SHORT_OPCODES
+    if (idx < 4) {
+        switch (op) {
+        case OP_get_loc:
+            dbuf_putc(bc_out, OP_get_loc0 + idx);
+            return;
+        case OP_put_loc:
+            dbuf_putc(bc_out, OP_put_loc0 + idx);
+            return;
+        case OP_set_loc:
+            dbuf_putc(bc_out, OP_set_loc0 + idx);
+            return;
+        case OP_get_arg:
+            dbuf_putc(bc_out, OP_get_arg0 + idx);
+            return;
+        case OP_put_arg:
+            dbuf_putc(bc_out, OP_put_arg0 + idx);
+            return;
+        case OP_set_arg:
+            dbuf_putc(bc_out, OP_set_arg0 + idx);
+            return;
+        case OP_get_var_ref:
+            dbuf_putc(bc_out, OP_get_var_ref0 + idx);
+            return;
+        case OP_put_var_ref:
+            dbuf_putc(bc_out, OP_put_var_ref0 + idx);
+            return;
+        case OP_set_var_ref:
+            dbuf_putc(bc_out, OP_set_var_ref0 + idx);
+            return;
+        case OP_call:
+            dbuf_putc(bc_out, OP_call0 + idx);
+            return;
+        }
+    }
+    if (idx < 256) {
+        switch (op) {
+        case OP_get_loc:
+            dbuf_putc(bc_out, OP_get_loc8);
+            dbuf_putc(bc_out, idx);
+            return;
+        case OP_put_loc:
+            dbuf_putc(bc_out, OP_put_loc8);
+            dbuf_putc(bc_out, idx);
+            return;
+        case OP_set_loc:
+            dbuf_putc(bc_out, OP_set_loc8);
+            dbuf_putc(bc_out, idx);
+            return;
+        }
+    }
+#endif
+    dbuf_putc(bc_out, op);
+    dbuf_put_u16(bc_out, idx);
+}
+
+/* peephole optimizations and resolve goto/labels */
+static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s)
+{
+    int pos, pos_next, bc_len, op, op1, len, i, line_num;
+    const uint8_t *bc_buf;
+    DynBuf bc_out;
+    LabelSlot *label_slots, *ls;
+    RelocEntry *re, *re_next;
+    CodeContext cc;
+    int label;
+#if SHORT_OPCODES
+    JumpSlot *jp;
+#endif
+
+    label_slots = s->label_slots;
+
+    line_num = s->line_num;
+
+    cc.bc_buf = bc_buf = s->byte_code.buf;
+    cc.bc_len = bc_len = s->byte_code.size;
+    js_dbuf_init(ctx, &bc_out);
+
+#if SHORT_OPCODES
+    if (s->jump_size) {
+        s->jump_slots = js_mallocz(s->ctx, sizeof(*s->jump_slots) * s->jump_size);
+        if (s->jump_slots == NULL)
+            return -1;
+    }
+#endif
+    /* XXX: Should skip this phase if not generating SHORT_OPCODES */
+    if (s->line_number_size && !(s->js_mode & JS_MODE_STRIP)) {
+        s->line_number_slots = js_mallocz(s->ctx, sizeof(*s->line_number_slots) * s->line_number_size);
+        if (s->line_number_slots == NULL)
+            return -1;
+        s->line_number_last = s->line_num;
+        s->line_number_last_pc = 0;
+    }
+
+    /* initialize the 'home_object' variable if needed */
+    if (s->home_object_var_idx >= 0) {
+        dbuf_putc(&bc_out, OP_special_object);
+        dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_HOME_OBJECT);
+        put_short_code(&bc_out, OP_put_loc, s->home_object_var_idx);
+    }
+    /* initialize the 'this.active_func' variable if needed */
+    if (s->this_active_func_var_idx >= 0) {
+        dbuf_putc(&bc_out, OP_special_object);
+        dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC);
+        put_short_code(&bc_out, OP_put_loc, s->this_active_func_var_idx);
+    }
+    /* initialize the 'new.target' variable if needed */
+    if (s->new_target_var_idx >= 0) {
+        dbuf_putc(&bc_out, OP_special_object);
+        dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_NEW_TARGET);
+        put_short_code(&bc_out, OP_put_loc, s->new_target_var_idx);
+    }
+    /* initialize the 'this' variable if needed. In a derived class
+       constructor, this is initially uninitialized. */
+    if (s->this_var_idx >= 0) {
+        if (s->is_derived_class_constructor) {
+            dbuf_putc(&bc_out, OP_set_loc_uninitialized);
+            dbuf_put_u16(&bc_out, s->this_var_idx);
+        } else {
+            dbuf_putc(&bc_out, OP_push_this);
+            put_short_code(&bc_out, OP_put_loc, s->this_var_idx);
+        }
+    }
+    /* initialize the 'arguments' variable if needed */
+    if (s->arguments_var_idx >= 0) {
+        if ((s->js_mode & JS_MODE_STRICT) || !s->has_simple_parameter_list) {
+            dbuf_putc(&bc_out, OP_special_object);
+            dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_ARGUMENTS);
+        } else {
+            dbuf_putc(&bc_out, OP_special_object);
+            dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS);
+        }
+        put_short_code(&bc_out, OP_put_loc, s->arguments_var_idx);
+    }
+    /* initialize a reference to the current function if needed */
+    if (s->func_var_idx >= 0) {
+        dbuf_putc(&bc_out, OP_special_object);
+        dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC);
+        put_short_code(&bc_out, OP_put_loc, s->func_var_idx);
+    }
+    /* initialize the variable environment object if needed */
+    if (s->var_object_idx >= 0) {
+        dbuf_putc(&bc_out, OP_special_object);
+        dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT);
+        put_short_code(&bc_out, OP_put_loc, s->var_object_idx);
+    }
+
+    for (pos = 0; pos < bc_len; pos = pos_next) {
+        int val;
+        op = bc_buf[pos];
+        len = opcode_info[op].size;
+        pos_next = pos + len;
+        switch(op) {
+        case OP_close_var_object:
+            {
+                if (s->var_object_idx >= 0) {
+                    /* close the var object and create a new one */
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_close_loc);
+                    dbuf_put_u16(&bc_out, s->var_object_idx);
+                    dbuf_putc(&bc_out, OP_special_object);
+                    dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT);
+                    put_short_code(&bc_out, OP_put_loc, s->var_object_idx);
+                }
+            }
+            break;
+
+        case OP_line_num:
+            /* line number info (for debug). We put it in a separate
+               compressed table to reduce memory usage and get better
+               performance */
+            line_num = get_u32(bc_buf + pos + 1);
+            break;
+
+        case OP_label:
+            {
+                label = get_u32(bc_buf + pos + 1);
+                assert(label >= 0 && label < s->label_count);
+                ls = &label_slots[label];
+                assert(ls->addr == -1);
+                ls->addr = bc_out.size;
+                /* resolve the relocation entries */
+                for(re = ls->first_reloc; re != NULL; re = re_next) {
+                    int diff = ls->addr - re->addr;
+                    re_next = re->next;
+                    switch (re->size) {
+                    case 4:
+                        put_u32(bc_out.buf + re->addr, diff);
+                        break;
+                    case 2:
+                        assert(diff == (int16_t)diff);
+                        put_u16(bc_out.buf + re->addr, diff);
+                        break;
+                    case 1:
+                        assert(diff == (int8_t)diff);
+                        put_u8(bc_out.buf + re->addr, diff);
+                        break;
+                    }
+                    js_free(ctx, re);
+                }
+                ls->first_reloc = NULL;
+            }
+            break;
+
+        case OP_call:
+        case OP_call_method:
+            {
+                /* detect and transform tail calls */
+                int argc;
+                argc = get_u16(bc_buf + pos + 1);
+                if (code_match(&cc, pos_next, OP_return, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    put_short_code(&bc_out, op + 1, argc);
+                    pos_next = skip_dead_code(s, bc_buf, bc_len, cc.pos, &line_num);
+                    break;
+                }
+                add_pc2line_info(s, bc_out.size, line_num);
+                put_short_code(&bc_out, op, argc);
+                break;
+            }
+            goto no_change;
+
+        case OP_return:
+        case OP_return_undef:
+        case OP_return_async:
+        case OP_throw:
+        case OP_throw_var:
+            pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &line_num);
+            goto no_change;
+
+        case OP_goto:
+            label = get_u32(bc_buf + pos + 1);
+        has_goto:
+            if (OPTIMIZE) {
+                int line1 = -1;
+                /* Use custom matcher because multiple labels can follow */
+                label = find_jump_target(s, label, &op1, &line1);
+                if (code_has_label(&cc, pos_next, label)) {
+                    /* jump to next instruction: remove jump */
+                    update_label(s, label, -1);
+                    break;
+                }
+                if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) {
+                    /* jump to return/throw: remove jump, append return/throw */
+                    /* updating the line number obfuscates assembly listing */
+                    //if (line1 >= 0) line_num = line1;
+                    update_label(s, label, -1);
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, op1);
+                    pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &line_num);
+                    break;
+                }
+                /* XXX: should duplicate single instructions followed by goto or return */
+                /* For example, can match one of these followed by return:
+                   push_i32 / push_const / push_atom_value / get_var /
+                   undefined / null / push_false / push_true / get_ref_value /
+                   get_loc / get_arg / get_var_ref
+                 */
+            }
+            goto has_label;
+
+        case OP_gosub:
+            label = get_u32(bc_buf + pos + 1);
+            if (0 && OPTIMIZE) {
+                label = find_jump_target(s, label, &op1, NULL);
+                if (op1 == OP_ret) {
+                    update_label(s, label, -1);
+                    /* empty finally clause: remove gosub */
+                    break;
+                }
+            }
+            goto has_label;
+
+        case OP_catch:
+            label = get_u32(bc_buf + pos + 1);
+            goto has_label;
+
+        case OP_if_true:
+        case OP_if_false:
+            label = get_u32(bc_buf + pos + 1);
+            if (OPTIMIZE) {
+                label = find_jump_target(s, label, &op1, NULL);
+                /* transform if_false/if_true(l1) label(l1) -> drop label(l1) */
+                if (code_has_label(&cc, pos_next, label)) {
+                    update_label(s, label, -1);
+                    dbuf_putc(&bc_out, OP_drop);
+                    break;
+                }
+                /* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1) */
+                if (code_match(&cc, pos_next, OP_goto, -1)) {
+                    int pos1 = cc.pos;
+                    int line1 = cc.line_num;
+                    if (code_has_label(&cc, pos1, label)) {
+                        if (line1 >= 0) line_num = line1;
+                        pos_next = pos1;
+                        update_label(s, label, -1);
+                        label = cc.label;
+                        op ^= OP_if_true ^ OP_if_false;
+                    }
+                }
+            }
+        has_label:
+            add_pc2line_info(s, bc_out.size, line_num);
+            if (op == OP_goto) {
+                pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &line_num);
+            }
+            assert(label >= 0 && label < s->label_count);
+            ls = &label_slots[label];
+#if SHORT_OPCODES
+            jp = &s->jump_slots[s->jump_count++];
+            jp->op = op;
+            jp->size = 4;
+            jp->pos = bc_out.size + 1;
+            jp->label = label;
+
+            if (ls->addr == -1) {
+                int diff = ls->pos2 - pos - 1;
+                if (diff < 128 && (op == OP_if_false || op == OP_if_true || op == OP_goto)) {
+                    jp->size = 1;
+                    jp->op = OP_if_false8 + (op - OP_if_false);
+                    dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false));
+                    dbuf_putc(&bc_out, 0);
+                    if (!add_reloc(ctx, ls, bc_out.size - 1, 1))
+                        goto fail;
+                    break;
+                }
+                if (diff < 32768 && op == OP_goto) {
+                    jp->size = 2;
+                    jp->op = OP_goto16;
+                    dbuf_putc(&bc_out, OP_goto16);
+                    dbuf_put_u16(&bc_out, 0);
+                    if (!add_reloc(ctx, ls, bc_out.size - 2, 2))
+                        goto fail;
+                    break;
+                }
+            } else {
+                int diff = ls->addr - bc_out.size - 1;
+                if (diff == (int8_t)diff && (op == OP_if_false || op == OP_if_true || op == OP_goto)) {
+                    jp->size = 1;
+                    jp->op = OP_if_false8 + (op - OP_if_false);
+                    dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false));
+                    dbuf_putc(&bc_out, diff);
+                    break;
+                }
+                if (diff == (int16_t)diff && op == OP_goto) {
+                    jp->size = 2;
+                    jp->op = OP_goto16;
+                    dbuf_putc(&bc_out, OP_goto16);
+                    dbuf_put_u16(&bc_out, diff);
+                    break;
+                }
+            }
+#endif
+            dbuf_putc(&bc_out, op);
+            dbuf_put_u32(&bc_out, ls->addr - bc_out.size);
+            if (ls->addr == -1) {
+                /* unresolved yet: create a new relocation entry */
+                if (!add_reloc(ctx, ls, bc_out.size - 4, 4))
+                    goto fail;
+            }
+            break;
+        case OP_with_get_var:
+        case OP_with_put_var:
+        case OP_with_delete_var:
+        case OP_with_make_ref:
+        case OP_with_get_ref:
+        case OP_with_get_ref_undef:
+            {
+                JSAtom atom;
+                int is_with;
+
+                atom = get_u32(bc_buf + pos + 1);
+                label = get_u32(bc_buf + pos + 5);
+                is_with = bc_buf[pos + 9];
+                if (OPTIMIZE) {
+                    label = find_jump_target(s, label, &op1, NULL);
+                }
+                assert(label >= 0 && label < s->label_count);
+                ls = &label_slots[label];
+                add_pc2line_info(s, bc_out.size, line_num);
+#if SHORT_OPCODES
+                jp = &s->jump_slots[s->jump_count++];
+                jp->op = op;
+                jp->size = 4;
+                jp->pos = bc_out.size + 5;
+                jp->label = label;
+#endif
+                dbuf_putc(&bc_out, op);
+                dbuf_put_u32(&bc_out, atom);
+                dbuf_put_u32(&bc_out, ls->addr - bc_out.size);
+                if (ls->addr == -1) {
+                    /* unresolved yet: create a new relocation entry */
+                    if (!add_reloc(ctx, ls, bc_out.size - 4, 4))
+                        goto fail;
+                }
+                dbuf_putc(&bc_out, is_with);
+            }
+            break;
+
+        case OP_drop:
+            if (OPTIMIZE) {
+                /* remove useless drops before return */
+                if (code_match(&cc, pos_next, OP_return_undef, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_null:
+#if SHORT_OPCODES
+            if (OPTIMIZE) {
+                /* transform null strict_eq into is_null */
+                if (code_match(&cc, pos_next, OP_strict_eq, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_is_null);
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transform null strict_neq if_false/if_true -> is_null if_true/if_false */
+                if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_is_null);
+                    pos_next = cc.pos;
+                    label = cc.label;
+                    op = cc.op ^ OP_if_false ^ OP_if_true;
+                    goto has_label;
+                }
+            }
+#endif
+            /* fall thru */
+        case OP_push_false:
+        case OP_push_true:
+            if (OPTIMIZE) {
+                val = (op == OP_push_true);
+                if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) {
+                has_constant_test:
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    if (val == cc.op - OP_if_false) {
+                        /* transform null if_false(l1) -> goto l1 */
+                        /* transform false if_false(l1) -> goto l1 */
+                        /* transform true if_true(l1) -> goto l1 */
+                        pos_next = cc.pos;
+                        op = OP_goto;
+                        label = cc.label;
+                        goto has_goto;
+                    } else {
+                        /* transform null if_true(l1) -> nop */
+                        /* transform false if_true(l1) -> nop */
+                        /* transform true if_false(l1) -> nop */
+                        pos_next = cc.pos;
+                        update_label(s, cc.label, -1);
+                        break;
+                    }
+                }
+            }
+            goto no_change;
+
+        case OP_push_i32:
+            if (OPTIMIZE) {
+                /* transform i32(val) neg -> i32(-val) */
+                val = get_i32(bc_buf + pos + 1);
+                if ((val != INT32_MIN && val != 0)
+                &&  code_match(&cc, pos_next, OP_neg, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    if (code_match(&cc, cc.pos, OP_drop, -1)) {
+                        if (cc.line_num >= 0) line_num = cc.line_num;
+                    } else {
+                        add_pc2line_info(s, bc_out.size, line_num);
+                        push_short_int(&bc_out, -val);
+                    }
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* remove push/drop pairs generated by the parser */
+                if (code_match(&cc, pos_next, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */
+                if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) {
+                    val = (val != 0);
+                    goto has_constant_test;
+                }
+                add_pc2line_info(s, bc_out.size, line_num);
+                push_short_int(&bc_out, val);
+                break;
+            }
+            goto no_change;
+
+#if SHORT_OPCODES
+        case OP_push_const:
+        case OP_fclosure:
+            if (OPTIMIZE) {
+                int idx = get_u32(bc_buf + pos + 1);
+                if (idx < 256) {
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_push_const8 + op - OP_push_const);
+                    dbuf_putc(&bc_out, idx);
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_get_field:
+            if (OPTIMIZE) {
+                JSAtom atom = get_u32(bc_buf + pos + 1);
+                if (atom == JS_ATOM_length) {
+                    JS_FreeAtom(ctx, atom);
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_get_length);
+                    break;
+                }
+            }
+            goto no_change;
+#endif
+        case OP_push_atom_value:
+            if (OPTIMIZE) {
+                JSAtom atom = get_u32(bc_buf + pos + 1);
+                /* remove push/drop pairs generated by the parser */
+                if (code_match(&cc, pos_next, OP_drop, -1)) {
+                    JS_FreeAtom(ctx, atom);
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    pos_next = cc.pos;
+                    break;
+                }
+#if SHORT_OPCODES
+                if (atom == JS_ATOM_empty_string) {
+                    JS_FreeAtom(ctx, atom);
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_push_empty_string);
+                    break;
+                }
+#endif
+            }
+            goto no_change;
+
+        case OP_to_propkey:
+        case OP_to_propkey2:
+            if (OPTIMIZE) {
+                /* remove redundant to_propkey/to_propkey2 opcodes when storing simple data */
+                if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_put_array_el, -1)
+                ||  code_match(&cc, pos_next, M3(OP_push_i32, OP_push_const, OP_push_atom_value), OP_put_array_el, -1)
+                ||  code_match(&cc, pos_next, M4(OP_undefined, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) {
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_undefined:
+            if (OPTIMIZE) {
+                /* remove push/drop pairs generated by the parser */
+                if (code_match(&cc, pos_next, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transform undefined return -> return_undefined */
+                if (code_match(&cc, pos_next, OP_return, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_return_undef);
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transform undefined if_true(l1)/if_false(l1) -> nop/goto(l1) */
+                if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) {
+                    val = 0;
+                    goto has_constant_test;
+                }
+#if SHORT_OPCODES
+                /* transform undefined strict_eq -> is_undefined */
+                if (code_match(&cc, pos_next, OP_strict_eq, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_is_undefined);
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transform undefined strict_neq if_false/if_true -> is_undefined if_true/if_false */
+                if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_is_undefined);
+                    pos_next = cc.pos;
+                    label = cc.label;
+                    op = cc.op ^ OP_if_false ^ OP_if_true;
+                    goto has_label;
+                }
+#endif
+            }
+            goto no_change;
+
+        case OP_insert2:
+            if (OPTIMIZE) {
+                /* Transformation:
+                   insert2 put_field(a) drop -> put_field(a)
+                   insert2 put_var_strict(a) drop -> put_var_strict(a)
+                */
+                if (code_match(&cc, pos_next, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, cc.op);
+                    dbuf_put_u32(&bc_out, cc.atom);
+                    pos_next = cc.pos;
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_dup:
+            if (OPTIMIZE) {
+                /* Transformation: dup put_x(n) drop -> put_x(n) */
+                int op1, line2 = -1;
+                /* Transformation: dup put_x(n) -> set_x(n) */
+                if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    op1 = cc.op + 1;  /* put_x -> set_x */
+                    pos_next = cc.pos;
+                    if (code_match(&cc, cc.pos, OP_drop, -1)) {
+                        if (cc.line_num >= 0) line_num = cc.line_num;
+                        op1 -= 1; /* set_x drop -> put_x */
+                        pos_next = cc.pos;
+                        if (code_match(&cc, cc.pos, op1 - 1, cc.idx, -1)) {
+                            line2 = cc.line_num; /* delay line number update */
+                            op1 += 1;   /* put_x(n) get_x(n) -> set_x(n) */
+                            pos_next = cc.pos;
+                        }
+                    }
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    put_short_code(&bc_out, op1, cc.idx);
+                    if (line2 >= 0) line_num = line2;
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_get_loc:
+            if (OPTIMIZE) {
+                /* transformation:
+                   get_loc(n) post_dec put_loc(n) drop -> dec_loc(n)
+                   get_loc(n) post_inc put_loc(n) drop -> inc_loc(n)
+                   get_loc(n) dec dup put_loc(n) drop -> dec_loc(n)
+                   get_loc(n) inc dup put_loc(n) drop -> inc_loc(n)
+                 */
+                int idx;
+                idx = get_u16(bc_buf + pos + 1);
+                if (idx >= 256)
+                    goto no_change;
+                if (code_match(&cc, pos_next, M2(OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1) ||
+                    code_match(&cc, pos_next, M2(OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc);
+                    dbuf_putc(&bc_out, idx);
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transformation:
+                   get_loc(n) push_atom_value(x) add dup put_loc(n) drop -> push_atom_value(x) add_loc(n)
+                 */
+                if (code_match(&cc, pos_next, OP_push_atom_value, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+#if SHORT_OPCODES
+                    if (cc.atom == JS_ATOM_empty_string) {
+                        JS_FreeAtom(ctx, cc.atom);
+                        dbuf_putc(&bc_out, OP_push_empty_string);
+                    } else
+#endif
+                    {
+                        dbuf_putc(&bc_out, OP_push_atom_value);
+                        dbuf_put_u32(&bc_out, cc.atom);
+                    }
+                    dbuf_putc(&bc_out, OP_add_loc);
+                    dbuf_putc(&bc_out, idx);
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transformation:
+                   get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x) add_loc(n)
+                 */
+                if (code_match(&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    push_short_int(&bc_out, cc.label);
+                    dbuf_putc(&bc_out, OP_add_loc);
+                    dbuf_putc(&bc_out, idx);
+                    pos_next = cc.pos;
+                    break;
+                }
+                /* transformation: XXX: also do these:
+                   get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n)
+                   get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n)
+                   get_loc(n) get_var_ref(x) add dup put_loc(n) drop -> get_var_ref(x) add_loc(n)
+                 */
+                if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    put_short_code(&bc_out, cc.op, cc.idx);
+                    dbuf_putc(&bc_out, OP_add_loc);
+                    dbuf_putc(&bc_out, idx);
+                    pos_next = cc.pos;
+                    break;
+                }
+                add_pc2line_info(s, bc_out.size, line_num);
+                put_short_code(&bc_out, op, idx);
+                break;
+            }
+            goto no_change;
+#if SHORT_OPCODES
+        case OP_get_arg:
+        case OP_get_var_ref:
+            if (OPTIMIZE) {
+                int idx;
+                idx = get_u16(bc_buf + pos + 1);
+                add_pc2line_info(s, bc_out.size, line_num);
+                put_short_code(&bc_out, op, idx);
+                break;
+            }
+            goto no_change;
+#endif
+        case OP_put_loc:
+        case OP_put_arg:
+        case OP_put_var_ref:
+            if (OPTIMIZE) {
+                /* transformation: put_x(n) get_x(n) -> set_x(n) */
+                int idx;
+                idx = get_u16(bc_buf + pos + 1);
+                if (code_match(&cc, pos_next, op - 1, idx, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    put_short_code(&bc_out, op + 1, idx);
+                    pos_next = cc.pos;
+                    break;
+                }
+                add_pc2line_info(s, bc_out.size, line_num);
+                put_short_code(&bc_out, op, idx);
+                break;
+            }
+            goto no_change;
+
+        case OP_post_inc:
+        case OP_post_dec:
+            if (OPTIMIZE) {
+                /* transformation:
+                   post_inc put_x drop -> inc put_x
+                   post_inc perm3 put_field drop -> inc put_field
+                   post_inc perm3 put_var_strict drop -> inc put_var_strict
+                   post_inc perm4 put_array_el drop -> inc put_array_el
+                 */
+                int op1, idx;
+                if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    op1 = cc.op;
+                    idx = cc.idx;
+                    pos_next = cc.pos;
+                    if (code_match(&cc, cc.pos, op1 - 1, idx, -1)) {
+                        if (cc.line_num >= 0) line_num = cc.line_num;
+                        op1 += 1;   /* put_x(n) get_x(n) -> set_x(n) */
+                        pos_next = cc.pos;
+                    }
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec));
+                    put_short_code(&bc_out, op1, idx);
+                    break;
+                }
+                if (code_match(&cc, pos_next, OP_perm3, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec));
+                    dbuf_putc(&bc_out, cc.op);
+                    dbuf_put_u32(&bc_out, cc.atom);
+                    pos_next = cc.pos;
+                    break;
+                }
+                if (code_match(&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    add_pc2line_info(s, bc_out.size, line_num);
+                    dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec));
+                    dbuf_putc(&bc_out, OP_put_array_el);
+                    pos_next = cc.pos;
+                    break;
+                }
+            }
+            goto no_change;
+
+        case OP_typeof:
+            if (OPTIMIZE) {
+                /* simplify typeof tests */
+                if (code_match(&cc, pos_next, OP_push_atom_value, M4(OP_strict_eq, OP_strict_neq, OP_eq, OP_neq), -1)) {
+                    if (cc.line_num >= 0) line_num = cc.line_num;
+                    int op1 = (cc.op == OP_strict_eq || cc.op == OP_eq) ? OP_strict_eq : OP_strict_neq;
+#if SHORT_OPCODES
+                    int op2 = -1;
+                    switch (cc.atom) {
+                    case JS_ATOM_undefined:
+                        op2 = OP_is_undefined;
+                        break;
+                    case JS_ATOM_function:
+                        op2 = OP_is_function;
+                        break;
+                    }
+                    if (op2 >= 0) {
+                        /* transform typeof(s) == "<type>" into is_<type> */
+                        if (op1 == OP_strict_eq) {
+                            add_pc2line_info(s, bc_out.size, line_num);
+                            dbuf_putc(&bc_out, op2);
+                            JS_FreeAtom(ctx, cc.atom);
+                            pos_next = cc.pos;
+                            break;
+                        }
+                        if (op1 == OP_strict_neq && code_match(&cc, cc.pos, OP_if_false, -1)) {
+                            /* transform typeof(s) != "<type>" if_false into is_<type> if_true */
+                            if (cc.line_num >= 0) line_num = cc.line_num;
+                            add_pc2line_info(s, bc_out.size, line_num);
+                            dbuf_putc(&bc_out, op2);
+                            JS_FreeAtom(ctx, cc.atom);
+                            pos_next = cc.pos;
+                            label = cc.label;
+                            op = OP_if_true;
+                            goto has_label;
+                        }
+                    }
+#endif
+                    if (cc.atom == JS_ATOM_undefined) {
+                        /* transform typeof(s) == "undefined" into s === void 0 */
+                        add_pc2line_info(s, bc_out.size, line_num);
+                        dbuf_putc(&bc_out, OP_undefined);
+                        dbuf_putc(&bc_out, op1);
+                        JS_FreeAtom(ctx, cc.atom);
+                        pos_next = cc.pos;
+                        break;
+                    }
+                }
+            }
+            goto no_change;
+
+        default:
+        no_change:
+            add_pc2line_info(s, bc_out.size, line_num);
+            dbuf_put(&bc_out, bc_buf + pos, len);
+            break;
+        }
+    }
+
+    /* check that there were no missing labels */
+    for(i = 0; i < s->label_count; i++) {
+        assert(label_slots[i].first_reloc == NULL);
+    }
+#if SHORT_OPCODES
+    if (OPTIMIZE) {
+        /* more jump optimizations */
+        int patch_offsets = 0;
+        for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) {
+            LabelSlot *ls;
+            JumpSlot *jp1;
+            int j, pos, diff, delta;
+
+            delta = 3;
+            switch (op = jp->op) {
+            case OP_goto16:
+                delta = 1;
+                /* fall thru */
+            case OP_if_false:
+            case OP_if_true:
+            case OP_goto:
+                pos = jp->pos;
+                diff = s->label_slots[jp->label].addr - pos;
+                if (diff >= -128 && diff <= 127 + delta) {
+                    //put_u8(bc_out.buf + pos, diff);
+                    jp->size = 1;
+                    if (op == OP_goto16) {
+                        bc_out.buf[pos - 1] = jp->op = OP_goto8;
+                    } else {
+                        bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false);
+                    }
+                    goto shrink;
+                } else
+                if (diff == (int16_t)diff && op == OP_goto) {
+                    //put_u16(bc_out.buf + pos, diff);
+                    jp->size = 2;
+                    delta = 2;
+                    bc_out.buf[pos - 1] = jp->op = OP_goto16;
+                shrink:
+                    /* XXX: should reduce complexity, using 2 finger copy scheme */
+                    memmove(bc_out.buf + pos + jp->size, bc_out.buf + pos + jp->size + delta,
+                            bc_out.size - pos - jp->size - delta);
+                    bc_out.size -= delta;
+                    patch_offsets++;
+                    for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) {
+                        if (ls->addr > pos)
+                            ls->addr -= delta;
+                    }
+                    for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) {
+                        if (jp1->pos > pos)
+                            jp1->pos -= delta;
+                    }
+                    for (j = 0; j < s->line_number_count; j++) {
+                        if (s->line_number_slots[j].pc > pos)
+                            s->line_number_slots[j].pc -= delta;
+                    }
+                    continue;
+                }
+                break;
+            }
+        }
+        if (patch_offsets) {
+            JumpSlot *jp1;
+            int j;
+            for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) {
+                int diff1 = s->label_slots[jp1->label].addr - jp1->pos;
+                switch (jp1->size) {
+                case 1:
+                    put_u8(bc_out.buf + jp1->pos, diff1);
+                    break;
+                case 2:
+                    put_u16(bc_out.buf + jp1->pos, diff1);
+                    break;
+                case 4:
+                    put_u32(bc_out.buf + jp1->pos, diff1);
+                    break;
+                }
+            }
+        }
+    }
+    js_free(ctx, s->jump_slots);
+    s->jump_slots = NULL;
+#endif
+    js_free(ctx, s->label_slots);
+    s->label_slots = NULL;
+    /* XXX: should delay until copying to runtime bytecode function */
+    compute_pc2line_info(s);
+    js_free(ctx, s->line_number_slots);
+    s->line_number_slots = NULL;
+    /* set the new byte code */
+    dbuf_free(&s->byte_code);
+    s->byte_code = bc_out;
+    s->use_short_opcodes = TRUE;
+    if (dbuf_error(&s->byte_code)) {
+        JS_ThrowOutOfMemory(ctx);
+        return -1;
+    }
+    return 0;
+ fail:
+    /* XXX: not safe */
+    dbuf_free(&bc_out);
+    return -1;
+}
+
+/* compute the maximum stack size needed by the function */
+
+typedef struct StackSizeState {
+    int stack_len_max;
+    uint16_t *stack_level_tab;
+} StackSizeState;
+
+static __exception int compute_stack_size_rec(JSContext *ctx,
+                                              JSFunctionDef *fd,
+                                              StackSizeState *s,
+                                              int pos, int op, int stack_len)
+{
+    int bc_len, diff, n_pop, pos_next;
+    const JSOpCode *oi;
+    const uint8_t *bc_buf;
+
+    if (stack_len > s->stack_len_max) {
+        s->stack_len_max = stack_len;
+        if (s->stack_len_max > JS_STACK_SIZE_MAX)
+            goto stack_overflow;
+    }
+    bc_buf = fd->byte_code.buf;
+    bc_len = fd->byte_code.size;
+    for(;;) {
+        if ((unsigned)pos >= bc_len)
+            goto buf_overflow;
+#if 0
+        printf("%5d: %d\n", pos, stack_len);
+#endif
+        if (s->stack_level_tab[pos] != 0xffff) {
+            /* already explored: check that the stack size is consistent */
+            if (s->stack_level_tab[pos] != stack_len) {
+                JS_ThrowInternalError(ctx, "unconsistent stack size: %d %d (pc=%d)",
+                                      s->stack_level_tab[pos], stack_len, pos);
+                return -1;
+            } else {
+                return 0;
+            }
+        } else {
+            s->stack_level_tab[pos] = stack_len;
+        }
+
+        op = bc_buf[pos];
+        if (op == 0 || op >= OP_COUNT) {
+            JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos);
+            return -1;
+        }
+        oi = &short_opcode_info(op);
+        pos_next = pos + oi->size;
+        if (pos_next > bc_len) {
+        buf_overflow:
+            JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
+            return -1;
+        }
+        n_pop = oi->n_pop;
+        /* call pops a variable number of arguments */
+        if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) {
+            n_pop += get_u16(bc_buf + pos + 1);
+        } else {
+#if SHORT_OPCODES
+            if (oi->fmt == OP_FMT_npopx) {
+                n_pop += op - OP_call0;
+            }
+#endif
+        }
+
+        if (stack_len < n_pop) {
+            JS_ThrowInternalError(ctx, "stack underflow (op=%d, pc=%d)", op, pos);
+            return -1;
+        }
+        stack_len += oi->n_push - n_pop;
+        if (stack_len > s->stack_len_max) {
+            s->stack_len_max = stack_len;
+            if (s->stack_len_max > JS_STACK_SIZE_MAX)
+                goto stack_overflow;
+        }
+        switch(op) {
+        case OP_tail_call:
+        case OP_tail_call_method:
+        case OP_return:
+        case OP_return_undef:
+        case OP_return_async:
+        case OP_throw:
+        case OP_throw_var:
+        case OP_ret:
+            goto done;
+        case OP_goto:
+            diff = get_u32(bc_buf + pos + 1);
+            pos_next = pos + 1 + diff;
+            break;
+#if SHORT_OPCODES
+        case OP_goto16:
+            diff = (int16_t)get_u16(bc_buf + pos + 1);
+            pos_next = pos + 1 + diff;
+            break;
+        case OP_goto8:
+            diff = (int8_t)bc_buf[pos + 1];
+            pos_next = pos + 1 + diff;
+            break;
+        case OP_if_true8:
+        case OP_if_false8:
+            diff = (int8_t)bc_buf[pos + 1];
+            if (compute_stack_size_rec(ctx, fd, s, pos + 1 + diff, op, stack_len))
+                return -1;
+            break;
+#endif
+        case OP_if_true:
+        case OP_if_false:
+        case OP_catch:
+            diff = get_u32(bc_buf + pos + 1);
+            if (compute_stack_size_rec(ctx, fd, s, pos + 1 + diff, op, stack_len))
+                return -1;
+            break;
+        case OP_gosub:
+            diff = get_u32(bc_buf + pos + 1);
+            if (compute_stack_size_rec(ctx, fd, s, pos + 1 + diff, op, stack_len + 1))
+                return -1;
+            break;
+        case OP_with_get_var:
+        case OP_with_delete_var:
+            diff = get_u32(bc_buf + pos + 5);
+            if (compute_stack_size_rec(ctx, fd, s, pos + 5 + diff, op, stack_len + 1))
+                return -1;
+            break;
+        case OP_with_make_ref:
+        case OP_with_get_ref:
+        case OP_with_get_ref_undef:
+            diff = get_u32(bc_buf + pos + 5);
+            if (compute_stack_size_rec(ctx, fd, s, pos + 5 + diff, op, stack_len + 2))
+                return -1;
+            break;
+        case OP_with_put_var:
+            diff = get_u32(bc_buf + pos + 5);
+            if (compute_stack_size_rec(ctx, fd, s, pos + 5 + diff, op, stack_len - 1))
+                return -1;
+            break;
+
+        default:
+            break;
+        }
+        pos = pos_next;
+    }
+ done:
+    return 0;
+
+ stack_overflow:
+    JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos);
+    return -1;
+}
+
+static __exception int compute_stack_size(JSContext *ctx,
+                                          JSFunctionDef *fd,
+                                          int *pstack_size)
+{
+    StackSizeState s_s, *s = &s_s;
+    int bc_len, i, ret;
+
+    bc_len = fd->byte_code.size;
+    /* bc_len > 0 */
+    s->stack_level_tab = js_malloc(ctx, sizeof(s->stack_level_tab[0]) * bc_len);
+    if (!s->stack_level_tab)
+        return -1;
+    for(i = 0; i < bc_len; i++)
+        s->stack_level_tab[i] = 0xffff;
+    s->stack_len_max = 0;
+    ret = compute_stack_size_rec(ctx, fd, s, 0, OP_invalid, 0);
+    js_free(ctx, s->stack_level_tab);
+    *pstack_size = s->stack_len_max;
+    return ret;
+}
+
+static int add_module_variables(JSContext *ctx, JSFunctionDef *fd)
+{
+    int i, idx;
+    JSModuleDef *m = fd->module;
+    JSExportEntry *me;
+    JSHoistedDef *hf;
+
+    /* The imported global variables were added as closure variables
+       in js_parse_import(). We add here the module global
+       variables. */
+
+    for(i = 0; i < fd->hoisted_def_count; i++) {
+        hf = &fd->hoisted_def[i];
+        if (add_closure_var(ctx, fd, TRUE, FALSE, i, hf->var_name, hf->is_const,
+                            hf->is_lexical, FALSE) < 0)
+            return -1;
+    }
+
+    /* resolve the variable names of the local exports */
+    for(i = 0; i < m->export_entries_count; i++) {
+        me = &m->export_entries[i];
+        if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
+            idx = find_closure_var(ctx, fd, me->local_name);
+            if (idx < 0) {
+                char buf1[ATOM_GET_STR_BUF_SIZE];
+                JS_ThrowSyntaxError(ctx, "exported variable '%s' does not exist",
+                                    JS_AtomGetStr(ctx, buf1, sizeof(buf1), me->local_name));
+                return -1;
+            }
+            me->u.local.var_idx = idx;
+        }
+    }
+    return 0;
+}
+
+/* create a function object from a function definition. The function
+   definition is freed. All the child functions are also created. It
+   must be done this way to resolve all the variables. */
+static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
+{
+    JSValue func_obj;
+    JSFunctionBytecode *b;
+    struct list_head *el, *el1;
+    int stack_size, scope, idx;
+    int function_size, byte_code_offset, cpool_offset;
+    int closure_var_offset, vardefs_offset;
+
+    /* recompute scope linkage */
+    for (scope = 0; scope < fd->scope_count; scope++) {
+        fd->scopes[scope].first = -1;
+    }
+    for (idx = 0; idx < fd->var_count; idx++) {
+        JSVarDef *vd = &fd->vars[idx];
+        vd->scope_next = fd->scopes[vd->scope_level].first;
+        fd->scopes[vd->scope_level].first = idx;
+    }
+    for (scope = 2; scope < fd->scope_count; scope++) {
+        JSVarScope *sd = &fd->scopes[scope];
+        if (sd->first == -1)
+            sd->first = fd->scopes[sd->parent].first;
+    }
+    for (idx = 0; idx < fd->var_count; idx++) {
+        JSVarDef *vd = &fd->vars[idx];
+        if (vd->scope_next == -1 && vd->scope_level > 1) {
+            scope = fd->scopes[vd->scope_level].parent;
+            vd->scope_next = fd->scopes[scope].first;
+        }
+    }
+
+    /* if the function contains an eval call, the closure variables
+       are used to compile the eval and they must be ordered by scope,
+       so it is necessary to create the closure variables before any
+       other variable lookup is done. */
+    if (fd->has_eval_call)
+        add_eval_variables(ctx, fd);
+
+    /* add the module global variables in the closure */
+    if (fd->module) {
+        if (add_module_variables(ctx, fd))
+            goto fail;
+    }
+
+    /* first create all the child functions */
+    list_for_each_safe(el, el1, &fd->child_list) {
+        JSFunctionDef *fd1;
+        int cpool_idx;
+
+        fd1 = list_entry(el, JSFunctionDef, link);
+        cpool_idx = fd1->parent_cpool_idx;
+        func_obj = js_create_function(ctx, fd1);
+        if (JS_IsException(func_obj))
+            goto fail;
+        /* save it in the constant pool */
+        assert(cpool_idx >= 0);
+        fd->cpool[cpool_idx] = func_obj;
+    }
+
+#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4)
+    if (!(fd->js_mode & JS_MODE_STRIP)) {
+        printf("pass 1\n");
+        dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size,
+                       fd->args, fd->arg_count, fd->vars, fd->var_count,
+                       fd->closure_var, fd->closure_var_count,
+                       fd->cpool, fd->cpool_count, fd->source, fd->line_num,
+                       fd->label_slots, NULL);
+        printf("\n");
+    }
+#endif
+
+    if (resolve_variables(ctx, fd))
+        goto fail;
+
+#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2)
+    if (!(fd->js_mode & JS_MODE_STRIP)) {
+        printf("pass 2\n");
+        dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size,
+                       fd->args, fd->arg_count, fd->vars, fd->var_count,
+                       fd->closure_var, fd->closure_var_count,
+                       fd->cpool, fd->cpool_count, fd->source, fd->line_num,
+                       fd->label_slots, NULL);
+        printf("\n");
+    }
+#endif
+
+    if (resolve_labels(ctx, fd))
+        goto fail;
+
+    if (compute_stack_size(ctx, fd, &stack_size) < 0)
+        goto fail;
+
+    if (fd->js_mode & JS_MODE_STRIP) {
+        function_size = offsetof(JSFunctionBytecode, debug);
+    } else {
+        function_size = sizeof(*b);
+    }
+    cpool_offset = function_size;
+    function_size += fd->cpool_count * sizeof(*fd->cpool);
+    vardefs_offset = function_size;
+    if (!(fd->js_mode & JS_MODE_STRIP) || fd->has_eval_call) {
+        function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs);
+    }
+    closure_var_offset = function_size;
+    function_size += fd->closure_var_count * sizeof(*fd->closure_var);
+    byte_code_offset = function_size;
+    function_size += fd->byte_code.size;
+
+    b = js_mallocz(ctx, function_size);
+    if (!b)
+        goto fail;
+    b->header.ref_count = 1;
+    b->gc_header.mark = 0;
+
+    b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset);
+    b->byte_code_len = fd->byte_code.size;
+    memcpy(b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size);
+    js_free(ctx, fd->byte_code.buf);
+    fd->byte_code.buf = NULL;
+
+    b->func_name = fd->func_name;
+    if (fd->arg_count + fd->var_count > 0) {
+        if ((fd->js_mode & JS_MODE_STRIP) && !fd->has_eval_call) {
+            /* Strip variable definitions not needed at runtime */
+            int i;
+            for(i = 0; i < fd->var_count; i++) {
+                JS_FreeAtom(ctx, fd->vars[i].var_name);
+            }
+            for(i = 0; i < fd->arg_count; i++) {
+                JS_FreeAtom(ctx, fd->args[i].var_name);
+            }
+            for(i = 0; i < fd->closure_var_count; i++) {
+                JS_FreeAtom(ctx, fd->closure_var[i].var_name);
+                fd->closure_var[i].var_name = JS_ATOM_NULL;
+            }
+        } else {
+            b->vardefs = (void *)((uint8_t*)b + vardefs_offset);
+            memcpy(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args[0]));
+            memcpy(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars[0]));
+        }
+        b->var_count = fd->var_count;
+        b->arg_count = fd->arg_count;
+        b->defined_arg_count = fd->defined_arg_count;
+        js_free(ctx, fd->args);
+        js_free(ctx, fd->vars);
+    }
+    b->cpool_count = fd->cpool_count;
+    if (b->cpool_count) {
+        b->cpool = (void *)((uint8_t*)b + cpool_offset);
+        memcpy(b->cpool, fd->cpool, b->cpool_count * sizeof(*b->cpool));
+    }
+    js_free(ctx, fd->cpool);
+    fd->cpool = NULL;
+
+    b->stack_size = stack_size;
+
+    if (fd->js_mode & JS_MODE_STRIP) {
+        JS_FreeAtom(ctx, fd->filename);
+        dbuf_free(&fd->pc2line);    // probably useless
+    } else {
+        /* XXX: source and pc2line info should be packed at the end of the
+           JSFunctionBytecode structure, avoiding allocation overhead
+         */
+        b->has_debug = 1;
+        b->debug.filename = fd->filename;
+        b->debug.line_num = fd->line_num;
+
+        //DynBuf pc2line;
+        //compute_pc2line_info(fd, &pc2line);
+        //js_free(ctx, fd->line_number_slots)
+        b->debug.pc2line_buf = js_realloc(ctx, fd->pc2line.buf, fd->pc2line.size);
+        if (!b->debug.pc2line_buf)
+            b->debug.pc2line_buf = fd->pc2line.buf;
+        b->debug.pc2line_len = fd->pc2line.size;
+        b->debug.source = fd->source;
+        b->debug.source_len = fd->source_len;
+    }
+    if (fd->scopes != fd->def_scope_array)
+        js_free(ctx, fd->scopes);
+
+    b->closure_var_count = fd->closure_var_count;
+    if (b->closure_var_count) {
+        b->closure_var = (void *)((uint8_t*)b + closure_var_offset);
+        memcpy(b->closure_var, fd->closure_var, b->closure_var_count * sizeof(*b->closure_var));
+    }
+    js_free(ctx, fd->closure_var);
+    fd->closure_var = NULL;
+
+    b->has_prototype = fd->has_prototype;
+    b->has_simple_parameter_list = fd->has_simple_parameter_list;
+    b->js_mode = fd->js_mode;
+    b->is_derived_class_constructor = fd->is_derived_class_constructor;
+    b->func_kind = fd->func_kind;
+    b->need_home_object = (fd->home_object_var_idx >= 0 ||
+                           fd->need_home_object);
+    b->new_target_allowed = fd->new_target_allowed;
+    b->super_call_allowed = fd->super_call_allowed;
+    b->super_allowed = fd->super_allowed;
+    b->arguments_allowed = fd->arguments_allowed;
+
+#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1)
+    if (!(fd->js_mode & JS_MODE_STRIP)) {
+        js_dump_function_bytecode(ctx, b);
+    }
+#endif
+
+    if (fd->parent) {
+        /* remove from parent list */
+        list_del(&fd->link);
+    }
+
+    js_free(ctx, fd);
+    return JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b);
+ fail:
+    js_free_function_def(ctx, fd);
+    return JS_EXCEPTION;
+}
+
+static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
+{
+    int i;
+
+#if 0
+    {
+        char buf[ATOM_GET_STR_BUF_SIZE];
+        printf("freeing %s\n",
+               JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name));
+    }
+#endif
+    free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE);
+
+    if (b->vardefs) {
+        for(i = 0; i < b->arg_count + b->var_count; i++) {
+            JS_FreeAtomRT(rt, b->vardefs[i].var_name);
+        }
+    }
+    for(i = 0; i < b->cpool_count; i++)
+        JS_FreeValueRT(rt, b->cpool[i]);
+
+    for(i = 0; i < b->closure_var_count; i++) {
+        JSClosureVar *cv = &b->closure_var[i];
+        JS_FreeAtomRT(rt, cv->var_name);
+    }
+
+    JS_FreeAtomRT(rt, b->func_name);
+    if (b->has_debug) {
+        JS_FreeAtomRT(rt, b->debug.filename);
+        js_free_rt(rt, b->debug.pc2line_buf);
+        js_free_rt(rt, b->debug.source);
+    }
+    js_free_rt(rt, b);
+}
+
+static __exception int js_parse_directives(JSParseState *s)
+{
+    char str[20];
+    JSParsePos pos;
+    BOOL has_semi;
+
+    if (s->token.val != TOK_STRING)
+        return 0;
+
+    js_parse_get_pos(s, &pos);
+
+    while(s->token.val == TOK_STRING) {
+        /* Copy actual source string representation */
+        snprintf(str, sizeof str, "%.*s",
+                 (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1);
+
+        if (next_token(s))
+            return -1;
+
+        has_semi = FALSE;
+        switch (s->token.val) {
+        case ';':
+            if (next_token(s))
+                return -1;
+            has_semi = TRUE;
+            break;
+        case '}':
+        case TOK_EOF:
+            has_semi = TRUE;
+            break;
+        case TOK_NUMBER:
+        case TOK_STRING:
+        case TOK_TEMPLATE:
+        case TOK_IDENT:
+        case TOK_REGEXP:
+        case TOK_DEC:
+        case TOK_INC:
+        case TOK_NULL:
+        case TOK_FALSE:
+        case TOK_TRUE:
+        case TOK_IF:
+        case TOK_RETURN:
+        case TOK_VAR:
+        case TOK_THIS:
+        case TOK_DELETE:
+        case TOK_TYPEOF:
+        case TOK_NEW:
+        case TOK_DO:
+        case TOK_WHILE:
+        case TOK_FOR:
+        case TOK_SWITCH:
+        case TOK_THROW:
+        case TOK_TRY:
+        case TOK_FUNCTION:
+        case TOK_DEBUGGER:
+        case TOK_WITH:
+        case TOK_CLASS:
+        case TOK_CONST:
+        case TOK_ENUM:
+        case TOK_EXPORT:
+        case TOK_IMPORT:
+        case TOK_SUPER:
+        case TOK_INTERFACE:
+        case TOK_LET:
+        case TOK_PACKAGE:
+        case TOK_PRIVATE:
+        case TOK_PROTECTED:
+        case TOK_PUBLIC:
+        case TOK_STATIC:
+            /* automatic insertion of ';' */
+            if (s->got_lf)
+                has_semi = TRUE;
+            break;
+        default:
+            break;
+        }
+        if (!has_semi)
+            break;
+        if (!strcmp(str, "use strict")) {
+            s->cur_func->has_use_strict = TRUE;
+            s->cur_func->js_mode |= JS_MODE_STRICT;
+        }
+#if !defined(DUMP_BYTECODE) || !(DUMP_BYTECODE & 8)
+        else if (!strcmp(str, "use strip")) {
+            s->cur_func->js_mode |= JS_MODE_STRIP;
+        }
+#endif
+#ifdef CONFIG_BIGNUM
+        else if (!strcmp(str, "use bigint")) {
+            s->cur_func->js_mode |= JS_MODE_BIGINT;
+        }
+        else if (!strcmp(str, "use math")) {
+            s->cur_func->js_mode |= JS_MODE_BIGINT | JS_MODE_MATH;
+        }
+#endif
+    }
+    return js_parse_seek_token(s, &pos);
+}
+
+static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd,
+                                         JSAtom func_name)
+{
+    JSAtom name;
+    int i, idx;
+
+    if (fd->js_mode & JS_MODE_STRICT) {
+        if (!fd->has_simple_parameter_list && fd->has_use_strict) {
+            return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter");
+        }
+        if (func_name == JS_ATOM_eval || func_name == JS_ATOM_arguments) {
+            return js_parse_error(s, "invalid function name in strict code");
+        }
+        for (idx = 0; idx < fd->arg_count; idx++) {
+            name = fd->args[idx].var_name;
+
+            if (name == JS_ATOM_eval || name == JS_ATOM_arguments) {
+                return js_parse_error(s, "invalid argument name in strict code");
+            }
+        }
+    }
+    /* check async_generator case */
+    if ((fd->js_mode & JS_MODE_STRICT)
+    ||  !fd->has_simple_parameter_list
+    ||  (fd->func_type == JS_PARSE_FUNC_METHOD && fd->func_kind == JS_FUNC_ASYNC)
+    ||  fd->func_type == JS_PARSE_FUNC_ARROW
+    ||  fd->func_type == JS_PARSE_FUNC_METHOD) {
+        for (idx = 0; idx < fd->arg_count; idx++) {
+            name = fd->args[idx].var_name;
+            if (name != JS_ATOM_NULL) {
+                for (i = 0; i < idx; i++) {
+                    if (fd->args[i].var_name == name)
+                        goto duplicate;
+                }
+                /* Check if argument name duplicates a destructuring parameter */
+                /* XXX: should have a flag for such variables */
+                for (i = 0; i < fd->var_count; i++) {
+                    if (fd->vars[i].var_name == name)
+                        goto duplicate;
+                }
+            }
+        }
+    }
+    return 0;
+
+duplicate:
+    return js_parse_error(s, "duplicate argument names not allowed in this context");
+}
+
+/* create a function to initialize class fields */
+static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s)
+{
+    JSFunctionDef *fd;
+    
+    fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE,
+                             s->filename, 0);
+    if (!fd)
+        return NULL;
+    fd->func_name = JS_ATOM_NULL;
+    fd->has_prototype = FALSE;
+    fd->has_home_object = TRUE;
+    
+    fd->has_arguments_binding = FALSE;
+    fd->has_this_binding = TRUE;
+    fd->is_derived_class_constructor = FALSE;
+    fd->new_target_allowed = TRUE;
+    fd->super_call_allowed = FALSE;
+    fd->super_allowed = fd->has_home_object;
+    fd->arguments_allowed = FALSE;
+    
+    fd->func_kind = JS_FUNC_NORMAL;
+    fd->func_type = JS_PARSE_FUNC_METHOD;
+    return fd;
+}
+
+/* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and
+   JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */
+static __exception int js_parse_function_decl2(JSParseState *s,
+                                               JSParseFunctionEnum func_type,
+                                               JSFunctionKindEnum func_kind,
+                                               JSAtom func_name,
+                                               const uint8_t *ptr,
+                                               int function_line_num,
+                                               JSParseExportEnum export_flag,
+                                               JSFunctionDef **pfd)
+{
+    JSContext *ctx = s->ctx;
+    JSFunctionDef *fd = s->cur_func;
+    BOOL is_expr;
+    int func_idx, lexical_func_idx = -1;
+    BOOL has_opt_arg;
+    BOOL create_func_var = FALSE;
+
+    is_expr = (func_type != JS_PARSE_FUNC_STATEMENT &&
+               func_type != JS_PARSE_FUNC_VAR);
+
+    if (func_type == JS_PARSE_FUNC_STATEMENT ||
+        func_type == JS_PARSE_FUNC_VAR ||
+        func_type == JS_PARSE_FUNC_EXPR) {
+        if (func_kind == JS_FUNC_NORMAL &&
+            token_is_pseudo_keyword(s, JS_ATOM_async) &&
+            peek_token(s, TRUE) != '\n') {
+            if (next_token(s))
+                return -1;
+            func_kind = JS_FUNC_ASYNC;
+        }
+        if (next_token(s))
+            return -1;
+        if (s->token.val == '*') {
+            if (next_token(s))
+                return -1;
+            func_kind |= JS_FUNC_GENERATOR;
+        }
+
+        if (s->token.val == TOK_IDENT) {
+            if (s->token.u.ident.is_reserved ||
+                (s->token.u.ident.atom == JS_ATOM_yield &&
+                 func_type == JS_PARSE_FUNC_EXPR &&
+                 (func_kind & JS_FUNC_GENERATOR)) ||
+                (s->token.u.ident.atom == JS_ATOM_await &&
+                 func_type == JS_PARSE_FUNC_EXPR &&
+                 (func_kind & JS_FUNC_ASYNC))) {
+                return js_parse_error_reserved_identifier(s);
+            }
+        }
+        if (s->token.val == TOK_IDENT ||
+            (((s->token.val == TOK_YIELD && !(fd->js_mode & JS_MODE_STRICT)) ||
+             (s->token.val == TOK_AWAIT && !s->is_module)) &&
+             func_type == JS_PARSE_FUNC_EXPR)) {
+            func_name = JS_DupAtom(ctx, s->token.u.ident.atom);
+            if (next_token(s)) {
+                JS_FreeAtom(ctx, func_name);
+                return -1;
+            }
+        } else {
+            if (func_type != JS_PARSE_FUNC_EXPR &&
+                export_flag != JS_PARSE_EXPORT_DEFAULT) {
+                return js_parse_error(s, "function name expected");
+            }
+        }
+    } else if (func_type != JS_PARSE_FUNC_ARROW) {
+        func_name = JS_DupAtom(ctx, func_name);
+    }
+
+    if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_MODULE &&
+        (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR)) {
+        JSHoistedDef *hf;
+        hf = find_hoisted_def(fd, func_name);
+        /* XXX: should check scope chain */
+        if (hf && hf->scope_level == fd->scope_level) {
+            js_parse_error(s, "invalid redefinition of global identifier in module code");
+            JS_FreeAtom(ctx, func_name);
+            return -1;
+        }
+    }
+
+    if (func_type == JS_PARSE_FUNC_VAR) {
+        /* Create lexical name here so function closure contains it */
+        if (!(fd->js_mode & JS_MODE_STRICT)
+        &&  find_lexical_decl(ctx, fd, func_name, fd->scope_first, FALSE) < 0
+        &&  !((func_idx = find_var(ctx, fd, func_name)) >= 0 && (func_idx & ARGUMENT_VAR_OFFSET))
+        &&  !(func_name == JS_ATOM_arguments && fd->has_arguments_binding)) {
+            create_func_var = TRUE;
+        }
+        if (fd->is_eval &&
+            (fd->eval_type == JS_EVAL_TYPE_GLOBAL ||
+             fd->eval_type == JS_EVAL_TYPE_MODULE) &&
+            fd->scope_level == 1) {
+            /* avoid creating a lexical variable in the global
+               scope. XXX: check annex B */
+            JSHoistedDef *hf;
+            hf = find_hoisted_def(fd, func_name);
+            /* XXX: should check scope chain */
+            if (hf && hf->scope_level == fd->scope_level) {
+                js_parse_error(s, "invalid redefinition of global identifier");
+                JS_FreeAtom(ctx, func_name);
+                return -1;
+            }
+        } else {
+            /* Always create a lexical name, fail if at the same scope as
+               existing name */
+            /* Lexical variable will be initialized upon entering scope */
+            lexical_func_idx = define_var(s, fd, func_name,
+                                          func_kind != JS_FUNC_NORMAL ?
+                                          JS_VAR_DEF_NEW_FUNCTION_DECL :
+                                          JS_VAR_DEF_FUNCTION_DECL);
+            if (lexical_func_idx < 0) {
+                JS_FreeAtom(ctx, func_name);
+                return -1;
+            }
+        }
+    }
+
+    fd = js_new_function_def(ctx, fd, FALSE, is_expr,
+                             s->filename, function_line_num);
+    if (!fd) {
+        JS_FreeAtom(ctx, func_name);
+        return -1;
+    }
+    if (pfd)
+        *pfd = fd;
+    s->cur_func = fd;
+    fd->func_name = func_name;
+    /* XXX: test !fd->is_generator is always false */
+    fd->has_prototype = (func_type == JS_PARSE_FUNC_STATEMENT ||
+                         func_type == JS_PARSE_FUNC_VAR ||
+                         func_type == JS_PARSE_FUNC_EXPR) &&
+                        func_kind == JS_FUNC_NORMAL;
+    fd->has_home_object = (func_type == JS_PARSE_FUNC_METHOD ||
+                           func_type == JS_PARSE_FUNC_GETTER ||
+                           func_type == JS_PARSE_FUNC_SETTER ||
+                           func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
+                           func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
+    fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW);
+    fd->has_this_binding = fd->has_arguments_binding;
+    fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
+    if (func_type == JS_PARSE_FUNC_ARROW) {
+        fd->new_target_allowed = fd->parent->new_target_allowed;
+        fd->super_call_allowed = fd->parent->super_call_allowed;
+        fd->super_allowed = fd->parent->super_allowed;
+        fd->arguments_allowed = fd->parent->arguments_allowed;
+    } else {
+        fd->new_target_allowed = TRUE;
+        fd->super_call_allowed = fd->is_derived_class_constructor;
+        fd->super_allowed = fd->has_home_object;
+        fd->arguments_allowed = TRUE;
+    }
+
+    /* fd->in_function_body == FALSE prevents yield/await during the parsing
+       of the arguments in generator/async functions. They are parsed as
+       regular identifiers for other function kinds. */
+    fd->func_kind = func_kind;
+    fd->func_type = func_type;
+
+    if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
+        func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
+        /* error if not invoked as a constructor */
+        emit_op(s, OP_check_ctor);
+    }
+
+    if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
+        emit_class_field_init(s);
+    }
+    
+    /* parse arguments */
+    fd->has_simple_parameter_list = TRUE;
+    has_opt_arg = FALSE;
+    if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) {
+        JSAtom name;
+        if (s->token.u.ident.is_reserved) {
+            js_parse_error_reserved_identifier(s);
+            goto fail;
+        }
+        name = s->token.u.ident.atom;
+        if (add_arg(ctx, fd, name) < 0)
+            goto fail;
+        fd->defined_arg_count = 1;
+    } else {
+        if (js_parse_expect(s, '('))
+            goto fail;
+
+        while (s->token.val != ')') {
+            JSAtom name;
+            BOOL rest = FALSE;
+            int idx;
+
+            if (s->token.val == TOK_ELLIPSIS) {
+                fd->has_simple_parameter_list = FALSE;
+                rest = TRUE;
+                if (next_token(s))
+                    goto fail;
+            }
+            if (s->token.val == '[' || s->token.val == '{') {
+                fd->has_simple_parameter_list = FALSE;
+                if (rest) {
+                    emit_op(s, OP_rest);
+                    emit_u16(s, fd->arg_count);
+                } else {
+                    /* unnamed arg for destructuring */
+                    idx = add_arg(ctx, fd, JS_ATOM_NULL);
+                    emit_op(s, OP_get_arg);
+                    emit_u16(s, idx);
+                }
+                if (js_parse_destructing_element(s, TOK_VAR, 1, TRUE, -1, TRUE))
+                    goto fail;
+                /* Close var object: necessary if direct eval call
+                   occurred in the assignment expression or if any
+                   variable was captured and a later direct eval call
+                   may instantiate it in the var object.
+                   The next pass will generate the capture if required.
+                 */
+                emit_op(s, OP_close_var_object);
+            } else if (s->token.val == TOK_IDENT) {
+                if (s->token.u.ident.is_reserved) {
+                    js_parse_error_reserved_identifier(s);
+                    goto fail;
+                }
+                name = s->token.u.ident.atom;
+                if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
+                    js_parse_error_reserved_identifier(s);
+                    goto fail;
+                }
+                idx = add_arg(ctx, fd, name);
+                if (idx < 0)
+                    goto fail;
+                if (next_token(s))
+                    goto fail;
+                if (rest) {
+                    emit_op(s, OP_rest);
+                    emit_u16(s, idx);
+                    emit_op(s, OP_put_arg);
+                    emit_u16(s, idx);
+                    fd->has_simple_parameter_list = FALSE;
+                    has_opt_arg = TRUE;
+                } else if (s->token.val == '=') {
+                    fd->has_simple_parameter_list = FALSE;
+                    has_opt_arg = TRUE;
+
+                    if (next_token(s))
+                        goto fail;
+
+                    /* optimize `x = void 0` default value: no code needed */
+                    if (s->token.val == TOK_VOID) {
+                        JSParsePos pos;
+                        js_parse_get_pos(s, &pos);
+                        if (next_token(s))
+                            goto fail;
+                        if (s->token.val == TOK_NUMBER) {
+                            if (next_token(s))
+                                goto fail;
+                            if (s->token.val == ',') {
+                                if (next_token(s))
+                                    goto fail;
+                                continue;
+                            }
+                            if (s->token.val == ')') {
+                                continue;
+                            }
+                        }
+                        if (js_parse_seek_token(s, &pos))
+                            goto fail;
+                    }
+#if 0
+                    /* XXX: not correct for eval code */
+                    /* Check for a default value of `undefined`
+                       to omit default argument processing */
+                    if (s->token.val == TOK_IDENT &&
+                        s->token.u.ident.atom == JS_ATOM_undefined &&
+                        fd->parent == NULL &&
+                        ((tok = peek_token(s, FALSE)) == ',' || tok == ')')) {
+                        if (next_token(s))  /* ignore undefined token */
+                            goto fail;
+                    } else
+#endif
+                    {
+                        int label = new_label(s);
+                        if (idx > 0) {
+                            emit_op(s, OP_set_arg_valid_upto);
+                            emit_u16(s, idx);
+                        }
+                        emit_op(s, OP_get_arg);
+                        emit_u16(s, idx);
+                        emit_op(s, OP_undefined);
+                        emit_op(s, OP_strict_eq);
+                        emit_goto(s, OP_if_false, label);
+                        if (js_parse_assign_expr(s, TRUE))
+                            goto fail;
+                        set_object_name(s, name);
+                        emit_op(s, OP_put_arg);
+                        emit_u16(s, idx);
+                        /* Close var object: see above comment. */
+                        emit_op(s, OP_close_var_object);
+                        emit_label(s, label);
+                    }
+                } else if (!has_opt_arg) {
+                    fd->defined_arg_count++;
+                }
+            } else {
+                js_parse_error(s, "missing formal parameter");
+                goto fail;
+            }
+            if (rest && s->token.val != ')') {
+                js_parse_expect(s, ')');
+                goto fail;
+            }
+            if (s->token.val == ',') {
+                if (next_token(s))
+                    goto fail;
+            }
+        }
+        if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) ||
+            (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) {
+            js_parse_error(s, "invalid number of arguments for getter or setter");
+            goto fail;
+        }
+    }
+
+    if (next_token(s))
+        goto fail;
+
+    /* generator function: yield after the parameters are evaluated */
+    if (func_kind == JS_FUNC_GENERATOR ||
+        func_kind == JS_FUNC_ASYNC_GENERATOR)
+        emit_op(s, OP_initial_yield);
+
+    /* in generators, yield expression is forbidden during the parsing
+       of the arguments */
+    fd->in_function_body = TRUE;
+    push_scope(s);  /* enter body scope: fd->scope_level = 1 */
+
+    if (s->token.val == TOK_ARROW) {
+        if (next_token(s))
+            goto fail;
+
+        if (s->token.val != '{') {
+            if (js_parse_function_check_names(s, fd, func_name))
+                goto fail;
+
+            if (js_parse_assign_expr(s, TRUE))
+                goto fail;
+
+            if (func_kind != JS_FUNC_NORMAL)
+                emit_op(s, OP_return_async);
+            else
+                emit_op(s, OP_return);
+
+            if (!(fd->js_mode & JS_MODE_STRIP)) {
+                /* save the function source code */
+                /* the end of the function source code is after the last
+                   token of the function source stored into s->last_ptr */
+                fd->source_len = s->last_ptr - ptr;
+                fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len);
+                if (!fd->source)
+                    goto fail;
+            }
+            goto done;
+        }
+    }
+
+    if (js_parse_expect(s, '{'))
+        goto fail;
+
+    if (js_parse_directives(s))
+        goto fail;
+
+    /* in strict_mode, check function and argument names */
+    if (js_parse_function_check_names(s, fd, func_name))
+        goto fail;
+
+    while (s->token.val != '}') {
+        if (js_parse_source_element(s))
+            goto fail;
+    }
+    if (!(fd->js_mode & JS_MODE_STRIP)) {
+        /* save the function source code */
+        fd->source_len = s->buf_ptr - ptr;
+        fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len);
+        if (!fd->source)
+            goto fail;
+    }
+
+    if (next_token(s)) {
+        /* consume the '}' */
+        goto fail;
+    }
+
+    /* in case there is no return, add one */
+    if (js_is_live_code(s)) {
+        emit_return(s, FALSE);
+    }
+done:
+    s->cur_func = fd->parent;
+
+    /* create the function object */
+    {
+        int idx;
+        JSAtom func_name = fd->func_name;
+
+        /* the real object will be set at the end of the compilation */
+        idx = cpool_add(s, JS_NULL);
+        fd->parent_cpool_idx = idx;
+
+        if (is_expr) {
+            /* for constructors, no code needs to be generated here */
+            if (func_type != JS_PARSE_FUNC_CLASS_CONSTRUCTOR &&
+                func_type != JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
+                /* OP_fclosure creates the function object from the bytecode
+                   and adds the scope information */
+                emit_op(s, OP_fclosure);
+                emit_u32(s, idx);
+                if (func_name == JS_ATOM_NULL) {
+                    emit_op(s, OP_set_name);
+                    emit_u32(s, JS_ATOM_NULL);
+                }
+            }
+        } else if (func_type == JS_PARSE_FUNC_VAR) {
+            emit_op(s, OP_fclosure);
+            emit_u32(s, idx);
+            if (create_func_var) {
+                if (s->cur_func->is_global_var) {
+                    JSHoistedDef *hf;
+                    /* the global variable must be defined at the start of the
+                       function */
+                    hf = add_hoisted_def(ctx, s->cur_func, -1, func_name, -1, FALSE);
+                    if (!hf)
+                        goto fail;
+                    /* it is considered as defined at the top level
+                       (needed for annex B.3.3.4 and B.3.3.5
+                       checks) */
+                    hf->scope_level = 0; 
+                    hf->force_init = ((s->cur_func->js_mode & JS_MODE_STRICT) != 0);
+                    /* store directly into global var, bypass lexical scope */
+                    emit_op(s, OP_dup);
+                    emit_op(s, OP_scope_put_var);
+                    emit_atom(s, func_name);
+                    emit_u16(s, 0);
+                } else {
+                    /* do not call define_var to bypass lexical scope check */
+                    func_idx = find_var(ctx, s->cur_func, func_name);
+                    if (func_idx < 0) {
+                        func_idx = add_var(ctx, s->cur_func, func_name);
+                        if (func_idx < 0)
+                            goto fail;
+                    }
+                    /* store directly into local var, bypass lexical catch scope */
+                    emit_op(s, OP_dup);
+                    emit_op(s, OP_scope_put_var);
+                    emit_atom(s, func_name);
+                    emit_u16(s, 0);
+                }
+            }
+            if (lexical_func_idx >= 0) {
+                /* lexical variable will be initialized upon entering scope */
+                s->cur_func->vars[lexical_func_idx].func_pool_or_scope_idx = idx;
+                emit_op(s, OP_drop);
+            } else {
+                /* store function object into its lexical name */
+                /* XXX: could use OP_put_loc directly */
+                emit_op(s, OP_scope_put_var_init);
+                emit_atom(s, func_name);
+                emit_u16(s, s->cur_func->scope_level);
+            }
+        } else {
+            if (!s->cur_func->is_global_var) {
+                int var_idx = define_var(s, s->cur_func, func_name, JS_VAR_DEF_VAR);
+
+                if (var_idx < 0)
+                    goto fail;
+                /* the variable will be assigned at the top of the function */
+                if (!add_hoisted_def(ctx, s->cur_func, idx, JS_ATOM_NULL, var_idx, FALSE))
+                    goto fail;
+            } else {
+                JSAtom func_var_name;
+                if (func_name == JS_ATOM_NULL)
+                    func_var_name = JS_ATOM__default_; /* export default */
+                else
+                    func_var_name = func_name;
+                /* the variable will be assigned at the top of the function */
+                if (!add_hoisted_def(ctx, s->cur_func, idx, func_var_name, -1, FALSE))
+                    goto fail;
+                if (export_flag != JS_PARSE_EXPORT_NONE) {
+                    if (!add_export_entry(s, s->cur_func->module, func_var_name,
+                                          export_flag == JS_PARSE_EXPORT_NAMED ? func_var_name : JS_ATOM_default, JS_EXPORT_TYPE_LOCAL))
+                        goto fail;
+                }
+            }
+        }
+    }
+    return 0;
+ fail:
+    s->cur_func = fd->parent;
+    js_free_function_def(ctx, fd);
+    if (pfd)
+        *pfd = NULL;
+    return -1;
+}
+
+static __exception int js_parse_function_decl(JSParseState *s,
+                                              JSParseFunctionEnum func_type,
+                                              JSFunctionKindEnum func_kind,
+                                              JSAtom func_name,
+                                              const uint8_t *ptr,
+                                              int function_line_num)
+{
+    return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr,
+                                   function_line_num, JS_PARSE_EXPORT_NONE,
+                                   NULL);
+}
+
+static __exception int js_parse_program(JSParseState *s)
+{
+    JSFunctionDef *fd = s->cur_func;
+    int idx;
+
+    if (next_token(s))
+        return -1;
+
+    if (js_parse_directives(s))
+        return -1;
+
+    fd->is_global_var = (fd->eval_type == JS_EVAL_TYPE_GLOBAL) ||
+        (fd->eval_type == JS_EVAL_TYPE_MODULE) ||
+        !(fd->js_mode & JS_MODE_STRICT);
+
+    if (!s->is_module) {
+        /* hidden variable for the return value */
+        fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_);
+        if (idx < 0)
+            return -1;
+    }
+
+    while (s->token.val != TOK_EOF) {
+        if (js_parse_source_element(s))
+            return -1;
+    }
+
+    if (!s->is_module) {
+        /* return the value of the hidden variable eval_ret_idx  */
+        emit_op(s, OP_get_loc);
+        emit_u16(s, fd->eval_ret_idx);
+
+        emit_op(s, OP_return);
+    } else {
+        emit_op(s, OP_return_undef);
+    }
+
+    return 0;
+}
+
+static void js_parse_init(JSContext *ctx, JSParseState *s,
+                          const char *input, size_t input_len,
+                          const char *filename)
+{
+    memset(s, 0, sizeof(*s));
+    s->ctx = ctx;
+    s->filename = filename;
+    s->line_num = 1;
+    s->buf_ptr = (const uint8_t *)input;
+    s->buf_end = s->buf_ptr + input_len;
+    s->token.val = ' ';
+    s->token.line_num = 1;
+}
+
+static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj,
+                                       JSValueConst this_obj,
+                                       JSVarRef **var_refs, JSStackFrame *sf)
+{
+    JSValue ret_val;
+    uint32_t tag;
+
+    tag = JS_VALUE_GET_TAG(fun_obj);
+    if (tag == JS_TAG_FUNCTION_BYTECODE) {
+        fun_obj = js_closure(ctx, fun_obj, var_refs, sf);
+        ret_val = JS_CallFree(ctx, fun_obj, this_obj, 0, NULL);
+    } else if (tag == JS_TAG_MODULE) {
+        JSModuleDef *m;
+        m = JS_VALUE_GET_PTR(fun_obj);
+        /* the module refcount should be >= 2 */
+        JS_FreeValue(ctx, fun_obj);
+        if (js_instantiate_module(ctx, m) < 0)
+            goto fail;
+        ret_val = js_evaluate_module(ctx, m);
+        if (JS_IsException(ret_val)) {
+        fail:
+            js_free_modules(ctx, JS_FREE_MODULE_NOT_EVALUATED);
+            return JS_EXCEPTION;
+        }
+    } else {
+        JS_FreeValue(ctx, fun_obj);
+        ret_val = JS_ThrowTypeError(ctx, "bytecode function expected");
+    }
+    return ret_val;
+}
+
+JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj)
+{
+    return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL);
+}
+
+static void skip_shebang(JSParseState *s)
+{
+    const uint8_t *p = s->buf_ptr;
+    int c;
+
+    if (p[0] == '#' && p[1] == '!') {
+        p += 2;
+        while (p < s->buf_end) {
+            if (*p == '\n' || *p == '\r') {
+                break;
+            } else if (*p >= 0x80) {
+                c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
+                if (c == CP_LS || c == CP_PS)
+                    break;
+            } else {
+                p++;
+            }
+        }
+        s->buf_ptr = p;
+    }
+}
+
+/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
+static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
+                                 const char *input, size_t input_len,
+                                 const char *filename, int flags, int scope_idx)
+{
+    JSParseState s1, *s = &s1;
+    int err, js_mode, eval_type;
+    JSValue fun_obj, ret_val;
+    JSStackFrame *sf;
+    JSVarRef **var_refs;
+    JSFunctionBytecode *b;
+    JSFunctionDef *fd;
+    JSModuleDef *m;
+
+    js_parse_init(ctx, s, input, input_len, filename);
+    skip_shebang(s);
+
+    eval_type = flags & JS_EVAL_TYPE_MASK;
+    m = NULL;
+    if (eval_type == JS_EVAL_TYPE_DIRECT) {
+        JSObject *p;
+        sf = ctx->current_stack_frame;
+        assert(sf != NULL);
+        assert(JS_VALUE_GET_TAG(sf->cur_func) == JS_TAG_OBJECT);
+        p = JS_VALUE_GET_OBJ(sf->cur_func);
+        assert(js_class_has_bytecode(p->class_id));
+        b = p->u.func.function_bytecode;
+        var_refs = p->u.func.var_refs;
+        js_mode = b->js_mode;
+    } else {
+        sf = NULL;
+        b = NULL;
+        var_refs = NULL;
+        js_mode = 0;
+        if (flags & JS_EVAL_FLAG_STRICT)
+            js_mode |= JS_MODE_STRICT;
+        if (flags & JS_EVAL_FLAG_STRIP)
+            js_mode |= JS_MODE_STRIP;
+        if (eval_type == JS_EVAL_TYPE_MODULE) {
+            JSAtom module_name = JS_NewAtom(ctx, filename);
+            if (module_name == JS_ATOM_NULL)
+                return JS_EXCEPTION;
+            m = js_new_module_def(ctx, module_name);
+            if (!m)
+                return JS_EXCEPTION;
+            js_mode |= JS_MODE_STRICT;
+        }
+    }
+    fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1);
+    if (!fd)
+        goto fail1;
+    s->cur_func = fd;
+    fd->eval_type = eval_type;
+    fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT);
+    if (eval_type == JS_EVAL_TYPE_DIRECT) {
+        fd->new_target_allowed = b->new_target_allowed;
+        fd->super_call_allowed = b->super_call_allowed;
+        fd->super_allowed = b->super_allowed;
+        fd->arguments_allowed = b->arguments_allowed;
+    } else {
+        fd->new_target_allowed = FALSE;
+        fd->super_call_allowed = FALSE;
+        fd->super_allowed = FALSE;
+        fd->arguments_allowed = TRUE;
+    }
+    fd->js_mode = js_mode;
+    fd->func_name = JS_DupAtom(ctx, JS_ATOM__eval_);
+    if (b) {
+        if (add_closure_variables(ctx, fd, b, scope_idx))
+            goto fail;
+    }
+    fd->module = m;
+    s->is_module = (m != NULL);
+    s->allow_html_comments = !s->is_module;
+
+    push_scope(s); /* body scope */
+
+    err = js_parse_program(s);
+    if (err) {
+    fail:
+        free_token(s, &s->token);
+        js_free_function_def(ctx, fd);
+        goto fail1;
+    }
+
+    /* create the function object and all the enclosed functions */
+    fun_obj = js_create_function(ctx, fd);
+    if (JS_IsException(fun_obj))
+        goto fail1;
+    /* Could add a flag to avoid resolution if necessary */
+    if (m) {
+        m->func_obj = fun_obj;
+        if (js_resolve_module(ctx, m) < 0)
+            goto fail1;
+        fun_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
+    }
+    if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
+        ret_val = fun_obj;
+    } else {
+        ret_val = JS_EvalFunctionInternal(ctx, fun_obj, this_obj, var_refs, sf);
+    }
+    return ret_val;
+ fail1:
+    /* XXX: should free all the unresolved dependencies */
+    if (m)
+        js_free_module_def(ctx, m);
+    return JS_EXCEPTION;
+}
+
+/* the indirection is needed to make 'eval' optional */
+static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
+                               const char *input, size_t input_len,
+                               const char *filename, int flags, int scope_idx)
+{
+    if (unlikely(!ctx->eval_internal)) {
+        return JS_ThrowTypeError(ctx, "eval is not supported");
+    }
+    return ctx->eval_internal(ctx, this_obj, input, input_len, filename,
+                              flags, scope_idx);
+}
+
+static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj,
+                             JSValueConst val, int flags, int scope_idx)
+{
+    JSValue ret;
+    const char *str;
+    size_t len;
+
+    if (!JS_IsString(val))
+        return JS_DupValue(ctx, val);
+    str = JS_ToCStringLen(ctx, &len, val);
+    if (!str)
+        return JS_EXCEPTION;
+    ret = JS_EvalInternal(ctx, this_obj, str, len, "<input>", flags, scope_idx);
+    JS_FreeCString(ctx, str);
+    return ret;
+
+}
+
+JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,
+                const char *filename, int eval_flags)
+{
+    int eval_type = eval_flags & JS_EVAL_TYPE_MASK;
+    JSValue ret;
+
+    assert(eval_type == JS_EVAL_TYPE_GLOBAL ||
+           eval_type == JS_EVAL_TYPE_MODULE);
+    ret = JS_EvalInternal(ctx, ctx->global_obj, input, input_len, filename,
+                          eval_flags, -1);
+    return ret;
+}
+
+int JS_ResolveModule(JSContext *ctx, JSValueConst obj)
+{
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
+        JSModuleDef *m = JS_VALUE_GET_PTR(obj);
+        if (js_resolve_module(ctx, m) < 0) {
+            js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+/*******************************************************************/
+/* binary object writer & reader */
+
+typedef enum BCTagEnum {
+    BC_TAG_NULL = 1,
+    BC_TAG_UNDEFINED,
+    BC_TAG_BOOL_FALSE,
+    BC_TAG_BOOL_TRUE,
+    BC_TAG_INT32,
+    BC_TAG_FLOAT64,
+    BC_TAG_STRING,
+    BC_TAG_OBJECT,
+    BC_TAG_ARRAY,
+    BC_TAG_BIG_INT,
+    BC_TAG_BIG_FLOAT,
+    BC_TAG_TEMPLATE_OBJECT,
+    BC_TAG_FUNCTION_BYTECODE,
+    BC_TAG_MODULE,
+} BCTagEnum;
+
+#ifdef CONFIG_BIGNUM
+#define BC_BASE_VERSION 2
+#else
+#define BC_BASE_VERSION 1
+#endif
+#define BC_BE_VERSION 0x40
+#ifdef WORDS_BIGENDIAN
+#define BC_VERSION (BC_BASE_VERSION | BC_BE_VERSION)
+#else
+#define BC_VERSION BC_BASE_VERSION
+#endif
+
+typedef struct BCWriterState {
+    JSContext *ctx;
+    DynBuf dbuf;
+    BOOL byte_swap;
+    BOOL allow_bytecode;
+    uint32_t first_atom;
+    uint32_t *atom_to_idx;
+    int atom_to_idx_size;
+    JSAtom *idx_to_atom;
+    int idx_to_atom_count;
+    int idx_to_atom_size;
+} BCWriterState;
+
+#ifdef DUMP_READ_OBJECT
+static const char * const bc_tag_str[] = {
+    "invalid",
+    "null",
+    "undefined",
+    "false",
+    "true",
+    "int32",
+    "float64",
+    "string",
+    "object",
+    "array",
+    "big_int",
+    "big_float",
+    "template",
+    "function",
+    "module",
+};
+#endif
+
+static void bc_put_u8(BCWriterState *s, uint8_t v)
+{
+    dbuf_putc(&s->dbuf, v);
+}
+
+static void bc_put_u16(BCWriterState *s, uint16_t v)
+{
+    if (s->byte_swap)
+        v = bswap16(v);
+    dbuf_put_u16(&s->dbuf, v);
+}
+
+static __maybe_unused void bc_put_u32(BCWriterState *s, uint32_t v)
+{
+    if (s->byte_swap)
+        v = bswap32(v);
+    dbuf_put_u32(&s->dbuf, v);
+}
+
+static void bc_put_u64(BCWriterState *s, uint64_t v)
+{
+    if (s->byte_swap)
+        v = bswap64(v);
+    dbuf_put(&s->dbuf, (uint8_t *)&v, sizeof(v));
+}
+
+static void bc_put_leb128(BCWriterState *s, uint32_t v)
+{
+    dbuf_put_leb128(&s->dbuf, v);
+}
+
+static void bc_put_sleb128(BCWriterState *s, int32_t v)
+{
+    dbuf_put_sleb128(&s->dbuf, v);
+}
+
+static void bc_set_flags(uint32_t *pflags, int *pidx, uint32_t val, int n)
+{
+    *pflags = *pflags | (val << *pidx);
+    *pidx += n;
+}
+
+static int bc_atom_to_idx(BCWriterState *s, uint32_t *pres, JSAtom atom)
+{
+    uint32_t v;
+
+    if (atom < s->first_atom || __JS_AtomIsTaggedInt(atom)) {
+        *pres = atom;
+        return 0;
+    }
+    atom -= s->first_atom;
+    if (atom < s->atom_to_idx_size && s->atom_to_idx[atom] != 0) {
+        *pres = s->atom_to_idx[atom];
+        return 0;
+    }
+    if (atom >= s->atom_to_idx_size) {
+        size_t new_size, i, slack;
+        uint32_t *new_tab;
+        /* XXX: potential arithmetic overflow */
+        new_size = s->atom_to_idx_size * 3 / 2;
+        if ((atom + 1) > new_size)
+            new_size = atom + 1;
+        new_tab = js_realloc2(s->ctx, s->atom_to_idx,
+                              new_size * sizeof(s->atom_to_idx[0]), &slack);
+        if (!new_tab)
+            goto fail;
+        new_size += slack / sizeof(*new_tab);
+        for(i = s->atom_to_idx_size; i < new_size; i++)
+            new_tab[i] = 0;
+        s->atom_to_idx = new_tab;
+        s->atom_to_idx_size = new_size;
+    }
+    if ((s->idx_to_atom_count + 1) > s->idx_to_atom_size) {
+        size_t new_size, slack;
+        JSAtom *new_tab;
+        new_size = s->idx_to_atom_size * 3 / 2;
+        if ((s->idx_to_atom_count + 1) > new_size)
+            new_size = s->idx_to_atom_count + 1;
+        new_tab = js_realloc2(s->ctx, s->idx_to_atom,
+                              new_size * sizeof(s->idx_to_atom[0]), &slack);
+        if (!new_tab)
+            goto fail;
+        new_size += slack / sizeof(*new_tab);
+        s->idx_to_atom = new_tab;
+        s->idx_to_atom_size = new_size;
+    }
+
+    v = s->idx_to_atom_count++;
+    s->idx_to_atom[v] = atom + s->first_atom;
+    v += s->first_atom;
+    s->atom_to_idx[atom] = v;
+    *pres = v;
+    return 0;
+ fail:
+    *pres = 0;
+    return -1;
+}
+
+static int bc_put_atom(BCWriterState *s, JSAtom atom)
+{
+    uint32_t v;
+
+    if (__JS_AtomIsTaggedInt(atom)) {
+        v = (__JS_AtomToUInt32(atom) << 1) | 1;
+    } else {
+        if (bc_atom_to_idx(s, &v, atom))
+            return -1;
+        v <<= 1;
+    }
+    bc_put_leb128(s, v);
+    return 0;
+}
+
+static void bc_byte_swap(uint8_t *bc_buf, int bc_len)
+{
+    int pos, len, op, fmt;
+
+    pos = 0;
+    while (pos < bc_len) {
+        op = bc_buf[pos];
+        len = short_opcode_info(op).size;
+        fmt = short_opcode_info(op).fmt;
+        switch(fmt) {
+        case OP_FMT_u16:
+        case OP_FMT_i16:
+        case OP_FMT_label16:
+        case OP_FMT_npop:
+        case OP_FMT_loc:
+        case OP_FMT_arg:
+        case OP_FMT_var_ref:
+            put_u16(bc_buf + pos + 1,
+                    bswap16(get_u16(bc_buf + pos + 1)));
+            break;
+        case OP_FMT_i32:
+        case OP_FMT_u32:
+        case OP_FMT_const:
+        case OP_FMT_label:
+        case OP_FMT_atom:
+        case OP_FMT_atom_u8:
+            put_u32(bc_buf + pos + 1,
+                    bswap32(get_u32(bc_buf + pos + 1)));
+            break;
+        case OP_FMT_atom_u16:
+        case OP_FMT_label_u16:
+            put_u32(bc_buf + pos + 1,
+                    bswap32(get_u32(bc_buf + pos + 1)));
+            put_u16(bc_buf + pos + 1 + 4,
+                    bswap16(get_u16(bc_buf + pos + 1 + 4)));
+            break;
+        case OP_FMT_atom_label_u8:
+        case OP_FMT_atom_label_u16:
+            put_u32(bc_buf + pos + 1,
+                    bswap32(get_u32(bc_buf + pos + 1)));
+            put_u32(bc_buf + pos + 1 + 4,
+                    bswap32(get_u32(bc_buf + pos + 1 + 4)));
+            if (fmt == OP_FMT_atom_label_u16) {
+                put_u16(bc_buf + pos + 1 + 4 + 4,
+                        bswap16(get_u16(bc_buf + pos + 1 + 4 + 4)));
+            }
+            break;
+        case OP_FMT_npop_u16:
+            put_u16(bc_buf + pos + 1,
+                    bswap16(get_u16(bc_buf + pos + 1)));
+            put_u16(bc_buf + pos + 1 + 2,
+                    bswap16(get_u16(bc_buf + pos + 1 + 2)));
+            break;
+        default:
+            break;
+        }
+        pos += len;
+    }
+}
+
+static int JS_WriteFunctionBytecode(BCWriterState *s,
+                                    const uint8_t *bc_buf1, int bc_len)
+{
+    int pos, len, op;
+    JSAtom atom;
+    uint8_t *bc_buf;
+    uint32_t val;
+
+    bc_buf = js_malloc(s->ctx, bc_len);
+    if (!bc_buf)
+        return -1;
+    memcpy(bc_buf, bc_buf1, bc_len);
+
+    pos = 0;
+    while (pos < bc_len) {
+        op = bc_buf[pos];
+        len = short_opcode_info(op).size;
+        switch(short_opcode_info(op).fmt) {
+        case OP_FMT_atom:
+        case OP_FMT_atom_u8:
+        case OP_FMT_atom_u16:
+        case OP_FMT_atom_label_u8:
+        case OP_FMT_atom_label_u16:
+            atom = get_u32(bc_buf + pos + 1);
+            if (bc_atom_to_idx(s, &val, atom))
+                goto fail;
+            put_u32(bc_buf + pos + 1, val);
+            break;
+        default:
+            break;
+        }
+        pos += len;
+    }
+
+    if (s->byte_swap)
+        bc_byte_swap(bc_buf, bc_len);
+
+    dbuf_put(&s->dbuf, bc_buf, bc_len);
+
+    js_free(s->ctx, bc_buf);
+    return 0;
+ fail:
+    js_free(s->ctx, bc_buf);
+    return -1;
+}
+
+static void JS_WriteString(BCWriterState *s, JSString *p)
+{
+    int i;
+    bc_put_leb128(s, ((uint32_t)p->len << 1) | p->is_wide_char);
+    if (p->is_wide_char) {
+        for(i = 0; i < p->len; i++)
+            bc_put_u16(s, p->u.str16[i]);
+    } else {
+        dbuf_put(&s->dbuf, p->u.str8, p->len);
+    }
+}
+
+static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj)
+{
+    uint32_t tag = JS_VALUE_GET_NORM_TAG(obj);
+
+    switch(tag) {
+    case JS_TAG_NULL:
+        bc_put_u8(s, BC_TAG_NULL);
+        break;
+    case JS_TAG_UNDEFINED:
+        bc_put_u8(s, BC_TAG_UNDEFINED);
+        break;
+    case JS_TAG_BOOL:
+        bc_put_u8(s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT(obj));
+        break;
+    case JS_TAG_INT:
+        bc_put_u8(s, BC_TAG_INT32);
+        bc_put_sleb128(s, JS_VALUE_GET_INT(obj));
+        break;
+    case JS_TAG_FLOAT64:
+        {
+            JSFloat64Union u;
+            bc_put_u8(s, BC_TAG_FLOAT64);
+            u.d = JS_VALUE_GET_FLOAT64(obj);
+            bc_put_u64(s, u.u64);
+        }
+        break;
+    case JS_TAG_STRING:
+        {
+            JSString *p = JS_VALUE_GET_STRING(obj);
+            bc_put_u8(s, BC_TAG_STRING);
+            JS_WriteString(s, p);
+        }
+        break;
+    case JS_TAG_FUNCTION_BYTECODE:
+        {
+            JSFunctionBytecode *b = JS_VALUE_GET_PTR(obj);
+            uint32_t flags;
+            int idx, i;
+
+            if (!s->allow_bytecode)
+                goto invalid_tag;
+            bc_put_u8(s, BC_TAG_FUNCTION_BYTECODE);
+            flags = idx = 0;
+            bc_set_flags(&flags, &idx, b->has_prototype, 1);
+            bc_set_flags(&flags, &idx, b->has_simple_parameter_list, 1);
+            bc_set_flags(&flags, &idx, b->is_derived_class_constructor, 1);
+            bc_set_flags(&flags, &idx, b->need_home_object, 1);
+            bc_set_flags(&flags, &idx, b->func_kind, 2);
+            bc_set_flags(&flags, &idx, b->new_target_allowed, 1);
+            bc_set_flags(&flags, &idx, b->super_call_allowed, 1);
+            bc_set_flags(&flags, &idx, b->super_allowed, 1);
+            bc_set_flags(&flags, &idx, b->arguments_allowed, 1);
+            bc_set_flags(&flags, &idx, b->has_debug, 1);
+            assert(idx <= 16);
+            bc_put_u16(s, flags);
+            bc_put_u8(s, b->js_mode);
+            bc_put_atom(s, b->func_name);
+
+            bc_put_leb128(s, b->arg_count);
+            bc_put_leb128(s, b->var_count);
+            bc_put_leb128(s, b->defined_arg_count);
+            bc_put_leb128(s, b->stack_size);
+            bc_put_leb128(s, b->closure_var_count);
+            bc_put_leb128(s, b->cpool_count);
+            bc_put_leb128(s, b->byte_code_len);
+            if (b->vardefs) {
+                bc_put_leb128(s, b->arg_count + b->var_count);
+                for(i = 0; i < b->arg_count + b->var_count; i++) {
+                    JSVarDef *vd = &b->vardefs[i];
+                    bc_put_atom(s, vd->var_name);
+                    bc_put_leb128(s, vd->scope_level);
+                    bc_put_leb128(s, vd->scope_next + 1);
+                    flags = idx = 0;
+                    bc_set_flags(&flags, &idx, vd->var_kind, 4);
+                    bc_set_flags(&flags, &idx, vd->is_func_var, 1);
+                    bc_set_flags(&flags, &idx, vd->is_const, 1);
+                    bc_set_flags(&flags, &idx, vd->is_lexical, 1);
+                    bc_set_flags(&flags, &idx, vd->is_captured, 1);
+                    assert(idx <= 8);
+                    bc_put_u8(s, flags);
+                }
+            } else {
+                bc_put_leb128(s, 0);
+            }
+
+            for(i = 0; i < b->closure_var_count; i++) {
+                JSClosureVar *cv = &b->closure_var[i];
+                bc_put_atom(s, cv->var_name);
+                bc_put_leb128(s, cv->var_idx);
+                flags = idx = 0;
+                bc_set_flags(&flags, &idx, cv->is_local, 1);
+                bc_set_flags(&flags, &idx, cv->is_arg, 1);
+                bc_set_flags(&flags, &idx, cv->is_const, 1);
+                bc_set_flags(&flags, &idx, cv->is_lexical, 1);
+                bc_set_flags(&flags, &idx, cv->var_kind, 4);
+                assert(idx <= 8);
+                bc_put_u8(s, flags);
+            }
+
+            if (JS_WriteFunctionBytecode(s, b->byte_code_buf, b->byte_code_len))
+                goto fail;
+
+            if (b->has_debug) {
+                bc_put_atom(s, b->debug.filename);
+                bc_put_leb128(s, b->debug.line_num);
+                bc_put_leb128(s, b->debug.pc2line_len);
+                dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len);
+            }
+
+            for(i = 0; i < b->cpool_count; i++) {
+                if (JS_WriteObjectRec(s, b->cpool[i]))
+                    goto fail;
+            }
+        }
+        break;
+    case JS_TAG_MODULE:
+        {
+            JSModuleDef *m = JS_VALUE_GET_PTR(obj);
+            int i;
+
+            if (!s->allow_bytecode)
+                goto invalid_tag;
+            bc_put_u8(s, BC_TAG_MODULE);
+            bc_put_atom(s, m->module_name);
+
+            bc_put_leb128(s, m->req_module_entries_count);
+            for(i = 0; i < m->req_module_entries_count; i++) {
+                JSReqModuleEntry *rme = &m->req_module_entries[i];
+                bc_put_atom(s, rme->module_name);
+            }
+
+            bc_put_leb128(s, m->export_entries_count);
+            for(i = 0; i < m->export_entries_count; i++) {
+                JSExportEntry *me = &m->export_entries[i];
+                bc_put_u8(s, me->export_type);
+                if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
+                    bc_put_leb128(s, me->u.local.var_idx);
+                } else {
+                    bc_put_leb128(s, me->u.req_module_idx);
+                    bc_put_atom(s, me->local_name);
+                }
+                bc_put_atom(s, me->export_name);
+            }
+
+            bc_put_leb128(s, m->star_export_entries_count);
+            for(i = 0; i < m->star_export_entries_count; i++) {
+                JSStarExportEntry *se = &m->star_export_entries[i];
+                bc_put_leb128(s, se->req_module_idx);
+            }
+
+            bc_put_leb128(s, m->import_entries_count);
+            for(i = 0; i < m->import_entries_count; i++) {
+                JSImportEntry *mi = &m->import_entries[i];
+                bc_put_leb128(s, mi->var_idx);
+                bc_put_atom(s, mi->import_name);
+                bc_put_leb128(s, mi->req_module_idx);
+            }
+
+            if (JS_WriteObjectRec(s, m->func_obj))
+                goto fail;
+        }
+        break;
+    case JS_TAG_OBJECT:
+        {
+            JSObject *p = JS_VALUE_GET_OBJ(obj);
+            uint32_t i, prop_count, len;
+            JSShape *sh;
+            JSShapeProperty *pr;
+            JSValue val;
+            int ret, pass;
+            BOOL is_template;
+            JSAtom atom;
+
+            if (p->class_id != JS_CLASS_ARRAY &&
+                p->class_id != JS_CLASS_OBJECT) {
+                JS_ThrowTypeError(s->ctx, "unsupported object class");
+                goto fail;
+            }
+            if (p->tmp_mark) {
+                JS_ThrowTypeError(s->ctx, "circular reference");
+                goto fail;
+            }
+            p->tmp_mark = 1;
+            if (p->class_id == JS_CLASS_ARRAY) {
+                if (s->allow_bytecode && !p->extensible) {
+                    /* not extensible array: we consider it is a
+                       template when we are saving bytecode */
+                    bc_put_u8(s, BC_TAG_TEMPLATE_OBJECT);
+                    is_template = TRUE;
+                } else {
+                    bc_put_u8(s, BC_TAG_ARRAY);
+                    is_template = FALSE;
+                }
+                if (js_get_length32(s->ctx, &len, obj))
+                    goto fail1;
+                bc_put_leb128(s, len);
+                for(i = 0; i < len; i++) {
+                    val = JS_GetPropertyUint32(s->ctx, obj, i);
+                    if (JS_IsException(val))
+                        goto fail1;
+                    ret = JS_WriteObjectRec(s, val);
+                    JS_FreeValue(s->ctx, val);
+                    if (ret)
+                        goto fail1;
+                }
+                if (is_template) {
+                    val = JS_GetProperty(s->ctx, obj, JS_ATOM_raw);
+                    if (JS_IsException(val))
+                        goto fail1;
+                    ret = JS_WriteObjectRec(s, val);
+                    JS_FreeValue(s->ctx, val);
+                    if (ret)
+                        goto fail1;
+                }
+            } else {
+                bc_put_u8(s, BC_TAG_OBJECT);
+                prop_count = 0;
+                sh = p->shape;
+                for(pass = 0; pass < 2; pass++) {
+                    if (pass == 1)
+                        bc_put_leb128(s, prop_count);
+                    for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
+                        atom = pr->atom;
+                        if (atom != JS_ATOM_NULL &&
+                            JS_AtomIsString(s->ctx, atom) &&
+                            (pr->flags & JS_PROP_ENUMERABLE)) {
+                            if (pr->flags & JS_PROP_TMASK) {
+                                JS_ThrowTypeError(s->ctx, "only value properties are supported");
+                                goto fail;
+                            }
+                            if (pass == 0) {
+                                prop_count++;
+                            } else {
+                                bc_put_atom(s, atom);
+                                if (JS_WriteObjectRec(s, p->prop[i].u.value)) {
+                                fail1:
+                                    p->tmp_mark = 0;
+                                    goto fail;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            p->tmp_mark = 0;
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+    case JS_TAG_BIG_FLOAT:
+        {
+            int32_t e;
+            JSBigFloat *bf = JS_VALUE_GET_PTR(obj);
+            bf_t *a = &bf->num;
+            size_t len, i, n1, j;
+            limb_t v;
+
+            bc_put_u8(s, tag == JS_TAG_BIG_INT ? BC_TAG_BIG_INT :
+                      BC_TAG_BIG_FLOAT);
+            if (a->len != 0) {
+                i = 0;
+                while (i < a->len && a->tab[i] == 0)
+                    i++;
+                assert(i < a->len);
+                v = a->tab[i];
+                n1 = sizeof(limb_t);
+                while ((v & 0xff) == 0) {
+                    n1--;
+                    v >>= 8;
+                }
+                i++;
+                len = (a->len - i) * sizeof(limb_t) + n1;
+                if (len > INT32_MAX) {
+                    JS_ThrowInternalError(s->ctx, "bignum is too large");
+                    goto fail;
+                }
+                /* XXX: it would be slightly more efficient to put the
+                   sign in the MSB of the mantissa */
+                bc_put_leb128(s, len * 2 + a->sign);
+                /* always saved in byte based little endian representation */
+                for(j = 0; j < n1; j++) {
+                    dbuf_putc(&s->dbuf, v >> (j * 8));
+                }
+                for(; i < a->len; i++) {
+                    limb_t v = a->tab[i];
+#if LIMB_BITS == 32
+#ifdef WORDS_BIGENDIAN
+                    v = bswap32(v);
+#endif
+                    dbuf_put_u32(&s->dbuf, v);
+#else
+#ifdef WORDS_BIGENDIAN
+                    v = bswap64(v);
+#endif
+                    dbuf_put_u64(&s->dbuf, v);
+#endif
+                }
+#if LIMB_BITS != 32
+                if (a->expn <= INT32_MIN || a->expn >= (INT32_MAX - 1)) {
+                    JS_ThrowInternalError(s->ctx, "bignum exponent is too large");
+                    goto fail;
+                }
+#endif
+                bc_put_sleb128(s, a->expn);
+            } else {
+                bc_put_leb128(s, a->sign); /* length + sign */
+                if (tag == JS_TAG_BIG_FLOAT) {
+                    /* special numbers */
+                    if (a->expn == BF_EXP_ZERO) {
+                        e = 0;
+                    } else if (a->expn == BF_EXP_INF) {
+                        e = 1;
+                    } else {
+                        e = 2;
+                    }
+                    bc_put_u8(s, e);
+                }
+            }
+        }
+        break;
+#endif
+    default:
+    invalid_tag:
+        JS_ThrowInternalError(s->ctx, "unsupported tag (%d)", tag);
+        goto fail;
+    }
+    return 0;
+
+ fail:
+    return -1;
+}
+
+/* create the atom table */
+static int JS_WriteObjectAtoms(BCWriterState *s)
+{
+    JSRuntime *rt = s->ctx->rt;
+    DynBuf dbuf1;
+    int i, atoms_size;
+    uint8_t version;
+
+    dbuf1 = s->dbuf;
+    js_dbuf_init(s->ctx, &s->dbuf);
+
+    version = BC_VERSION;
+    if (s->byte_swap)
+        version ^= BC_BE_VERSION;
+    bc_put_u8(s, version);
+
+    bc_put_leb128(s, s->idx_to_atom_count);
+    for(i = 0; i < s->idx_to_atom_count; i++) {
+        JSAtomStruct *p = rt->atom_array[s->idx_to_atom[i]];
+        JS_WriteString(s, p);
+    }
+    /* XXX: should check for OOM in above phase */
+
+    /* move the atoms at the start */
+    /* XXX: could just append dbuf1 data, but it uses more memory if
+       dbuf1 is larger than dbuf */
+    atoms_size = s->dbuf.size;
+    if (dbuf_realloc(&dbuf1, dbuf1.size + atoms_size))
+        goto fail;
+    memmove(dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size);
+    memcpy(dbuf1.buf, s->dbuf.buf, atoms_size);
+    dbuf1.size += atoms_size;
+    dbuf_free(&s->dbuf);
+    s->dbuf = dbuf1;
+    return 0;
+ fail:
+    dbuf_free(&dbuf1);
+    return -1;
+}
+
+uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj,
+                        int flags)
+{
+    BCWriterState ss, *s = &ss;
+
+    memset(s, 0, sizeof(*s));
+    s->ctx = ctx;
+    /* XXX: byte swapped output is untested */
+    s->byte_swap = ((flags & JS_WRITE_OBJ_BSWAP) != 0);
+    s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0);
+    /* XXX: could use a different version when bytecode is included */
+    if (s->allow_bytecode)
+        s->first_atom = JS_ATOM_END;
+    else
+        s->first_atom = 1;
+    js_dbuf_init(ctx, &s->dbuf);
+
+    if (JS_WriteObjectRec(s, obj))
+        goto fail;
+    if (JS_WriteObjectAtoms(s))
+        goto fail;
+    js_free(ctx, s->atom_to_idx);
+    js_free(ctx, s->idx_to_atom);
+    *psize = s->dbuf.size;
+    return s->dbuf.buf;
+ fail:
+    js_free(ctx, s->atom_to_idx);
+    js_free(ctx, s->idx_to_atom);
+    dbuf_free(&s->dbuf);
+    *psize = 0;
+    return NULL;
+}
+
+typedef struct BCReaderState {
+    JSContext *ctx;
+    const uint8_t *buf_start, *ptr, *buf_end;
+    uint32_t first_atom;
+    uint32_t idx_to_atom_count;
+    JSAtom *idx_to_atom;
+    int error_state;
+    BOOL allow_bytecode;
+    BOOL is_rom_data;
+#ifdef DUMP_READ_OBJECT
+    const uint8_t *ptr_last;
+    int level;
+#endif
+} BCReaderState;
+
+#ifdef DUMP_READ_OBJECT
+static void __attribute__((format(printf, 2, 3))) bc_read_trace(BCReaderState *s, const char *fmt, ...) {
+    va_list ap;
+    int i, n, n0;
+
+    if (!s->ptr_last)
+        s->ptr_last = s->buf_start;
+
+    n = n0 = 0;
+    if (s->ptr > s->ptr_last || s->ptr == s->buf_start) {
+        n0 = printf("%04x: ", (int)(s->ptr_last - s->buf_start));
+        n += n0;
+    }
+    for (i = 0; s->ptr_last < s->ptr; i++) {
+        if ((i & 7) == 0 && i > 0) {
+            printf("\n%*s", n0, "");
+            n = n0;
+        }
+        n += printf(" %02x", *s->ptr_last++);
+    }
+    if (*fmt == '}')
+        s->level--;
+    if (n < 32 + s->level * 2) {
+        printf("%*s", 32 + s->level * 2 - n, "");
+    }
+    va_start(ap, fmt);
+    vfprintf(stdout, fmt, ap);
+    va_end(ap);
+    if (strchr(fmt, '{'))
+        s->level++;
+}
+#else
+#define bc_read_trace(...)
+#endif
+
+static int bc_read_error_end(BCReaderState *s)
+{
+    if (!s->error_state) {
+        JS_ThrowSyntaxError(s->ctx, "read after the end of the buffer");
+    }
+    return s->error_state = -1;
+}
+
+static int bc_get_u8(BCReaderState *s, uint8_t *pval)
+{
+    if (unlikely(s->buf_end - s->ptr < 1)) {
+        *pval = 0; /* avoid warning */
+        return bc_read_error_end(s);
+    }
+    *pval = *s->ptr++;
+    return 0;
+}
+
+static int bc_get_u16(BCReaderState *s, uint16_t *pval)
+{
+    if (unlikely(s->buf_end - s->ptr < 2)) {
+        *pval = 0; /* avoid warning */
+        return bc_read_error_end(s);
+    }
+    *pval = get_u16(s->ptr);
+    s->ptr += 2;
+    return 0;
+}
+
+static __maybe_unused int bc_get_u32(BCReaderState *s, uint32_t *pval)
+{
+    if (unlikely(s->buf_end - s->ptr < 4)) {
+        *pval = 0; /* avoid warning */
+        return bc_read_error_end(s);
+    }
+    *pval = get_u32(s->ptr);
+    s->ptr += 4;
+    return 0;
+}
+
+static int bc_get_u64(BCReaderState *s, uint64_t *pval)
+{
+    if (unlikely(s->buf_end - s->ptr < 8)) {
+        *pval = 0; /* avoid warning */
+        return bc_read_error_end(s);
+    }
+    *pval = get_u64(s->ptr);
+    s->ptr += 8;
+    return 0;
+}
+
+static int bc_get_leb128(BCReaderState *s, uint32_t *pval)
+{
+    int ret;
+    ret = get_leb128(pval, s->ptr, s->buf_end);
+    if (unlikely(ret < 0))
+        return bc_read_error_end(s);
+    s->ptr += ret;
+    return 0;
+}
+
+static int bc_get_sleb128(BCReaderState *s, int32_t *pval)
+{
+    int ret;
+    ret = get_sleb128(pval, s->ptr, s->buf_end);
+    if (unlikely(ret < 0))
+        return bc_read_error_end(s);
+    s->ptr += ret;
+    return 0;
+}
+
+/* XXX: used to read an `int` with a positive value */
+static int bc_get_leb128_int(BCReaderState *s, int *pval)
+{
+    return bc_get_leb128(s, (uint32_t *)pval);
+}
+
+static int bc_get_leb128_u16(BCReaderState *s, uint16_t *pval)
+{
+    uint32_t val;
+    if (bc_get_leb128(s, &val)) {
+        *pval = 0;
+        return -1;
+    }
+    *pval = val;
+    return 0;
+}
+
+static int bc_get_buf(BCReaderState *s, uint8_t *buf, uint32_t buf_len)
+{
+    if (buf_len != 0) {
+        if (unlikely(!buf || s->buf_end - s->ptr < buf_len))
+            return bc_read_error_end(s);
+        memcpy(buf, s->ptr, buf_len);
+        s->ptr += buf_len;
+    }
+    return 0;
+}
+
+static int bc_idx_to_atom(BCReaderState *s, JSAtom *patom, uint32_t idx)
+{
+    JSAtom atom;
+
+    if (__JS_AtomIsTaggedInt(idx)) {
+        atom = idx;
+    } else if (idx < s->first_atom) {
+        atom = JS_DupAtom(s->ctx, idx);
+    } else {
+        idx -= s->first_atom;
+        if (idx >= s->idx_to_atom_count) {
+            JS_ThrowSyntaxError(s->ctx, "invalid atom index (pos=%u)",
+                                (unsigned int)(s->ptr - s->buf_start));
+            *patom = JS_ATOM_NULL;
+            return s->error_state = -1;
+        }
+        atom = JS_DupAtom(s->ctx, s->idx_to_atom[idx]);
+    }
+    *patom = atom;
+    return 0;
+}
+
+static int bc_get_atom(BCReaderState *s, JSAtom *patom)
+{
+    uint32_t v;
+    if (bc_get_leb128(s, &v))
+        return -1;
+    if (v & 1) {
+        *patom = __JS_AtomFromUInt32(v >> 1);
+        return 0;
+    } else {
+        return bc_idx_to_atom(s, patom, v >> 1);
+    }
+}
+
+static JSString *JS_ReadString(BCReaderState *s)
+{
+    uint32_t len;
+    size_t size;
+    BOOL is_wide_char;
+    JSString *p;
+
+    if (bc_get_leb128(s, &len))
+        return NULL;
+    is_wide_char = len & 1;
+    len >>= 1;
+    p = js_alloc_string(s->ctx, len, is_wide_char);
+    if (!p) {
+        s->error_state = -1;
+        return NULL;
+    }
+    size = (size_t)len << is_wide_char;
+    if ((s->buf_end - s->ptr) < size) {
+        bc_read_error_end(s);
+        js_free_string(s->ctx->rt, p);
+        return NULL;
+    }
+    memcpy(p->u.str8, s->ptr, size);
+    s->ptr += size;
+    if (!is_wide_char) {
+        p->u.str8[size] = '\0'; /* add the trailing zero for 8 bit strings */
+    }
+#ifdef DUMP_READ_OBJECT
+    bc_read_trace(s, "string: "); JS_DumpString(s->ctx->rt, p); printf("\n");
+#endif
+    return p;
+}
+
+static uint32_t bc_get_flags(uint32_t flags, int *pidx, int n)
+{
+    uint32_t val;
+    /* XXX: this does not work for n == 32 */
+    val = (flags >> *pidx) & ((1U << n) - 1);
+    *pidx += n;
+    return val;
+}
+
+static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b,
+                                   int byte_code_offset, uint32_t bc_len)
+{
+    uint8_t *bc_buf;
+    int pos, len, op;
+    JSAtom atom;
+    uint32_t idx;
+
+    if (s->is_rom_data) {
+        /* directly use the input buffer */
+        if (unlikely(s->buf_end - s->ptr < bc_len))
+            return bc_read_error_end(s);
+        bc_buf = (uint8_t *)s->ptr;
+        s->ptr += bc_len;
+    } else {
+        bc_buf = (void *)((uint8_t*)b + byte_code_offset);
+        if (bc_get_buf(s, bc_buf, bc_len))
+            return -1;
+    }
+    b->byte_code_buf = bc_buf;
+
+    pos = 0;
+    while (pos < bc_len) {
+        op = bc_buf[pos];
+        len = short_opcode_info(op).size;
+        switch(short_opcode_info(op).fmt) {
+        case OP_FMT_atom:
+        case OP_FMT_atom_u8:
+        case OP_FMT_atom_u16:
+        case OP_FMT_atom_label_u8:
+        case OP_FMT_atom_label_u16:
+            idx = get_u32(bc_buf + pos + 1);
+            if (s->is_rom_data) {
+                /* just increment the reference count of the atom */
+                JS_DupAtom(s->ctx, (JSAtom)idx);
+            } else {
+                if (bc_idx_to_atom(s, &atom, idx)) {
+                    /* Note: the atoms will be freed up to this position */
+                    b->byte_code_len = pos;
+                    return -1;
+                }
+                put_u32(bc_buf + pos + 1, atom);
+#ifdef DUMP_READ_OBJECT
+                bc_read_trace(s, "at %d, fixup atom: ", pos + 1); print_atom(s->ctx, atom); printf("\n");
+#endif
+            }
+            break;
+        default:
+            break;
+        }
+        pos += len;
+    }
+    return 0;
+}
+
+static JSValue JS_ReadObjectRec(BCReaderState *s)
+{
+    JSContext *ctx = s->ctx;
+    uint8_t tag;
+    JSValue obj = JS_UNDEFINED;
+    JSModuleDef *m = NULL;
+
+    if (bc_get_u8(s, &tag))
+        return JS_EXCEPTION;
+
+    switch(tag) {
+    case BC_TAG_NULL:
+        bc_read_trace(s, "null\n");
+        obj = JS_NULL;
+        break;
+    case BC_TAG_UNDEFINED:
+        bc_read_trace(s, "undefined\n");
+        obj = JS_UNDEFINED;
+        break;
+    case BC_TAG_BOOL_FALSE:
+    case BC_TAG_BOOL_TRUE:
+        bc_read_trace(s, "%s\n", bc_tag_str[tag]);
+        obj = JS_NewBool(ctx, tag - BC_TAG_BOOL_FALSE);
+        break;
+    case BC_TAG_INT32:
+        {
+            int32_t val;
+            if (bc_get_sleb128(s, &val))
+                return JS_EXCEPTION;
+            bc_read_trace(s, "int32 %d\n", val);
+            obj = JS_NewInt32(ctx, val);
+        }
+        break;
+    case BC_TAG_FLOAT64:
+        {
+            JSFloat64Union u;
+            if (bc_get_u64(s, &u.u64))
+                return JS_EXCEPTION;
+            bc_read_trace(s, "float64 %g\n", u.d);
+            obj = __JS_NewFloat64(ctx, u.d);
+        }
+        break;
+    case BC_TAG_STRING:
+        {
+            JSString *p;
+            p = JS_ReadString(s);
+            if (!p)
+                return JS_EXCEPTION;
+            obj = JS_MKPTR(JS_TAG_STRING, p);
+        }
+        break;
+    case BC_TAG_FUNCTION_BYTECODE:
+        {
+            JSFunctionBytecode bc, *b;
+            uint16_t v16;
+            uint8_t v8;
+            int idx, i, local_count;
+            int function_size, cpool_offset, byte_code_offset;
+            int closure_var_offset, vardefs_offset;
+
+            if (!s->allow_bytecode)
+                goto invalid_tag;
+            bc_read_trace(s, "%s {\n", bc_tag_str[tag]);
+
+            memset(&bc, 0, sizeof(bc));
+            bc.header.ref_count = 1;
+            //bc.gc_header.mark = 0;
+
+            if (bc_get_u16(s, &v16))
+                goto fail;
+            idx = 0;
+            bc.has_prototype = bc_get_flags(v16, &idx, 1);
+            bc.has_simple_parameter_list = bc_get_flags(v16, &idx, 1);
+            bc.is_derived_class_constructor = bc_get_flags(v16, &idx, 1);
+            bc.need_home_object = bc_get_flags(v16, &idx, 1);
+            bc.func_kind = bc_get_flags(v16, &idx, 2);
+            bc.new_target_allowed = bc_get_flags(v16, &idx, 1);
+            bc.super_call_allowed = bc_get_flags(v16, &idx, 1);
+            bc.super_allowed = bc_get_flags(v16, &idx, 1);
+            bc.arguments_allowed = bc_get_flags(v16, &idx, 1);
+            bc.has_debug = bc_get_flags(v16, &idx, 1);
+            bc.read_only_bytecode = s->is_rom_data;
+            if (bc_get_u8(s, &v8))
+                goto fail;
+            bc.js_mode = v8;
+            if (bc_get_atom(s, &bc.func_name))  //@ atom leak if failure
+                goto fail;
+            if (bc_get_leb128_u16(s, &bc.arg_count))
+                goto fail;
+            if (bc_get_leb128_u16(s, &bc.var_count))
+                goto fail;
+            if (bc_get_leb128_u16(s, &bc.defined_arg_count))
+                goto fail;
+            if (bc_get_leb128_u16(s, &bc.stack_size))
+                goto fail;
+            if (bc_get_leb128_int(s, &bc.closure_var_count))
+                goto fail;
+            if (bc_get_leb128_int(s, &bc.cpool_count))
+                goto fail;
+            if (bc_get_leb128_int(s, &bc.byte_code_len))
+                goto fail;
+            if (bc_get_leb128_int(s, &local_count))
+                goto fail;
+
+            if (bc.has_debug) {
+                function_size = sizeof(*b);
+            } else {
+                function_size = offsetof(JSFunctionBytecode, debug);
+            }
+            cpool_offset = function_size;
+            function_size += bc.cpool_count * sizeof(*bc.cpool);
+            vardefs_offset = function_size;
+            function_size += local_count * sizeof(*bc.vardefs);
+            closure_var_offset = function_size;
+            function_size += bc.closure_var_count * sizeof(*bc.closure_var);
+            byte_code_offset = function_size;
+            if (!bc.read_only_bytecode) {
+                function_size += bc.byte_code_len;
+            }
+
+            b = js_mallocz(ctx, function_size);
+            if (!b)
+                return JS_EXCEPTION;
+
+            memcpy(b, &bc, offsetof(JSFunctionBytecode, debug));
+            obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b);
+
+#ifdef DUMP_READ_OBJECT
+            bc_read_trace(s, "name: "); print_atom(s->ctx, b->func_name); printf("\n");
+#endif
+            bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n",
+                          b->arg_count, b->var_count, b->defined_arg_count,
+                          b->closure_var_count, b->cpool_count);
+            bc_read_trace(s, "stack=%d bclen=%d locals=%d\n",
+                          b->stack_size, b->byte_code_len, local_count);
+
+            if (local_count != 0) {
+                bc_read_trace(s, "vars {\n");
+                b->vardefs = (void *)((uint8_t*)b + vardefs_offset);
+                for(i = 0; i < local_count; i++) {
+                    JSVarDef *vd = &b->vardefs[i];
+                    if (bc_get_atom(s, &vd->var_name))
+                        goto fail;
+                    if (bc_get_leb128_int(s, &vd->scope_level))
+                        goto fail;
+                    if (bc_get_leb128_int(s, &vd->scope_next))
+                        goto fail;
+                    vd->scope_next--;
+                    if (bc_get_u8(s, &v8))
+                        goto fail;
+                    idx = 0;
+                    vd->var_kind = bc_get_flags(v8, &idx, 4);
+                    vd->is_func_var = bc_get_flags(v8, &idx, 1);
+                    vd->is_const = bc_get_flags(v8, &idx, 1);
+                    vd->is_lexical = bc_get_flags(v8, &idx, 1);
+                    vd->is_captured = bc_get_flags(v8, &idx, 1);
+#ifdef DUMP_READ_OBJECT
+                    bc_read_trace(s, "name: "); print_atom(s->ctx, vd->var_name); printf("\n");
+#endif
+                }
+                bc_read_trace(s, "}\n");
+            }
+            if (b->closure_var_count != 0) {
+                bc_read_trace(s, "closure vars {\n");
+                b->closure_var = (void *)((uint8_t*)b + closure_var_offset);
+                for(i = 0; i < b->closure_var_count; i++) {
+                    JSClosureVar *cv = &b->closure_var[i];
+                    int var_idx;
+                    if (bc_get_atom(s, &cv->var_name))
+                        goto fail;
+                    if (bc_get_leb128_int(s, &var_idx))
+                        goto fail;
+                    cv->var_idx = var_idx;
+                    if (bc_get_u8(s, &v8))
+                        goto fail;
+                    idx = 0;
+                    cv->is_local = bc_get_flags(v8, &idx, 1);
+                    cv->is_arg = bc_get_flags(v8, &idx, 1);
+                    cv->is_const = bc_get_flags(v8, &idx, 1);
+                    cv->is_lexical = bc_get_flags(v8, &idx, 1);
+                    cv->var_kind = bc_get_flags(v8, &idx, 4);
+#ifdef DUMP_READ_OBJECT
+                    bc_read_trace(s, "name: "); print_atom(s->ctx, cv->var_name); printf("\n");
+#endif
+                }
+                bc_read_trace(s, "}\n");
+            }
+            {
+                bc_read_trace(s, "bytecode {\n");
+                if (JS_ReadFunctionBytecode(s, b, byte_code_offset, b->byte_code_len))
+                    goto fail;
+                bc_read_trace(s, "}\n");
+            }
+            if (b->has_debug) {
+                /* read optional debug information */
+                bc_read_trace(s, "debug {\n");
+                if (bc_get_atom(s, &b->debug.filename))
+                    goto fail;
+                if (bc_get_leb128_int(s, &b->debug.line_num))
+                    goto fail;
+                if (bc_get_leb128_int(s, &b->debug.pc2line_len))
+                    goto fail;
+                if (b->debug.pc2line_len) {
+                    b->debug.pc2line_buf = js_mallocz(ctx, b->debug.pc2line_len);
+                    if (!b->debug.pc2line_buf)
+                        goto fail;
+                    if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len))
+                        goto fail;
+                }
+#ifdef DUMP_READ_OBJECT
+                bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n");
+#endif
+                bc_read_trace(s, "}\n");
+            }
+            if (b->cpool_count != 0) {
+                bc_read_trace(s, "cpool {\n");
+                b->cpool = (void *)((uint8_t*)b + cpool_offset);
+                for(i = 0; i < b->cpool_count; i++) {
+                    JSValue val;
+                    val = JS_ReadObjectRec(s);
+                    if (JS_IsException(val))
+                        goto fail;
+                    b->cpool[i] = val;
+                }
+                bc_read_trace(s, "}\n");
+            }
+            bc_read_trace(s, "}\n");
+        }
+        break;
+    case BC_TAG_MODULE:
+        {
+            JSAtom module_name;
+            int i;
+            uint8_t v8;
+
+            if (!s->allow_bytecode)
+                goto invalid_tag;
+            bc_read_trace(s, "%s {\n", bc_tag_str[tag]);
+            if (bc_get_atom(s, &module_name))
+                goto fail;
+#ifdef DUMP_READ_OBJECT
+            bc_read_trace(s, "name: "); print_atom(s->ctx, module_name); printf("\n");
+#endif
+            m = js_new_module_def(ctx, module_name);
+            if (!m)
+                goto fail;
+            obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
+            if (bc_get_leb128_int(s, &m->req_module_entries_count))
+                goto fail;
+            if (m->req_module_entries_count != 0) {
+                m->req_module_entries_size = m->req_module_entries_count;
+                m->req_module_entries = js_mallocz(ctx, sizeof(m->req_module_entries[0]) * m->req_module_entries_size);
+                if (!m->req_module_entries)
+                    goto fail;
+                for(i = 0; i < m->req_module_entries_count; i++) {
+                    JSReqModuleEntry *rme = &m->req_module_entries[i];
+                    if (bc_get_atom(s, &rme->module_name))
+                        goto fail;
+                }
+            }
+
+            if (bc_get_leb128_int(s, &m->export_entries_count))
+                goto fail;
+            if (m->export_entries_count != 0) {
+                m->export_entries_size = m->export_entries_count;
+                m->export_entries = js_mallocz(ctx, sizeof(m->export_entries[0]) * m->export_entries_size);
+                if (!m->export_entries)
+                    goto fail;
+                for(i = 0; i < m->export_entries_count; i++) {
+                    JSExportEntry *me = &m->export_entries[i];
+                    if (bc_get_u8(s, &v8))
+                        goto fail;
+                    me->export_type = v8;
+                    if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
+                        if (bc_get_leb128_int(s, &me->u.local.var_idx))
+                            goto fail;
+                    } else {
+                        if (bc_get_leb128_int(s, &me->u.req_module_idx))
+                            goto fail;
+                        if (bc_get_atom(s, &me->local_name))
+                            goto fail;
+                    }
+                    if (bc_get_atom(s, &me->export_name))
+                        goto fail;
+                }
+            }
+
+            if (bc_get_leb128_int(s, &m->star_export_entries_count))
+                goto fail;
+            if (m->star_export_entries_count != 0) {
+                m->star_export_entries_size = m->star_export_entries_count;
+                m->star_export_entries = js_mallocz(ctx, sizeof(m->star_export_entries[0]) * m->star_export_entries_size);
+                if (!m->star_export_entries)
+                    goto fail;
+                for(i = 0; i < m->star_export_entries_count; i++) {
+                    JSStarExportEntry *se = &m->star_export_entries[i];
+                    if (bc_get_leb128_int(s, &se->req_module_idx))
+                        goto fail;
+                }
+            }
+
+            if (bc_get_leb128_int(s, &m->import_entries_count))
+                goto fail;
+            if (m->import_entries_count != 0) {
+                m->import_entries_size = m->import_entries_count;
+                m->import_entries = js_mallocz(ctx, sizeof(m->import_entries[0]) * m->import_entries_size);
+                if (!m->import_entries)
+                    goto fail;
+                for(i = 0; i < m->import_entries_count; i++) {
+                    JSImportEntry *mi = &m->import_entries[i];
+                    if (bc_get_leb128_int(s, &mi->var_idx))
+                        goto fail;
+                    if (bc_get_atom(s, &mi->import_name))
+                        goto fail;
+                    if (bc_get_leb128_int(s, &mi->req_module_idx))
+                        goto fail;
+                }
+            }
+
+            m->func_obj = JS_ReadObjectRec(s);
+            if (JS_IsException(m->func_obj))
+                goto fail;
+            bc_read_trace(s, "}\n");
+        }
+        break;
+    case BC_TAG_OBJECT:
+        {
+            uint32_t prop_count, i;
+            JSAtom atom;
+            JSValue val;
+            int ret;
+
+            bc_read_trace(s, "%s {\n", bc_tag_str[tag]);
+
+            obj = JS_NewObject(ctx);
+            if (bc_get_leb128(s, &prop_count))
+                goto fail;
+            for(i = 0; i < prop_count; i++) {
+                if (bc_get_atom(s, &atom))
+                    goto fail;
+#ifdef DUMP_READ_OBJECT
+                bc_read_trace(s, "propname: "); print_atom(s->ctx, atom); printf("\n");
+#endif
+                val = JS_ReadObjectRec(s);
+                if (JS_IsException(val)) {
+                    JS_FreeAtom(ctx, atom);
+                    goto fail;
+                }
+                ret = JS_DefinePropertyValue(ctx, obj, atom, val, JS_PROP_C_W_E);
+                JS_FreeAtom(ctx, atom);
+                if (ret < 0)
+                    goto fail;
+            }
+            bc_read_trace(s, "}\n");
+        }
+        break;
+    case BC_TAG_ARRAY:
+    case BC_TAG_TEMPLATE_OBJECT:
+        {
+            uint32_t len, i;
+            JSValue val;
+            int ret, prop_flags;
+            BOOL is_template;
+
+            bc_read_trace(s, "%s {\n", bc_tag_str[tag]);
+
+            obj = JS_NewArray(ctx);
+            is_template = (tag == BC_TAG_TEMPLATE_OBJECT);
+            if (bc_get_leb128(s, &len))
+                goto fail;
+            for(i = 0; i < len; i++) {
+                val = JS_ReadObjectRec(s);
+                if (JS_IsException(val))
+                    goto fail;
+                if (is_template)
+                    prop_flags = JS_PROP_ENUMERABLE;
+                else
+                    prop_flags = JS_PROP_C_W_E;
+                ret = JS_DefinePropertyValueUint32(ctx, obj, i, val,
+                                                   prop_flags);
+                if (ret < 0)
+                    goto fail;
+            }
+            if (is_template) {
+                val = JS_ReadObjectRec(s);
+                if (JS_IsException(val))
+                    goto fail;
+                if (!JS_IsUndefined(val)) {
+                    ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_raw, val, 0);
+                    if (ret < 0)
+                        goto fail;
+                }
+                JS_PreventExtensions(ctx, obj);
+            }
+            bc_read_trace(s, "}\n");
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case BC_TAG_BIG_INT:
+    case BC_TAG_BIG_FLOAT:
+        {
+            uint8_t sign, v8;
+            int32_t e;
+            uint32_t len;
+            limb_t l, i, n;
+            JSBigFloat *p;
+            bf_t *a;
+
+            bc_read_trace(s, "%s {\n", bc_tag_str[tag]);
+
+            if (bc_get_leb128(s, &len))
+                goto fail;
+            sign = len & 1;
+            len >>= 1;
+            p = js_new_bf(s->ctx);
+            if (!p)
+                goto fail;
+            if (tag == BC_TAG_BIG_INT)
+                obj = JS_MKPTR(JS_TAG_BIG_INT, p);
+            else
+                obj = JS_MKPTR(JS_TAG_BIG_FLOAT, p);
+            l = (len + sizeof(limb_t) - 1) / sizeof(limb_t);
+            a = &p->num;
+            bf_resize(a, l);
+            a->sign = sign;
+            if (len == 0) {
+                if (tag == BC_TAG_BIG_INT) {
+                    a->expn = BF_EXP_ZERO;
+                } else {
+                    if (bc_get_u8(s, &v8))
+                        goto fail;
+                    switch(v8) {
+                    case 0:
+                        a->expn = BF_EXP_ZERO;
+                        break;
+                    case 1:
+                        a->expn = BF_EXP_INF;
+                        break;
+                    default:
+                    case 2:
+                        a->expn = BF_EXP_NAN;
+                        break;
+                    }
+                }
+            } else {
+                n = len & (sizeof(limb_t) - 1);
+                if (n != 0) {
+                    limb_t v;
+                    v = 0;
+                    for(i = 0; i < n; i++) {
+                        if (bc_get_u8(s, &v8))
+                            goto fail;
+                        v |= (limb_t)v8 << ((sizeof(limb_t) - n + i) * 8);
+                    }
+                    a->tab[0] = v;
+                    i = 1;
+                } else {
+                    i = 0;
+                }
+                for(; i < l; i++) {
+                    limb_t v;
+#if LIMB_BITS == 32
+                    if (bc_get_u32(s, &v))
+                        goto fail;
+#ifdef WORDS_BIGENDIAN
+                    v = bswap32(v);
+#endif
+#else
+                    if (bc_get_u64(s, &v))
+                        goto fail;
+#ifdef WORDS_BIGENDIAN
+                    v = bswap64(v);
+#endif
+#endif
+                    a->tab[i] = v;
+                }
+                if (bc_get_sleb128(s, &e))
+                    goto fail;
+                a->expn = e;
+            }
+            bc_read_trace(s, "}\n");
+        }
+        break;
+#endif
+    default:
+    invalid_tag:
+        return JS_ThrowSyntaxError(ctx, "invalid tag (tag=%d pos=%u)",
+                                   tag, (unsigned int)(s->ptr - s->buf_start));
+    }
+    return obj;
+ fail:
+    if (m) {
+        js_free_module_def(ctx, m);
+    }
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static int JS_ReadObjectAtoms(BCReaderState *s)
+{
+    uint8_t v8;
+    JSString *p;
+    int i;
+    JSAtom atom;
+
+    if (bc_get_u8(s, &v8))
+        return -1;
+    /* XXX: could support byte swapped input */
+    if (v8 != BC_VERSION) {
+        JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)",
+                            v8, BC_VERSION);
+        return -1;
+    }
+    if (bc_get_leb128(s, &s->idx_to_atom_count))
+        return -1;
+
+    bc_read_trace(s, "%d atom indexes {\n", s->idx_to_atom_count);
+
+    if (s->idx_to_atom_count != 0) {
+        s->idx_to_atom = js_mallocz(s->ctx, s->idx_to_atom_count *
+                                    sizeof(s->idx_to_atom[0]));
+        if (!s->idx_to_atom)
+            return s->error_state = -1;
+    }
+    for(i = 0; i < s->idx_to_atom_count; i++) {
+        p = JS_ReadString(s);
+        if (!p)
+            return -1;
+        atom = JS_NewAtomStr(s->ctx, p);
+        if (atom == JS_ATOM_NULL)
+            return s->error_state = -1;
+        s->idx_to_atom[i] = atom;
+        if (s->is_rom_data && (atom != (i + s->first_atom)))
+            s->is_rom_data = FALSE; /* atoms must be relocated */
+    }
+    bc_read_trace(s, "}\n");
+    return 0;
+}
+
+static void bc_reader_free(BCReaderState *s)
+{
+    int i;
+    if (s->idx_to_atom) {
+        for(i = 0; i < s->idx_to_atom_count; i++) {
+            JS_FreeAtom(s->ctx, s->idx_to_atom[i]);
+        }
+        js_free(s->ctx, s->idx_to_atom);
+    }
+}
+
+JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len,
+                      int flags)
+{
+    BCReaderState ss, *s = &ss;
+    JSValue obj;
+
+    ctx->binary_object_count += 1;
+    ctx->binary_object_size += buf_len;
+
+    memset(s, 0, sizeof(*s));
+    s->ctx = ctx;
+    s->buf_start = buf;
+    s->buf_end = buf + buf_len;
+    s->ptr = buf;
+    s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0);
+    s->is_rom_data = ((flags & JS_READ_OBJ_ROM_DATA) != 0);
+    if (s->allow_bytecode)
+        s->first_atom = JS_ATOM_END;
+    else
+        s->first_atom = 1;
+    if (JS_ReadObjectAtoms(s)) {
+        obj = JS_EXCEPTION;
+    } else {
+        obj = JS_ReadObjectRec(s);
+    }
+    bc_reader_free(s);
+    return obj;
+}
+
+/*******************************************************************/
+/* runtime functions & objects */
+
+static JSValue js_string_constructor(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv);
+static JSValue js_boolean_constructor(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv);
+static JSValue js_number_constructor(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv);
+
+static int check_function(JSContext *ctx, JSValueConst obj)
+{
+    if (likely(JS_IsFunction(ctx, obj)))
+        return 0;
+    JS_ThrowTypeError(ctx, "not a function");
+    return -1;
+}
+
+static int check_exception_free(JSContext *ctx, JSValue obj)
+{
+    JS_FreeValue(ctx, obj);
+    return JS_IsException(obj);
+}
+
+static JSAtom find_atom(JSContext *ctx, const char *name)
+{
+    JSAtom atom;
+    int len;
+
+    if (*name == '[') {
+        name++;
+        len = strlen(name) - 1;
+        /* We assume 8 bit non null strings, which is the case for these
+           symbols */
+        for(atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) {
+            JSAtomStruct *p = ctx->rt->atom_array[atom];
+            JSString *str = p;
+            if (str->len == len && !memcmp(str->u.str8, name, len))
+                return JS_DupAtom(ctx, atom);
+        }
+        abort();
+    } else {
+        atom = JS_NewAtom(ctx, name);
+    }
+    return atom;
+}
+
+static int JS_InstantiateFunctionListItem(JSContext *ctx, JSObject *p,
+                                          JSAtom atom, void *opaque)
+{
+    const JSCFunctionListEntry *e = opaque;
+    JSValueConst obj = JS_MKPTR(JS_TAG_OBJECT, p);
+    JSValue val;
+    int prop_flags = e->prop_flags;
+
+    switch(e->def_type) {
+    case JS_DEF_ALIAS:
+        {
+            JSAtom atom1 = find_atom(ctx, e->u.alias.name);
+            switch (e->u.alias.base) {
+            case -1:
+                val = JS_GetProperty(ctx, obj, atom1);
+                break;
+            case 0:
+                val = JS_GetProperty(ctx, ctx->global_obj, atom1);
+                break;
+            case 1:
+                val = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], atom1);
+                break;
+            default:
+                abort();
+            }
+            JS_FreeAtom(ctx, atom1);
+            goto setval;
+        }
+    case JS_DEF_CFUNC:
+        val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic,
+                               e->name, e->u.func.length, e->u.func.cproto, e->magic);
+    setval:
+        if (atom == JS_ATOM_Symbol_toPrimitive) {
+            /* Symbol.toPrimitive functions are not writable */
+            prop_flags = JS_PROP_CONFIGURABLE;
+        } else if (atom == JS_ATOM_Symbol_hasInstance) {
+            /* Function.prototype[Symbol.hasInstance] is not writable nor configurable */
+            prop_flags = 0;
+        }
+        break;
+    case JS_DEF_CGETSET:
+    case JS_DEF_CGETSET_MAGIC:
+        {
+            JSValue getter, setter;
+            char buf[64];
+
+            getter = JS_UNDEFINED;
+            if (e->u.getset.get.generic) {
+                snprintf(buf, sizeof(buf), "get %s", e->name);
+                getter = JS_NewCFunction2(ctx, e->u.getset.get.generic,
+                                          buf, 0, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_getter_magic : JS_CFUNC_getter,
+                                          e->magic);
+            }
+            setter = JS_UNDEFINED;
+            if (e->u.getset.set.generic) {
+                snprintf(buf, sizeof(buf), "set %s", e->name);
+                setter = JS_NewCFunction2(ctx, e->u.getset.set.generic,
+                                          buf, 1, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_setter_magic : JS_CFUNC_setter,
+                                          e->magic);
+            }
+            JS_DefinePropertyGetSet(ctx, obj, atom, getter, setter, prop_flags);
+            return 0;
+        }
+        break;
+    case JS_DEF_PROP_STRING:
+        val = JS_NewAtomString(ctx, e->u.str);
+        break;
+    case JS_DEF_PROP_INT32:
+        val = JS_NewInt32(ctx, e->u.i32);
+        break;
+    case JS_DEF_PROP_INT64:
+        val = JS_NewInt64(ctx, e->u.i64);
+        break;
+    case JS_DEF_PROP_DOUBLE:
+        val = __JS_NewFloat64(ctx, e->u.f64);
+        break;
+    case JS_DEF_PROP_UNDEFINED:
+        val = JS_UNDEFINED;
+        break;
+    case JS_DEF_OBJECT:
+        val = JS_NewObject(ctx);
+        JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len);
+        break;
+    default:
+        abort();
+    }
+    JS_DefinePropertyValue(ctx, obj, atom, val, prop_flags);
+    return 0;
+}
+
+void JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj,
+                                const JSCFunctionListEntry *tab, int len)
+{
+    int i, prop_flags;
+
+    for (i = 0; i < len; i++) {
+        const JSCFunctionListEntry *e = &tab[i];
+        JSAtom atom = find_atom(ctx, e->name);
+
+        switch (e->def_type) {
+        case JS_DEF_CFUNC:
+        case JS_DEF_CGETSET:
+        case JS_DEF_CGETSET_MAGIC:
+        case JS_DEF_PROP_STRING:
+        case JS_DEF_ALIAS:
+        case JS_DEF_OBJECT:
+            prop_flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE | (e->prop_flags & JS_PROP_ENUMERABLE);
+            JS_DefineAutoInitProperty(ctx, obj, atom,
+                                      JS_InstantiateFunctionListItem,
+                                      (void *)e, prop_flags);
+            break;
+        case JS_DEF_PROP_INT32:
+        case JS_DEF_PROP_INT64:
+        case JS_DEF_PROP_DOUBLE:
+        case JS_DEF_PROP_UNDEFINED:
+            {
+                JSObject *p = JS_VALUE_GET_OBJ(obj);
+                JS_InstantiateFunctionListItem(ctx, p, atom, (void *)e);
+            }
+            break;
+        default:
+            abort();
+        }
+        JS_FreeAtom(ctx, atom);
+    }
+}
+
+int JS_AddModuleExportList(JSContext *ctx, JSModuleDef *m,
+                           const JSCFunctionListEntry *tab, int len)
+{
+    int i;
+    for(i = 0; i < len; i++) {
+        if (JS_AddModuleExport(ctx, m, tab[i].name))
+            return -1;
+    }
+    return 0;
+}
+
+int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
+                           const JSCFunctionListEntry *tab, int len)
+{
+    int i;
+    JSValue val;
+
+    for(i = 0; i < len; i++) {
+        const JSCFunctionListEntry *e = &tab[i];
+        switch(e->def_type) {
+        case JS_DEF_CFUNC:
+            val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic,
+                                   e->name, e->u.func.length, e->u.func.cproto, e->magic);
+            break;
+        case JS_DEF_PROP_STRING:
+            val = JS_NewString(ctx, e->u.str);
+            break;
+        case JS_DEF_PROP_INT32:
+            val = JS_NewInt32(ctx, e->u.i32);
+            break;
+        case JS_DEF_PROP_INT64:
+            val = JS_NewInt64(ctx, e->u.i64);
+            break;
+        case JS_DEF_PROP_DOUBLE:
+            val = __JS_NewFloat64(ctx, e->u.f64);
+            break;
+        default:
+            abort();
+        }
+        if (JS_SetModuleExport(ctx, m, e->name, val))
+            return -1;
+    }
+    return 0;
+}
+
+/* Note: 'func_obj' is not necessarily a constructor */
+static void JS_SetConstructor2(JSContext *ctx,
+                               JSValueConst func_obj,
+                               JSValueConst proto,
+                               int proto_flags, int ctor_flags)
+{
+    JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype,
+                           JS_DupValue(ctx, proto), proto_flags);
+    JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor,
+                           JS_DupValue(ctx, func_obj),
+                           ctor_flags);
+    set_cycle_flag(ctx, func_obj);
+    set_cycle_flag(ctx, proto);
+}
+
+static void JS_SetConstructor(JSContext *ctx,
+                              JSValueConst func_obj,
+                              JSValueConst proto)
+{
+    JS_SetConstructor2(ctx, func_obj, proto,
+                       0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+}
+
+static void JS_NewGlobalCConstructor2(JSContext *ctx,
+                                      JSValue func_obj,
+                                      const char *name,
+                                      JSValueConst proto)
+{
+    JS_DefinePropertyValueStr(ctx, ctx->global_obj, name,
+                           JS_DupValue(ctx, func_obj),
+                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    JS_SetConstructor(ctx, func_obj, proto);
+    JS_FreeValue(ctx, func_obj);
+}
+
+static JSValueConst JS_NewGlobalCConstructor(JSContext *ctx, const char *name,
+                                             JSCFunction *func, int length,
+                                             JSValueConst proto)
+{
+    JSValue func_obj;
+    func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor_or_func, 0);
+    JS_NewGlobalCConstructor2(ctx, func_obj, name, proto);
+    return func_obj;
+}
+
+static JSValueConst JS_NewGlobalCConstructorOnly(JSContext *ctx, const char *name,
+                                                 JSCFunction *func, int length,
+                                                 JSValueConst proto)
+{
+    JSValue func_obj;
+    func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor, 0);
+    JS_NewGlobalCConstructor2(ctx, func_obj, name, proto);
+    return func_obj;
+}
+
+static JSValue js_global_eval(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    return JS_EvalObject(ctx, ctx->global_obj, argv[0], JS_EVAL_TYPE_INDIRECT, -1);
+}
+
+static JSValue js_global_isNaN(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    double d;
+
+    /* XXX: does this work for bigfloat? */
+    if (unlikely(JS_ToFloat64(ctx, &d, argv[0])))
+        return JS_EXCEPTION;
+    return JS_NewBool(ctx, isnan(d));
+}
+
+#ifdef CONFIG_BIGNUM
+static BOOL js_number_is_finite(JSContext *ctx, JSValueConst val)
+{
+    BOOL res;
+    uint32_t tag;
+
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_FLOAT64:
+        res = isfinite(JS_VALUE_GET_FLOAT64(val));
+        break;
+    case JS_TAG_BIG_FLOAT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            res = bf_is_finite(&p->num);
+        }
+        break;
+    default:
+        res = TRUE;
+        break;
+    }
+    return res;
+}
+#endif
+
+static JSValue js_global_isFinite(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    BOOL res;
+#ifdef CONFIG_BIGNUM
+    JSValue val;
+
+    val = JS_ToNumber(ctx, argv[0]);
+    if (JS_IsException(val))
+        return val;
+    res = js_number_is_finite(ctx, val);
+    JS_FreeValue(ctx, val);
+#else
+    double d;
+    if (unlikely(JS_ToFloat64(ctx, &d, argv[0])))
+        return JS_EXCEPTION;
+    res = isfinite(d);
+#endif
+    return JS_NewBool(ctx, res);
+}
+
+/* Object class */
+
+static JSValue JS_ToObject(JSContext *ctx, JSValueConst val)
+{
+    int tag = JS_VALUE_GET_NORM_TAG(val);
+    JSValue obj;
+
+    switch(tag) {
+    default:
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+        return JS_ThrowTypeError(ctx, "cannot convert to object");
+    case JS_TAG_OBJECT:
+    case JS_TAG_EXCEPTION:
+        return JS_DupValue(ctx, val);
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_INT:
+        if (is_bignum_mode(ctx))
+            obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_INT);
+        else
+            obj = JS_NewObjectClass(ctx, JS_CLASS_NUMBER);
+        goto set_value;
+    case JS_TAG_BIG_INT:
+        obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_INT);
+        goto set_value;
+    case JS_TAG_FLOAT64:
+        obj = JS_NewObjectClass(ctx, JS_CLASS_NUMBER);
+        goto set_value;
+    case JS_TAG_BIG_FLOAT:
+        obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_FLOAT);
+        goto set_value;
+#else
+    case JS_TAG_INT:
+    case JS_TAG_FLOAT64:
+        obj = JS_NewObjectClass(ctx, JS_CLASS_NUMBER);
+        goto set_value;
+#endif
+    case JS_TAG_STRING:
+        /* XXX: should call the string constructor */
+        {
+            JSString *p1 = JS_VALUE_GET_STRING(val);
+            obj = JS_NewObjectClass(ctx, JS_CLASS_STRING);
+            JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, JS_NewInt32(ctx, p1->len), 0);
+        }
+        goto set_value;
+    case JS_TAG_BOOL:
+        obj = JS_NewObjectClass(ctx, JS_CLASS_BOOLEAN);
+        goto set_value;
+    case JS_TAG_SYMBOL:
+        obj = JS_NewObjectClass(ctx, JS_CLASS_SYMBOL);
+    set_value:
+        if (!JS_IsException(obj))
+            JS_SetObjectData(ctx, obj, JS_DupValue(ctx, val));
+        return obj;
+    }
+}
+
+static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val)
+{
+    JSValue obj = JS_ToObject(ctx, val);
+    JS_FreeValue(ctx, val);
+    return obj;
+}
+
+static int js_obj_to_desc(JSContext *ctx, JSPropertyDescriptor *d,
+                          JSValueConst desc)
+{
+    JSValue val, getter, setter;
+    int flags;
+
+    if (!JS_IsObject(desc)) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    flags = 0;
+    val = JS_UNDEFINED;
+    getter = JS_UNDEFINED;
+    setter = JS_UNDEFINED;
+    if (JS_HasProperty(ctx, desc, JS_ATOM_configurable)) {
+        JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_configurable);
+        if (JS_IsException(prop))
+            goto fail;
+        flags |= JS_PROP_HAS_CONFIGURABLE;
+        if (JS_ToBoolFree(ctx, prop))
+            flags |= JS_PROP_CONFIGURABLE;
+    }
+    if (JS_HasProperty(ctx, desc, JS_ATOM_writable)) {
+        JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_writable);
+        if (JS_IsException(prop))
+            goto fail;
+        flags |= JS_PROP_HAS_WRITABLE;
+        if (JS_ToBoolFree(ctx, prop))
+            flags |= JS_PROP_WRITABLE;
+    }
+    if (JS_HasProperty(ctx, desc, JS_ATOM_enumerable)) {
+        JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_enumerable);
+        if (JS_IsException(prop))
+            goto fail;
+        flags |= JS_PROP_HAS_ENUMERABLE;
+        if (JS_ToBoolFree(ctx, prop))
+            flags |= JS_PROP_ENUMERABLE;
+    }
+    if (JS_HasProperty(ctx, desc, JS_ATOM_value)) {
+        flags |= JS_PROP_HAS_VALUE;
+        val = JS_GetProperty(ctx, desc, JS_ATOM_value);
+        if (JS_IsException(val))
+            goto fail;
+    }
+    if (JS_HasProperty(ctx, desc, JS_ATOM_get)) {
+        flags |= JS_PROP_HAS_GET;
+        getter = JS_GetProperty(ctx, desc, JS_ATOM_get);
+        if (JS_IsException(getter) ||
+            !(JS_IsUndefined(getter) || JS_IsFunction(ctx, getter))) {
+            JS_ThrowTypeError(ctx, "invalid getter");
+            goto fail;
+        }
+    }
+    if (JS_HasProperty(ctx, desc, JS_ATOM_set)) {
+        flags |= JS_PROP_HAS_SET;
+        setter = JS_GetProperty(ctx, desc, JS_ATOM_set);
+        if (JS_IsException(setter) ||
+            !(JS_IsUndefined(setter) || JS_IsFunction(ctx, setter))) {
+            JS_ThrowTypeError(ctx, "invalid setter");
+            goto fail;
+        }
+    }
+    if ((flags & (JS_PROP_HAS_SET | JS_PROP_HAS_GET)) &&
+        (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE))) {
+        JS_ThrowTypeError(ctx, "cannot have setter/getter and value or writable");
+        goto fail;
+    }
+    d->flags = flags;
+    d->value = val;
+    d->getter = getter;
+    d->setter = setter;
+    return 0;
+ fail:
+    JS_FreeValue(ctx, val);
+    JS_FreeValue(ctx, getter);
+    JS_FreeValue(ctx, setter);
+    return -1;
+}
+
+static __exception int JS_DefinePropertyDesc(JSContext *ctx, JSValueConst obj,
+                                             JSAtom prop, JSValueConst desc,
+                                             int flags)
+{
+    JSPropertyDescriptor d;
+    int ret;
+
+    if (js_obj_to_desc(ctx, &d, desc) < 0)
+        return -1;
+
+    ret = JS_DefineProperty(ctx, obj, prop,
+                            d.value, d.getter, d.setter, d.flags | flags);
+    js_free_desc(ctx, &d);
+    return ret;
+}
+
+static __exception int JS_ObjectDefineProperties(JSContext *ctx,
+                                                 JSValueConst obj,
+                                                 JSValueConst properties)
+{
+    JSValue props, desc;
+    JSObject *p;
+    JSPropertyEnum *atoms;
+    uint32_t len, i;
+    int ret = -1;
+
+    if (!JS_IsObject(obj)) {
+        JS_ThrowTypeErrorNotAnObject(ctx);
+        return -1;
+    }
+    desc = JS_UNDEFINED;
+    props = JS_ToObject(ctx, properties);
+    if (JS_IsException(props))
+        return -1;
+    p = JS_VALUE_GET_OBJ(props);
+    if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0)
+        goto exception;
+    for(i = 0; i < len; i++) {
+        JS_FreeValue(ctx, desc);
+        desc = JS_GetProperty(ctx, props, atoms[i].atom);
+        if (JS_IsException(desc))
+            goto exception;
+        if (JS_DefinePropertyDesc(ctx, obj, atoms[i].atom, desc, JS_PROP_THROW) < 0)
+            goto exception;
+    }
+    ret = 0;
+
+exception:
+    js_free_prop_enum(ctx, atoms, len);
+    JS_FreeValue(ctx, props);
+    JS_FreeValue(ctx, desc);
+    return ret;
+}
+
+static JSValue js_object_constructor(JSContext *ctx, JSValueConst new_target,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue ret;
+    if (!JS_IsUndefined(new_target) &&
+        JS_VALUE_GET_OBJ(new_target) !=
+        JS_VALUE_GET_OBJ(JS_GetActiveFunction(ctx))) {
+        ret = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT);
+    } else {
+        int tag = JS_VALUE_GET_NORM_TAG(argv[0]);
+        switch(tag) {
+        case JS_TAG_NULL:
+        case JS_TAG_UNDEFINED:
+            ret = JS_NewObject(ctx);
+            break;
+        default:
+            ret = JS_ToObject(ctx, argv[0]);
+            break;
+        }
+    }
+    return ret;
+}
+
+static JSValue js_object_create(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValueConst proto, props;
+    JSValue obj;
+
+    proto = argv[0];
+    if (!JS_IsObject(proto) && !JS_IsNull(proto))
+        return JS_ThrowTypeError(ctx, "not a prototype");
+    obj = JS_NewObjectProto(ctx, proto);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    props = argv[1];
+    if (!JS_IsUndefined(props)) {
+        if (JS_ObjectDefineProperties(ctx, obj, props)) {
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+    }
+    return obj;
+}
+
+static JSValue js_object_getPrototypeOf(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv, int magic)
+{
+    JSValueConst val;
+
+    val = argv[0];
+    if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) {
+        /* ES6 feature non compatible with ES5.1: primitive types are
+           accepted */
+        if (magic || JS_VALUE_GET_TAG(val) == JS_TAG_NULL ||
+            JS_VALUE_GET_TAG(val) == JS_TAG_UNDEFINED)
+            return JS_ThrowTypeErrorNotAnObject(ctx);
+    }
+    return JS_DupValue(ctx, JS_GetPrototype(ctx, val));
+}
+
+static JSValue js_object_setPrototypeOf(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    JSValueConst obj;
+    obj = argv[0];
+    if (JS_SetPrototypeInternal(ctx, obj, argv[1], TRUE) < 0)
+        return JS_EXCEPTION;
+    return JS_DupValue(ctx, obj);
+}
+
+/* magic = 1 if called as Reflect.defineProperty */
+static JSValue js_object_defineProperty(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv, int magic)
+{
+    JSValueConst obj, prop, desc;
+    int ret, flags;
+    JSAtom atom;
+
+    obj = argv[0];
+    prop = argv[1];
+    desc = argv[2];
+
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    atom = JS_ValueToAtom(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return JS_EXCEPTION;
+    flags = 0;
+    if (!magic)
+        flags |= JS_PROP_THROW;
+    ret = JS_DefinePropertyDesc(ctx, obj, atom, desc, flags);
+    JS_FreeAtom(ctx, atom);
+    if (ret < 0) {
+        return JS_EXCEPTION;
+    } else if (magic) {
+        return JS_NewBool(ctx, ret);
+    } else {
+        return JS_DupValue(ctx, obj);
+    }
+}
+
+static JSValue js_object_defineProperties(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv)
+{
+    // defineProperties(obj, properties)
+    JSValueConst obj = argv[0];
+
+    if (JS_ObjectDefineProperties(ctx, obj, argv[1]))
+        return JS_EXCEPTION;
+    else
+        return JS_DupValue(ctx, obj);
+}
+
+/* magic = 1 if called as __defineSetter__ */
+static JSValue js_object___defineGetter__(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv, int magic)
+{
+    JSValue obj;
+    JSValueConst prop, value, get, set;
+    int ret, flags;
+    JSAtom atom;
+
+    prop = argv[0];
+    value = argv[1];
+
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+
+    if (check_function(ctx, value)) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    atom = JS_ValueToAtom(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL)) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    flags = JS_PROP_THROW |
+        JS_PROP_HAS_ENUMERABLE | JS_PROP_ENUMERABLE |
+        JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE;
+    if (magic) {
+        get = JS_UNDEFINED;
+        set = value;
+        flags |= JS_PROP_HAS_SET;
+    } else {
+        get = value;
+        set = JS_UNDEFINED;
+        flags |= JS_PROP_HAS_GET;
+    }
+    ret = JS_DefineProperty(ctx, obj, atom, JS_UNDEFINED, get, set, flags);
+    JS_FreeValue(ctx, obj);
+    JS_FreeAtom(ctx, atom);
+    if (ret < 0) {
+        return JS_EXCEPTION;
+    } else {
+        return JS_UNDEFINED;
+    }
+}
+
+static JSValue js_object_getOwnPropertyDescriptor(JSContext *ctx, JSValueConst this_val,
+                                                  int argc, JSValueConst *argv, int magic)
+{
+    JSValueConst prop;
+    JSAtom atom;
+    JSValue ret, obj;
+    JSPropertyDescriptor desc;
+    int res, flags;
+
+    if (magic) {
+        /* Reflect.getOwnPropertyDescriptor case */
+        if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT)
+            return JS_ThrowTypeErrorNotAnObject(ctx);
+        obj = JS_DupValue(ctx, argv[0]);
+    } else {
+        obj = JS_ToObject(ctx, argv[0]);
+        if (JS_IsException(obj))
+            return obj;
+    }
+    prop = argv[1];
+    atom = JS_ValueToAtom(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL))
+        goto exception;
+    ret = JS_UNDEFINED;
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), atom);
+        if (res < 0)
+            goto exception;
+        if (res) {
+            ret = JS_NewObject(ctx);
+            if (JS_IsException(ret))
+                goto exception1;
+            flags = JS_PROP_C_W_E | JS_PROP_THROW;
+            if (desc.flags & JS_PROP_GETSET) {
+                if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, JS_DupValue(ctx, desc.getter), flags) < 0
+                ||  JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, JS_DupValue(ctx, desc.setter), flags) < 0)
+                    goto exception1;
+            } else {
+                if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, JS_DupValue(ctx, desc.value), flags) < 0
+                ||  JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable,
+                                           JS_NewBool(ctx, (desc.flags & JS_PROP_WRITABLE) != 0), flags) < 0)
+                    goto exception1;
+            }
+            if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable,
+                                       JS_NewBool(ctx, (desc.flags & JS_PROP_ENUMERABLE) != 0), flags) < 0
+            ||  JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable,
+                                       JS_NewBool(ctx, (desc.flags & JS_PROP_CONFIGURABLE) != 0), flags) < 0)
+                goto exception1;
+            js_free_desc(ctx, &desc);
+        }
+    }
+    JS_FreeAtom(ctx, atom);
+    JS_FreeValue(ctx, obj);
+    return ret;
+
+exception1:
+    js_free_desc(ctx, &desc);
+    JS_FreeValue(ctx, ret);
+exception:
+    JS_FreeAtom(ctx, atom);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_object_getOwnPropertyDescriptors(JSContext *ctx, JSValueConst this_val,
+                                                   int argc, JSValueConst *argv)
+{
+    //getOwnPropertyDescriptors(obj)
+    JSValue obj, r;
+    JSObject *p;
+    JSPropertyEnum *props;
+    uint32_t len, i;
+
+    r = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, argv[0]);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+
+    p = JS_VALUE_GET_OBJ(obj);
+    if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p,
+                               JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK))
+        goto exception;
+    r = JS_NewObject(ctx);
+    if (JS_IsException(r))
+        goto exception;
+    for(i = 0; i < len; i++) {
+        JSValue atomValue, desc;
+        JSValueConst args[2];
+
+        atomValue = JS_AtomToValue(ctx, props[i].atom);
+        if (JS_IsException(atomValue))
+            goto exception;
+        args[0] = obj;
+        args[1] = atomValue;
+        desc = js_object_getOwnPropertyDescriptor(ctx, JS_UNDEFINED, 2, args, 0);
+        JS_FreeValue(ctx, atomValue);
+        if (JS_IsException(desc))
+            goto exception;
+        if (!JS_IsUndefined(desc)) {
+            if (JS_DefinePropertyValue(ctx, r, props[i].atom, desc,
+                                       JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                goto exception;
+        }
+    }
+    js_free_prop_enum(ctx, props, len);
+    JS_FreeValue(ctx, obj);
+    return r;
+
+exception:
+    js_free_prop_enum(ctx, props, len);
+    JS_FreeValue(ctx, obj);
+    JS_FreeValue(ctx, r);
+    return JS_EXCEPTION;
+}
+
+static JSValue JS_GetOwnPropertyNames2(JSContext *ctx, JSValueConst obj1,
+                                       int flags, int kind)
+{
+    JSValue obj, r, val, key, value;
+    JSObject *p;
+    JSPropertyEnum *atoms;
+    uint32_t len, i, j;
+
+    r = JS_UNDEFINED;
+    val = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, obj1);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    p = JS_VALUE_GET_OBJ(obj);
+    if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, flags & ~JS_GPN_ENUM_ONLY))
+        goto exception;
+    r = JS_NewArray(ctx);
+    if (JS_IsException(r))
+        goto exception;
+    for(j = i = 0; i < len; i++) {
+        JSAtom atom = atoms[i].atom;
+        if (flags & JS_GPN_ENUM_ONLY) {
+            JSPropertyDescriptor desc;
+            int res;
+
+            /* Check if property is still enumerable */
+            res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
+            if (res < 0)
+                goto exception;
+            if (!res)
+                continue;
+            js_free_desc(ctx, &desc);
+            if (!(desc.flags & JS_PROP_ENUMERABLE))
+                continue;
+        }
+        switch(kind) {
+        default:
+        case JS_ITERATOR_KIND_KEY:
+            val = JS_AtomToValue(ctx, atom);
+            if (JS_IsException(val))
+                goto exception;
+            break;
+        case JS_ITERATOR_KIND_VALUE:
+            val = JS_GetProperty(ctx, obj, atom);
+            if (JS_IsException(val))
+                goto exception;
+            break;
+        case JS_ITERATOR_KIND_KEY_AND_VALUE:
+            val = JS_NewArray(ctx);
+            if (JS_IsException(val))
+                goto exception;
+            key = JS_AtomToValue(ctx, atom);
+            if (JS_IsException(key))
+                goto exception1;
+            if (JS_CreateDataPropertyUint32(ctx, val, 0, key, JS_PROP_THROW) < 0)
+                goto exception1;
+            value = JS_GetProperty(ctx, obj, atom);
+            if (JS_IsException(value))
+                goto exception1;
+            if (JS_CreateDataPropertyUint32(ctx, val, 1, value, JS_PROP_THROW) < 0)
+                goto exception1;
+            break;
+        }
+        if (JS_CreateDataPropertyUint32(ctx, r, j++, val, 0) < 0)
+            goto exception;
+    }
+    goto done;
+
+exception1:
+    JS_FreeValue(ctx, val);
+exception:
+    JS_FreeValue(ctx, r);
+    r = JS_EXCEPTION;
+done:
+    js_free_prop_enum(ctx, atoms, len);
+    JS_FreeValue(ctx, obj);
+    return r;
+}
+
+static JSValue js_object_getOwnPropertyNames(JSContext *ctx, JSValueConst this_val,
+                                             int argc, JSValueConst *argv)
+{
+    return JS_GetOwnPropertyNames2(ctx, argv[0],
+                                   JS_GPN_STRING_MASK, JS_ITERATOR_KIND_KEY);
+}
+
+static JSValue js_object_getOwnPropertySymbols(JSContext *ctx, JSValueConst this_val,
+                                             int argc, JSValueConst *argv)
+{
+    return JS_GetOwnPropertyNames2(ctx, argv[0],
+                                   JS_GPN_SYMBOL_MASK, JS_ITERATOR_KIND_KEY);
+}
+
+static JSValue js_object_keys(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int kind)
+{
+    return JS_GetOwnPropertyNames2(ctx, argv[0],
+                                   JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK, kind);
+}
+
+static JSValue js_object_isExtensible(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv, int reflect)
+{
+    JSValueConst obj;
+    int ret;
+
+    obj = argv[0];
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
+        if (reflect)
+            return JS_ThrowTypeErrorNotAnObject(ctx);
+        else
+            return JS_FALSE;
+    }
+    ret = JS_IsExtensible(ctx, obj);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_object_preventExtensions(JSContext *ctx, JSValueConst this_val,
+                                           int argc, JSValueConst *argv, int reflect)
+{
+    JSValueConst obj;
+    int ret;
+
+    obj = argv[0];
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
+        if (reflect)
+            return JS_ThrowTypeErrorNotAnObject(ctx);
+        else
+            return JS_DupValue(ctx, obj);
+    }
+    ret = JS_PreventExtensions(ctx, obj);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    if (reflect)
+        return JS_NewBool(ctx, ret);
+    else
+        return JS_DupValue(ctx, obj);
+}
+
+static JSValue js_object_hasOwnProperty(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    JSAtom atom;
+    JSObject *p;
+    BOOL ret;
+
+    atom = JS_ValueToAtom(ctx, argv[0]); /* must be done first */
+    if (unlikely(atom == JS_ATOM_NULL))
+        return JS_EXCEPTION;
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj)) {
+        JS_FreeAtom(ctx, atom);
+        return obj;
+    }
+    p = JS_VALUE_GET_OBJ(obj);
+    ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom);
+    JS_FreeAtom(ctx, atom);
+    JS_FreeValue(ctx, obj);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_object_valueOf(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    return JS_ToObject(ctx, this_val);
+}
+
+static JSValue js_object_toString(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValue obj, tag;
+    int is_array;
+    JSAtom atom;
+    JSObject *p;
+
+    if (JS_IsNull(this_val)) {
+        tag = JS_NewString(ctx, "Null");
+    } else if (JS_IsUndefined(this_val)) {
+        tag = JS_NewString(ctx, "Undefined");
+    } else {
+        obj = JS_ToObject(ctx, this_val);
+        if (JS_IsException(obj))
+            return obj;
+        is_array = JS_IsArray(ctx, obj);
+        if (is_array < 0) {
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+        if (is_array) {
+            atom = JS_ATOM_Array;
+        } else if (JS_IsFunction(ctx, obj)) {
+            atom = JS_ATOM_Function;
+        } else {
+            p = JS_VALUE_GET_OBJ(obj);
+            switch(p->class_id) {
+            case JS_CLASS_STRING:
+            case JS_CLASS_ARGUMENTS:
+            case JS_CLASS_MAPPED_ARGUMENTS:
+            case JS_CLASS_ERROR:
+            case JS_CLASS_BOOLEAN:
+            case JS_CLASS_NUMBER:
+            case JS_CLASS_DATE:
+            case JS_CLASS_REGEXP:
+                atom = ctx->rt->class_array[p->class_id].class_name;
+                break;
+            default:
+                atom = JS_ATOM_Object;
+                break;
+            }
+        }
+        tag = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_toStringTag);
+        JS_FreeValue(ctx, obj);
+        if (JS_IsException(tag))
+            return JS_EXCEPTION;
+        if (!JS_IsString(tag)) {
+            JS_FreeValue(ctx, tag);
+            tag = JS_AtomToString(ctx, atom);
+        }
+    }
+    return JS_ConcatString3(ctx, "[object ", tag, "]");
+}
+
+static JSValue js_object_toLocaleString(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    return JS_Invoke(ctx, this_val, JS_ATOM_toString, 0, NULL);
+}
+
+static JSValue js_object_assign(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    // Object.assign(obj, source1)
+    JSValue obj, s;
+    int i;
+
+    s = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, argv[0]);
+    if (JS_IsException(obj))
+        goto exception;
+    for (i = 1; i < argc; i++) {
+        if (!JS_IsNull(argv[i]) && !JS_IsUndefined(argv[i])) {
+            s = JS_ToObject(ctx, argv[i]);
+            if (JS_IsException(s))
+                goto exception;
+            if (JS_CopyDataProperties(ctx, obj, s, JS_UNDEFINED, TRUE))
+                goto exception;
+            JS_FreeValue(ctx, s);
+        }
+    }
+    return obj;
+exception:
+    JS_FreeValue(ctx, obj);
+    JS_FreeValue(ctx, s);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_object_seal(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int freeze_flag)
+{
+    JSValueConst obj = argv[0];
+    JSObject *p;
+    JSPropertyEnum *props;
+    uint32_t len, i;
+    int flags, desc_flags, res;
+
+    if (!JS_IsObject(obj))
+        return JS_DupValue(ctx, obj);
+
+    p = JS_VALUE_GET_OBJ(obj);
+    flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK;
+    if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags))
+        return JS_EXCEPTION;
+
+    for(i = 0; i < len; i++) {
+        JSPropertyDescriptor desc;
+        JSAtom prop = props[i].atom;
+
+        desc_flags = JS_PROP_THROW | JS_PROP_HAS_CONFIGURABLE;
+        if (freeze_flag) {
+            res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
+            if (res < 0)
+                goto exception;
+            if (res) {
+                if (desc.flags & JS_PROP_WRITABLE)
+                    desc_flags |= JS_PROP_HAS_WRITABLE;
+                js_free_desc(ctx, &desc);
+            }
+        }
+        if (JS_DefineProperty(ctx, obj, prop, JS_UNDEFINED,
+                              JS_UNDEFINED, JS_UNDEFINED, desc_flags) < 0)
+            goto exception;
+    }
+    js_free_prop_enum(ctx, props, len);
+    return js_object_preventExtensions(ctx, JS_UNDEFINED, 1, argv, 0);
+
+ exception:
+    js_free_prop_enum(ctx, props, len);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_object_isSealed(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv, int is_frozen)
+{
+    JSValueConst obj = argv[0];
+    JSObject *p;
+    JSPropertyEnum *props;
+    uint32_t len, i;
+    int flags, res;
+    
+    if (!JS_IsObject(obj))
+        return JS_TRUE;
+
+    p = JS_VALUE_GET_OBJ(obj);
+    flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK;
+    if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags))
+        return JS_EXCEPTION;
+
+    for(i = 0; i < len; i++) {
+        JSPropertyDescriptor desc;
+        JSAtom prop = props[i].atom;
+
+        res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
+        if (res < 0)
+            goto exception;
+        if (res) {
+            js_free_desc(ctx, &desc);
+            if ((desc.flags & JS_PROP_CONFIGURABLE)
+            ||  (is_frozen && (desc.flags & JS_PROP_WRITABLE))) {
+                res = FALSE;
+                goto done;
+            }
+        }
+    }
+    res = JS_IsExtensible(ctx, obj);
+    if (res < 0)
+        return JS_EXCEPTION;
+    res ^= 1;
+done:        
+    js_free_prop_enum(ctx, props, len);
+    return JS_NewBool(ctx, res);
+
+exception:
+    js_free_prop_enum(ctx, props, len);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_object_fromEntries(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue obj, iter, next_method = JS_UNDEFINED;
+    JSValueConst iterable;
+    BOOL done;
+
+    /*  RequireObjectCoercible() not necessary because it is tested in
+        JS_GetIterator() by JS_GetProperty() */
+    iterable = argv[0];
+
+    obj = JS_NewObject(ctx);
+    if (JS_IsException(obj))
+        return obj;
+    
+    iter = JS_GetIterator(ctx, iterable, FALSE);
+    if (JS_IsException(iter))
+        goto fail;
+    next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
+    if (JS_IsException(next_method))
+        goto fail;
+    
+    for(;;) {
+        JSValue key, value, item;
+        item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
+        if (JS_IsException(item))
+            goto fail;
+        if (done) {
+            JS_FreeValue(ctx, item);
+            break;
+        }
+        
+        key = JS_UNDEFINED;
+        value = JS_UNDEFINED;
+        if (!JS_IsObject(item)) {
+            JS_ThrowTypeErrorNotAnObject(ctx);
+            goto fail1;
+        }
+        key = JS_GetPropertyUint32(ctx, item, 0);
+        if (JS_IsException(key))
+            goto fail1;
+        value = JS_GetPropertyUint32(ctx, item, 1);
+        if (JS_IsException(value)) {
+            JS_FreeValue(ctx, key);
+            goto fail1;
+        }
+        if (JS_DefinePropertyValueValue(ctx, obj, key, value,
+                                        JS_PROP_C_W_E | JS_PROP_THROW) < 0) {
+        fail1:
+            JS_FreeValue(ctx, item);
+            goto fail;
+        }
+        JS_FreeValue(ctx, item);
+    }
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    return obj;
+ fail:
+    if (JS_IsObject(iter)) {
+        /* close the iterator object, preserving pending exception */
+        JS_IteratorClose(ctx, iter, TRUE);
+    }
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+#if 0
+/* Note: corresponds to ECMA spec: CreateDataPropertyOrThrow() */
+static JSValue js_object___setOwnProperty(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv)
+{
+    int ret;
+    ret = JS_DefinePropertyValueValue(ctx, argv[0], JS_DupValue(ctx, argv[1]),
+                                      JS_DupValue(ctx, argv[2]),
+                                      JS_PROP_C_W_E | JS_PROP_THROW);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_object___toObject(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    return JS_ToObject(ctx, argv[0]);
+}
+
+static JSValue js_object___toPrimitive(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    int hint = HINT_NONE;
+
+    if (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT)
+        hint = JS_VALUE_GET_INT(argv[1]);
+
+    return JS_ToPrimitive(ctx, argv[0], hint);
+}
+#endif
+
+/* return an empty string if not an object */
+static JSValue js_object___getClass(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSAtom atom;
+    JSObject *p;
+    uint32_t tag;
+    int class_id;
+
+    tag = JS_VALUE_GET_NORM_TAG(argv[0]);
+    if (tag == JS_TAG_OBJECT) {
+        p = JS_VALUE_GET_OBJ(argv[0]);
+        class_id = p->class_id;
+        if (class_id == JS_CLASS_PROXY && JS_IsFunction(ctx, argv[0]))
+            class_id = JS_CLASS_BYTECODE_FUNCTION;
+        atom = ctx->rt->class_array[class_id].class_name;
+    } else {
+        atom = JS_ATOM_empty_string;
+    }
+    return JS_AtomToString(ctx, atom);
+}
+
+static JSValue js_object_is(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    return JS_NewBool(ctx, js_same_value(ctx, argv[0], argv[1]));
+}
+
+#if 0
+static JSValue js_object___getObjectData(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    return JS_GetObjectData(ctx, argv[0]);
+}
+
+static JSValue js_object___setObjectData(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    if (JS_SetObjectData(ctx, argv[0], JS_DupValue(ctx, argv[1])))
+        return JS_EXCEPTION;
+    return JS_DupValue(ctx, argv[1]);
+}
+
+static JSValue js_object___toPropertyKey(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    return JS_ToPropertyKey(ctx, argv[0]);
+}
+
+static JSValue js_object___isObject(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    return JS_NewBool(ctx, JS_IsObject(argv[0]));
+}
+
+static JSValue js_object___isSameValueZero(JSContext *ctx, JSValueConst this_val,
+                                           int argc, JSValueConst *argv)
+{
+    return JS_NewBool(ctx, js_same_value_zero(ctx, argv[0], argv[1]));
+}
+
+static JSValue js_object___isConstructor(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    return JS_NewBool(ctx, JS_IsConstructor(ctx, argv[0]));
+}
+#endif
+
+static JSValue JS_SpeciesConstructor(JSContext *ctx, JSValueConst obj,
+                                     JSValueConst defaultConstructor)
+{
+    JSValue ctor, species;
+
+    if (!JS_IsObject(obj))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor);
+    if (JS_IsException(ctor))
+        return ctor;
+    if (JS_IsUndefined(ctor))
+        return JS_DupValue(ctx, defaultConstructor);
+    if (!JS_IsObject(ctor)) {
+        JS_FreeValue(ctx, ctor);
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    }
+    species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species);
+    JS_FreeValue(ctx, ctor);
+    if (JS_IsException(species))
+        return species;
+    if (JS_IsUndefined(species) || JS_IsNull(species))
+        return JS_DupValue(ctx, defaultConstructor);
+    if (!JS_IsConstructor(ctx, species)) {
+        JS_FreeValue(ctx, species);
+        return JS_ThrowTypeError(ctx, "not a constructor");
+    }
+    return species;
+}
+
+#if 0
+static JSValue js_object___speciesConstructor(JSContext *ctx, JSValueConst this_val,
+                                              int argc, JSValueConst *argv)
+{
+    return JS_SpeciesConstructor(ctx, argv[0], argv[1]);
+}
+#endif
+
+static JSValue js_object_get___proto__(JSContext *ctx, JSValueConst this_val)
+{
+    JSValue val, ret;
+
+    val = JS_ToObject(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    ret = JS_DupValue(ctx, JS_GetPrototype(ctx, val));
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static JSValue js_object_set___proto__(JSContext *ctx, JSValueConst this_val,
+                                       JSValueConst proto)
+{
+    if (JS_IsUndefined(this_val) || JS_IsNull(this_val))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    if (!JS_IsObject(proto) && !JS_IsNull(proto))
+        return JS_UNDEFINED;
+    if (JS_SetPrototypeInternal(ctx, this_val, proto, TRUE) < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_UNDEFINED;
+}
+
+static JSValue js_object_isPrototypeOf(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    JSValueConst v;
+    int max_depth = 1000, res = -1;
+
+    v = argv[0];
+    if (!JS_IsObject(v))
+        return JS_FALSE;
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    while (--max_depth > 0) {
+        v = JS_GetPrototype(ctx, v);
+        if (JS_IsException(v))
+            goto exception;
+        if (JS_IsNull(v)) {
+            res = FALSE;
+            break;
+        }
+        if (js_strict_eq2(ctx, JS_DupValue(ctx, obj), JS_DupValue(ctx, v),
+                          JS_EQ_STRICT)) {
+            res = TRUE;
+            break;
+        }
+    }
+    JS_FreeValue(ctx, obj);
+    if (res < 0)
+        return JS_ThrowInternalError(ctx, "prototype chain cycle");
+    else
+        return JS_NewBool(ctx, res);
+
+exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_object_propertyIsEnumerable(JSContext *ctx, JSValueConst this_val,
+                                              int argc, JSValueConst *argv)
+{
+    JSValue obj, res = JS_EXCEPTION;
+    JSAtom prop = JS_ATOM_NULL;
+    JSPropertyDescriptor desc;
+    int has_prop;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj))
+        goto exception;
+    prop = JS_ValueToAtom(ctx, argv[0]);
+    if (unlikely(prop == JS_ATOM_NULL))
+        goto exception;
+
+    has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop);
+    if (has_prop < 0)
+        goto exception;
+    if (has_prop) {
+        res = JS_NewBool(ctx, (desc.flags & JS_PROP_ENUMERABLE) != 0);
+        js_free_desc(ctx, &desc);
+    } else {
+        res = JS_FALSE;
+    }
+
+exception:
+    JS_FreeAtom(ctx, prop);
+    JS_FreeValue(ctx, obj);
+    return res;
+}
+
+static JSValue js_object___lookupGetter__(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv, int setter)
+{
+    JSValue obj, res = JS_EXCEPTION;
+    JSValueConst v;
+    JSAtom prop = JS_ATOM_NULL;
+    JSPropertyDescriptor desc;
+    int has_prop;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj))
+        goto exception;
+    prop = JS_ValueToAtom(ctx, argv[0]);
+    if (unlikely(prop == JS_ATOM_NULL))
+        goto exception;
+
+    for (v = obj;;) {
+        has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(v), prop);
+        if (has_prop < 0)
+            goto exception;
+        if (has_prop) {
+            if (desc.flags & JS_PROP_GETSET)
+                res = JS_DupValue(ctx, setter ? desc.setter : desc.getter);
+            else
+                res = JS_UNDEFINED;
+            js_free_desc(ctx, &desc);
+            break;
+        }
+        v = JS_GetPrototype(ctx, v);
+        if (JS_IsException(v))
+            goto exception;
+        if (JS_IsNull(v)) {
+            res = JS_UNDEFINED;
+            break;
+        }
+    }
+
+exception:
+    JS_FreeAtom(ctx, prop);
+    JS_FreeValue(ctx, obj);
+    return res;
+}
+
+static const JSCFunctionListEntry js_object_funcs[] = {
+    JS_CFUNC_DEF("create", 2, js_object_create ),
+    JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 0 ),
+    JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf ),
+    JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 0 ),
+    JS_CFUNC_DEF("defineProperties", 2, js_object_defineProperties ),
+    JS_CFUNC_DEF("getOwnPropertyNames", 1, js_object_getOwnPropertyNames ),
+    JS_CFUNC_DEF("getOwnPropertySymbols", 1, js_object_getOwnPropertySymbols ),
+    JS_CFUNC_MAGIC_DEF("keys", 1, js_object_keys, JS_ITERATOR_KIND_KEY ),
+    JS_CFUNC_MAGIC_DEF("values", 1, js_object_keys, JS_ITERATOR_KIND_VALUE ),
+    JS_CFUNC_MAGIC_DEF("entries", 1, js_object_keys, JS_ITERATOR_KIND_KEY_AND_VALUE ),
+    JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 0 ),
+    JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 0 ),
+    JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 0 ),
+    JS_CFUNC_DEF("getOwnPropertyDescriptors", 1, js_object_getOwnPropertyDescriptors ),
+    JS_CFUNC_DEF("is", 2, js_object_is ),
+    JS_CFUNC_DEF("assign", 2, js_object_assign ),
+    JS_CFUNC_MAGIC_DEF("seal", 1, js_object_seal, 0 ),
+    JS_CFUNC_MAGIC_DEF("freeze", 1, js_object_seal, 1 ),
+    JS_CFUNC_MAGIC_DEF("isSealed", 1, js_object_isSealed, 0 ),
+    JS_CFUNC_MAGIC_DEF("isFrozen", 1, js_object_isSealed, 1 ),
+    JS_CFUNC_DEF("__getClass", 1, js_object___getClass ),
+    //JS_CFUNC_DEF("__isObject", 1, js_object___isObject ),
+    //JS_CFUNC_DEF("__isConstructor", 1, js_object___isConstructor ),
+    //JS_CFUNC_DEF("__toObject", 1, js_object___toObject ),
+    //JS_CFUNC_DEF("__setOwnProperty", 3, js_object___setOwnProperty ),
+    //JS_CFUNC_DEF("__toPrimitive", 2, js_object___toPrimitive ),
+    //JS_CFUNC_DEF("__toPropertyKey", 1, js_object___toPropertyKey ),
+    //JS_CFUNC_DEF("__speciesConstructor", 2, js_object___speciesConstructor ),
+    //JS_CFUNC_DEF("__isSameValueZero", 2, js_object___isSameValueZero ),
+    //JS_CFUNC_DEF("__getObjectData", 1, js_object___getObjectData ),
+    //JS_CFUNC_DEF("__setObjectData", 2, js_object___setObjectData ),
+    JS_CFUNC_DEF("fromEntries", 1, js_object_fromEntries ),
+};
+
+static const JSCFunctionListEntry js_object_proto_funcs[] = {
+    JS_CFUNC_DEF("toString", 0, js_object_toString ),
+    JS_CFUNC_DEF("toLocaleString", 0, js_object_toLocaleString ),
+    JS_CFUNC_DEF("valueOf", 0, js_object_valueOf ),
+    JS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty ),
+    JS_CFUNC_DEF("isPrototypeOf", 1, js_object_isPrototypeOf ),
+    JS_CFUNC_DEF("propertyIsEnumerable", 1, js_object_propertyIsEnumerable ),
+    JS_CGETSET_DEF("__proto__", js_object_get___proto__, js_object_set___proto__ ),
+    JS_CFUNC_MAGIC_DEF("__defineGetter__", 2, js_object___defineGetter__, 0 ),
+    JS_CFUNC_MAGIC_DEF("__defineSetter__", 2, js_object___defineGetter__, 1 ),
+    JS_CFUNC_MAGIC_DEF("__lookupGetter__", 1, js_object___lookupGetter__, 0 ),
+    JS_CFUNC_MAGIC_DEF("__lookupSetter__", 1, js_object___lookupGetter__, 1 ),
+};
+
+/* Function class */
+
+static JSValue js_function_proto(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    return JS_UNDEFINED;
+}
+
+/* insert a '\n' at unicode character position 'pos' in the source of
+   'func_obj' */
+static int patch_function_constructor_source(JSContext *ctx,
+                                             JSValueConst func_obj,
+                                             size_t pos)
+{
+    JSObject *p;
+    JSFunctionBytecode *b;
+    char *r, *r_end, *new_source;
+    int c;
+    size_t idx, len;
+    
+    if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)
+        return 0;
+    p = JS_VALUE_GET_OBJ(func_obj);
+    if (!js_class_has_bytecode(p->class_id))
+        return 0;
+    b = p->u.func.function_bytecode;
+    if (!b->has_debug)
+        return 0;
+    r = b->debug.source;
+    r_end = b->debug.source + b->debug.source_len;
+    idx = 0;
+    while (r < r_end) {
+        if (idx == pos) {
+            /* add the '\n' */
+            new_source = js_realloc(ctx, b->debug.source,
+                                    b->debug.source_len + 2);
+            if (!new_source)
+                return -1;
+            len = r - b->debug.source;
+            memmove(new_source + len + 1, new_source + len,
+                    b->debug.source_len + 1 - len);
+            new_source[len] = '\n';
+            b->debug.source = new_source;
+            b->debug.source_len++;
+            break;
+        }
+        c = unicode_from_utf8((const uint8_t *)r, UTF8_CHAR_LEN_MAX,
+                              (const uint8_t **)&r);
+        if (c < 0)
+            break;
+        idx++;
+    }
+    return 0;
+}
+                                             
+/* XXX: add a specific eval mode so that Function("}), ({") is rejected */
+static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target,
+                                       int argc, JSValueConst *argv, int magic)
+{
+    JSFunctionKindEnum func_kind = magic;
+    int i, n, ret, func_start_pos;
+    JSValue s, proto, obj = JS_UNDEFINED;
+    StringBuffer b_s, *b = &b_s;
+
+    string_buffer_init(ctx, b, 0);
+    string_buffer_putc8(b, '(');
+    
+    if (func_kind == JS_FUNC_ASYNC || func_kind == JS_FUNC_ASYNC_GENERATOR) {
+        string_buffer_puts8(b, "async ");
+    }
+    string_buffer_puts8(b, "function");
+
+    if (func_kind == JS_FUNC_GENERATOR || func_kind == JS_FUNC_ASYNC_GENERATOR) {
+        string_buffer_putc8(b, '*');
+    }
+    string_buffer_puts8(b, " anonymous(");
+
+    n = argc - 1;
+    for(i = 0; i < n; i++) {
+        if (i != 0) {
+            string_buffer_putc8(b, ',');
+        }
+        if (string_buffer_concat_value(b, argv[i]))
+            goto fail;
+    }
+    string_buffer_puts8(b, "\n) {");
+    /* Annex B HTML comments: We don't add a '\n' after "{" so that
+       "-->" is not considered as an HTML comment. It is necessary
+       because in the spec the function body is parsed separately. */
+    /* XXX: find a simpler way or be deliberately incompatible to
+       simplify the code ? */
+    func_start_pos = b->len - 1; /* the leading '(' is not in the source */
+    if (n >= 0) {
+        if (string_buffer_concat_value(b, argv[n]))
+            goto fail;
+    }
+    string_buffer_puts8(b, "\n})");
+    s = string_buffer_end(b);
+    if (JS_IsException(s))
+        goto fail1;
+
+    obj = JS_EvalObject(ctx, ctx->global_obj, s, JS_EVAL_TYPE_INDIRECT, -1);
+    JS_FreeValue(ctx, s);
+    if (JS_IsException(obj))
+        goto fail1;
+    if (patch_function_constructor_source(ctx, obj, func_start_pos) < 0)
+        goto fail1;
+    if (!JS_IsUndefined(new_target)) {
+        /* set the prototype */
+        proto = js_get_prototype_from_ctor(ctx, new_target, JS_UNDEFINED);
+        if (JS_IsException(proto))
+            goto fail1;
+        if (!JS_IsUndefined(proto)) {
+            ret = JS_SetPrototypeInternal(ctx, obj, proto, TRUE);
+            JS_FreeValue(ctx, proto);
+            if (ret < 0)
+                goto fail1;
+        }
+    }
+    return obj;
+
+ fail:
+    string_buffer_free(b);
+ fail1:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static __exception int js_get_length32(JSContext *ctx, uint32_t *pres,
+                                       JSValueConst obj)
+{
+    JSValue len_val;
+    len_val = JS_GetProperty(ctx, obj, JS_ATOM_length);
+    if (JS_IsException(len_val)) {
+        *pres = 0;
+        return -1;
+    }
+    return JS_ToUint32Free(ctx, pres, len_val);
+}
+
+static __exception int js_get_length64(JSContext *ctx, int64_t *pres,
+                                       JSValueConst obj)
+{
+    JSValue len_val;
+    len_val = JS_GetProperty(ctx, obj, JS_ATOM_length);
+    if (JS_IsException(len_val)) {
+        *pres = 0;
+        return -1;
+    }
+    return JS_ToLengthFree(ctx, pres, len_val);
+}
+
+static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len)
+{
+    uint32_t i;
+    for(i = 0; i < len; i++) {
+        JS_FreeValue(ctx, tab[i]);
+    }
+    js_free(ctx, tab);
+}
+
+/* XXX: should use ValueArray */
+static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen,
+                               JSValueConst array_arg)
+{
+    uint32_t len, i;
+    JSValue *tab, ret;
+    JSObject *p;
+
+    if (JS_VALUE_GET_TAG(array_arg) != JS_TAG_OBJECT) {
+        JS_ThrowTypeError(ctx, "not a object");
+        return NULL;
+    }
+    if (js_get_length32(ctx, &len, array_arg))
+        return NULL;
+    /* avoid allocating 0 bytes */
+    tab = js_mallocz(ctx, sizeof(tab[0]) * max_uint32(1, len));
+    if (!tab)
+        return NULL;
+    p = JS_VALUE_GET_OBJ(array_arg);
+    if ((p->class_id == JS_CLASS_ARRAY || p->class_id == JS_CLASS_ARGUMENTS) &&
+        p->fast_array &&
+        len == p->u.array.count) {
+        for(i = 0; i < len; i++) {
+            tab[i] = JS_DupValue(ctx, p->u.array.u.values[i]);
+        }
+    } else {
+        for(i = 0; i < len; i++) {
+            ret = JS_GetPropertyUint32(ctx, array_arg, i);
+            if (JS_IsException(ret)) {
+                free_arg_list(ctx, tab, i);
+                return NULL;
+            }
+            tab[i] = ret;
+        }
+    }
+    *plen = len;
+    return tab;
+}
+
+static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv, int magic)
+{
+    JSValueConst this_arg, array_arg;
+    uint32_t len;
+    JSValue *tab, ret;
+
+    if (check_function(ctx, this_val))
+        return JS_EXCEPTION;
+    this_arg = argv[0];
+    array_arg = argv[1];
+    if (JS_VALUE_GET_TAG(array_arg) == JS_TAG_UNDEFINED ||
+        JS_VALUE_GET_TAG(array_arg) == JS_TAG_NULL) {
+        return JS_Call(ctx, this_val, this_arg, 0, NULL);
+    }
+    tab = build_arg_list(ctx, &len, array_arg);
+    if (!tab)
+        return JS_EXCEPTION;
+    if (magic) {
+        ret = JS_CallConstructor2(ctx, this_val, this_arg, len, (JSValueConst *)tab);
+    } else {
+        ret = JS_Call(ctx, this_val, this_arg, len, (JSValueConst *)tab);
+    }
+    free_arg_list(ctx, tab, len);
+    return ret;
+}
+
+static JSValue js_function_call(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    if (argc <= 0) {
+        return JS_Call(ctx, this_val, JS_UNDEFINED, 0, NULL);
+    } else {
+        return JS_Call(ctx, this_val, argv[0], argc - 1, argv + 1);
+    }
+}
+
+static JSValue js_function_bind(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSBoundFunction *bf;
+    JSValue func_obj, name1;
+    JSObject *p;
+    int arg_count, i;
+    uint32_t len1;
+
+    if (check_function(ctx, this_val))
+        return JS_EXCEPTION;
+
+    func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
+                                 JS_CLASS_BOUND_FUNCTION);
+    if (JS_IsException(func_obj))
+        return JS_EXCEPTION;
+    p = JS_VALUE_GET_OBJ(func_obj);
+    p->is_constructor = JS_IsConstructor(ctx, this_val);
+    arg_count = max_int(0, argc - 1);
+    bf = js_malloc(ctx, sizeof(*bf) + arg_count * sizeof(JSValue));
+    if (!bf)
+        goto exception;
+    bf->func_obj = JS_DupValue(ctx, this_val);
+    bf->this_val = JS_DupValue(ctx, argv[0]);
+    bf->argc = arg_count;
+    for(i = 0; i < arg_count; i++) {
+        bf->argv[i] = JS_DupValue(ctx, argv[i + 1]);
+    }
+    p->u.bound_function = bf;
+
+    name1 = JS_GetProperty(ctx, this_val, JS_ATOM_name);
+    if (JS_IsException(name1))
+        goto exception;
+    if (!JS_IsString(name1)) {
+        JS_FreeValue(ctx, name1);
+        name1 = JS_AtomToString(ctx, JS_ATOM_empty_string);
+    }
+    name1 = JS_ConcatString3(ctx, "bound ", name1, "");
+    if (JS_IsException(name1))
+        goto exception;
+    JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name1,
+                           JS_PROP_CONFIGURABLE);
+    if (js_get_length32(ctx, &len1, this_val))
+        goto exception;
+    if (len1 <= (uint32_t)arg_count)
+        len1 = 0;
+    else
+        len1 -= arg_count;
+    JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length,
+                           JS_NewUint32(ctx, len1),
+                           JS_PROP_CONFIGURABLE);
+    return func_obj;
+ exception:
+    JS_FreeValue(ctx, func_obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_function_toString(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    JSFunctionKindEnum func_kind = JS_FUNC_NORMAL;
+
+    if (check_function(ctx, this_val))
+        return JS_EXCEPTION;
+
+    p = JS_VALUE_GET_OBJ(this_val);
+    if (js_class_has_bytecode(p->class_id)) {
+        JSFunctionBytecode *b = p->u.func.function_bytecode;
+        if (b->has_debug && b->debug.source) {
+            return JS_NewStringLen(ctx, b->debug.source, b->debug.source_len);
+        }
+        func_kind = b->func_kind;
+    }
+    {
+        JSValue name;
+        const char *pref, *suff;
+
+        if (p->is_class) {
+            pref = "class ";
+            suff = " {\n    [native code]\n}";
+        } else {
+            switch(func_kind) {
+            default:
+            case JS_FUNC_NORMAL:
+                pref = "function ";
+                break;
+            case JS_FUNC_GENERATOR:
+                pref = "function *";
+                break;
+            case JS_FUNC_ASYNC:
+                pref = "async function ";
+                break;
+            case JS_FUNC_ASYNC_GENERATOR:
+                pref = "async function *";
+                break;
+            }
+            suff = "() {\n    [native code]\n}";
+        }
+        name = JS_GetProperty(ctx, this_val, JS_ATOM_name);
+        if (JS_IsUndefined(name))
+            name = JS_AtomToString(ctx, JS_ATOM_empty_string);
+        return JS_ConcatString3(ctx, pref, name, suff);
+    }
+}
+
+static JSValue js_function_hasInstance(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    int ret;
+    ret = JS_OrdinaryIsInstanceOf(ctx, argv[0], this_val);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static const JSCFunctionListEntry js_function_proto_funcs[] = {
+    JS_CFUNC_DEF("call", 1, js_function_call ),
+    JS_CFUNC_MAGIC_DEF("apply", 2, js_function_apply, 0 ),
+    JS_CFUNC_DEF("bind", 1, js_function_bind ),
+    JS_CFUNC_DEF("toString", 0, js_function_toString ),
+    JS_CFUNC_DEF("[Symbol.hasInstance]", 1, js_function_hasInstance ),
+    JS_CGETSET_DEF("fileName", js_function_proto_fileName, NULL ),
+    JS_CGETSET_DEF("lineNumber", js_function_proto_lineNumber, NULL ),
+};
+
+/* Error class */
+
+static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target,
+                                    int argc, JSValueConst *argv, int magic)
+{
+    JSValue obj, msg, proto;
+    JSValueConst proto1;
+
+    if (JS_IsUndefined(new_target))
+        new_target = JS_GetActiveFunction(ctx);
+    if (magic < 0) {
+        proto1 = ctx->class_proto[JS_CLASS_ERROR];
+    } else {
+        proto1 = ctx->native_error_proto[magic];
+    }
+    proto = js_get_prototype_from_ctor(ctx, new_target, proto1);
+    if (JS_IsException(proto))
+        return proto;
+    obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_ERROR);
+    JS_FreeValue(ctx, proto);
+    if (!JS_IsException(obj) && !JS_IsUndefined(argv[0])) {
+        msg = JS_ToString(ctx, argv[0]);
+        if (unlikely(JS_IsException(msg))) {
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+        JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg,
+                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    }
+    return obj;
+}
+
+static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSValue name, msg;
+
+    name = JS_GetProperty(ctx, this_val, JS_ATOM_name);
+    if (JS_IsUndefined(name))
+        name = JS_AtomToString(ctx, JS_ATOM_Error);
+    else
+        name = JS_ToStringFree(ctx, name);
+    if (JS_IsException(name))
+        return JS_EXCEPTION;
+
+    msg = JS_GetProperty(ctx, this_val, JS_ATOM_message);
+    if (JS_IsUndefined(msg))
+        msg = JS_AtomToString(ctx, JS_ATOM_empty_string);
+    else
+        msg = JS_ToStringFree(ctx, msg);
+    if (JS_IsException(msg)) {
+        JS_FreeValue(ctx, name);
+        return JS_EXCEPTION;
+    }
+    if (!JS_IsEmptyString(name) && !JS_IsEmptyString(msg))
+        name = JS_ConcatString3(ctx, "", name, ": ");
+    return JS_ConcatString(ctx, name, msg);
+}
+
+static const JSCFunctionListEntry js_error_proto_funcs[] = {
+    JS_CFUNC_DEF("toString", 0, js_error_toString ),
+    JS_PROP_STRING_DEF("name", "Error", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
+    JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
+};
+
+/* Array */
+
+static int JS_CopySubArray(JSContext *ctx,
+                           JSValueConst obj, int64_t to_pos,
+                           int64_t from_pos, int64_t count, int dir)
+{
+    int64_t i, from, to;
+    JSValue val;
+    int fromPresent;
+
+    /* XXX: should special case fast arrays */
+    for (i = 0; i < count; i++) {
+        if (dir < 0) {
+            from = from_pos + count - i - 1;
+            to = to_pos + count - i - 1;
+        } else {
+            from = from_pos + i;
+            to = to_pos + i;
+        }
+        fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val);
+        if (fromPresent < 0)
+            goto exception;
+
+        if (fromPresent) {
+            if (JS_SetPropertyInt64(ctx, obj, to, val) < 0)
+                goto exception;
+        } else {
+            if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0)
+                goto exception;
+        }
+    }
+    return 0;
+
+ exception:
+    return -1;
+}
+
+static JSValue js_array_constructor(JSContext *ctx, JSValueConst new_target,
+                                    int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    int i;
+
+    obj = js_create_from_ctor(ctx, new_target, JS_CLASS_ARRAY);
+    if (JS_IsException(obj))
+        return obj;
+    if (argc == 1 && JS_IsNumber(argv[0])) {
+        uint32_t len;
+        if (JS_ToArrayLengthFree(ctx, &len, JS_DupValue(ctx, argv[0])))
+            goto fail;
+        if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, len)) < 0)
+            goto fail;
+    } else {
+        for(i = 0; i < argc; i++) {
+            if (JS_SetPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i])) < 0)
+                goto fail;
+        }
+    }
+    return obj;
+fail:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_from(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    // from(items, mapfn = void 0, this_arg = void 0)
+    JSValueConst items = argv[0], mapfn, this_arg;
+    JSValueConst args[2];
+    JSValue stack[2];
+    JSValue iter, r, v, v2, arrayLike;
+    int64_t k, len;
+    int done, mapping;
+
+    mapping = FALSE;
+    mapfn = JS_UNDEFINED;
+    this_arg = JS_UNDEFINED;
+    r = JS_UNDEFINED;
+    arrayLike = JS_UNDEFINED;
+    stack[0] = JS_UNDEFINED;
+    stack[1] = JS_UNDEFINED;
+
+    if (argc > 1) {
+        mapfn = argv[1];
+        if (!JS_IsUndefined(mapfn)) {
+            if (check_function(ctx, mapfn))
+                goto exception;
+            mapping = 1;
+            if (argc > 2)
+                this_arg = argv[2];
+        }
+    }
+    iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator);
+    if (JS_IsException(iter))
+        goto exception;
+    if (!JS_IsUndefined(iter)) {
+        JS_FreeValue(ctx, iter);
+        if (JS_IsConstructor(ctx, this_val))
+            r = JS_CallConstructor(ctx, this_val, 0, NULL);
+        else
+            r = JS_NewArray(ctx);
+        if (JS_IsException(r))
+            goto exception;
+        stack[0] = JS_DupValue(ctx, items);
+        if (js_for_of_start(ctx, &stack[1], FALSE))
+            goto exception;
+        for (k = 0;; k++) {
+            v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
+            if (JS_IsException(v))
+                goto exception_close;
+            if (done)
+                break;
+            if (mapping) {
+                args[0] = v;
+                args[1] = JS_NewInt32(ctx, k);
+                v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
+                JS_FreeValue(ctx, v);
+                v = v2;
+                if (JS_IsException(v))
+                    goto exception_close;
+            }
+            if (JS_DefinePropertyValueInt64(ctx, r, k, v,
+                                            JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                goto exception_close;
+        }
+    } else {
+        arrayLike = JS_ToObject(ctx, items);
+        if (JS_IsException(arrayLike))
+            goto exception;
+        if (js_get_length64(ctx, &len, arrayLike) < 0)
+            goto exception;
+        v = JS_NewInt64(ctx, len);
+        args[0] = v;
+        if (JS_IsConstructor(ctx, this_val)) {
+            r = JS_CallConstructor(ctx, this_val, 1, args);
+        } else {
+            r = js_array_constructor(ctx, JS_UNDEFINED, 1, args);
+        }
+        JS_FreeValue(ctx, v);
+        if (JS_IsException(r))
+            goto exception;
+        for(k = 0; k < len; k++) {
+            v = JS_GetPropertyInt64(ctx, arrayLike, k);
+            if (JS_IsException(v))
+                goto exception;
+            if (mapping) {
+                args[0] = v;
+                args[1] = JS_NewInt32(ctx, k);
+                v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
+                JS_FreeValue(ctx, v);
+                v = v2;
+                if (JS_IsException(v))
+                    goto exception;
+            }
+            if (JS_DefinePropertyValueInt64(ctx, r, k, v,
+                                            JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                goto exception;
+        }
+    }
+    if (JS_SetProperty(ctx, r, JS_ATOM_length, JS_NewUint32(ctx, k)) < 0)
+        goto exception;
+    goto done;
+
+ exception_close:
+    if (!JS_IsUndefined(stack[0]))
+        JS_IteratorClose(ctx, stack[0], TRUE);
+ exception:
+    JS_FreeValue(ctx, r);
+    r = JS_EXCEPTION;
+ done:
+    JS_FreeValue(ctx, arrayLike);
+    JS_FreeValue(ctx, stack[0]);
+    JS_FreeValue(ctx, stack[1]);
+    return r;
+}
+
+static JSValue js_array_of(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    JSValue obj, args[1];
+    int i;
+
+    if (JS_IsConstructor(ctx, this_val)) {
+        args[0] = JS_NewInt32(ctx, argc);
+        obj = JS_CallConstructor(ctx, this_val, 1, (JSValueConst *)args);
+    } else {
+        obj = JS_NewArray(ctx);
+    }
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    for(i = 0; i < argc; i++) {
+        if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i]),
+                                        JS_PROP_THROW) < 0) {
+            goto fail;
+        }
+    }
+    if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, argc)) < 0) {
+    fail:
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    return obj;
+}
+
+static JSValue js_array_isArray(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    int ret;
+    ret = JS_IsArray(ctx, argv[0]);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_get_this(JSContext *ctx,
+                           JSValueConst this_val)
+{
+    return JS_DupValue(ctx, this_val);
+}
+
+static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj,
+                                     JSValueConst len_val)
+{
+    JSValue ctor, ret;
+    int res;
+
+    res = JS_IsArray(ctx, obj);
+    if (res < 0)
+        return JS_EXCEPTION;
+    if (!res)
+        return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val);
+    ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED);
+    if (JS_IsException(ctor))
+        return JS_EXCEPTION;
+    if (JS_IsUndefined(ctor))
+        return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val);
+    ret = JS_CallConstructor(ctx, ctor, 1, &len_val);
+    JS_FreeValue(ctx, ctor);
+    return ret;
+}
+
+static const JSCFunctionListEntry js_array_funcs[] = {
+    JS_CFUNC_DEF("isArray", 1, js_array_isArray ),
+    JS_CFUNC_DEF("from", 1, js_array_from ),
+    JS_CFUNC_DEF("of", 0, js_array_of ),
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
+};
+
+static int JS_isConcatSpreadable(JSContext *ctx, JSValueConst obj)
+{
+    JSValue val;
+
+    if (!JS_IsObject(obj))
+        return FALSE;
+    val = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_isConcatSpreadable);
+    if (JS_IsException(val))
+        return -1;
+    if (!JS_IsUndefined(val))
+        return JS_ToBoolFree(ctx, val);
+    return JS_IsArray(ctx, obj);
+}
+
+static JSValue js_array_concat(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    JSValue obj, arr, val;
+    JSValueConst e;
+    int64_t len, k, n;
+    int i, res;
+
+    arr = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj))
+        goto exception;
+
+    arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0));
+    if (JS_IsException(arr))
+        goto exception;
+    n = 0;
+    for (i = -1; i < argc; i++) {
+        if (i < 0)
+            e = obj;
+        else
+            e = argv[i];
+
+        res = JS_isConcatSpreadable(ctx, e);
+        if (res < 0)
+            goto exception;
+        if (res) {
+            if (js_get_length64(ctx, &len, e))
+                goto exception;
+            if (n + len >= MAX_SAFE_INTEGER) {
+                JS_ThrowTypeError(ctx, "Array loo long");
+                goto exception;
+            }
+            for (k = 0; k < len; k++, n++) {
+                res = JS_TryGetPropertyInt64(ctx, e, k, &val);
+                if (res < 0)
+                    goto exception;
+                if (res) {
+                    if (JS_DefinePropertyValueInt64(ctx, arr, n, val,
+                                                    JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                        goto exception;
+                }
+            }
+        } else {
+            if (n >= MAX_SAFE_INTEGER) {
+                JS_ThrowTypeError(ctx, "Array loo long");
+                goto exception;
+            }
+            if (JS_DefinePropertyValueInt64(ctx, arr, n, JS_DupValue(ctx, e),
+                                            JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                goto exception;
+            n++;
+        }
+    }
+    if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0)
+        goto exception;
+
+    JS_FreeValue(ctx, obj);
+    return arr;
+
+exception:
+    JS_FreeValue(ctx, arr);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+#define special_every    0
+#define special_some     1
+#define special_forEach  2
+#define special_map      3
+#define special_filter   4
+#define special_TA       8
+
+static int js_typed_array_get_length_internal(JSContext *ctx, JSValueConst obj);
+
+static JSValue js_typed_array___speciesCreate(JSContext *ctx,
+                                              JSValueConst this_val,
+                                              int argc, JSValueConst *argv);
+
+static JSValue js_array_every(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int special)
+{
+    JSValue obj, val, index_val, res, ret;
+    JSValueConst args[3];
+    JSValueConst func, this_arg;
+    int64_t len, k, n;
+    int present;
+
+    ret = JS_UNDEFINED;
+    val = JS_UNDEFINED;
+    if (special & special_TA) {
+        obj = JS_DupValue(ctx, this_val);
+        len = js_typed_array_get_length_internal(ctx, obj);
+        if (len < 0)
+            goto exception;
+    } else {
+        obj = JS_ToObject(ctx, this_val);
+        if (js_get_length64(ctx, &len, obj))
+            goto exception;
+    }
+    func = argv[0];
+    this_arg = JS_UNDEFINED;
+    if (argc > 1)
+        this_arg = argv[1];
+        
+    if (check_function(ctx, func))
+        goto exception;
+
+    switch (special) {
+    case special_every:
+    case special_every | special_TA:
+        ret = JS_TRUE;
+        break;
+    case special_some:
+    case special_some | special_TA:
+        ret = JS_FALSE;
+        break;
+    case special_map:
+        /* XXX: JS_ArraySpeciesCreate should take int64_t */
+        ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64(ctx, len));
+        if (JS_IsException(ret))
+            goto exception;
+        break;
+    case special_filter:
+        ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0));
+        if (JS_IsException(ret))
+            goto exception;
+        break;
+    case special_map | special_TA:
+        args[0] = obj;
+        args[1] = JS_NewInt32(ctx, len);
+        ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
+        if (JS_IsException(ret))
+            goto exception;
+        break;
+    case special_filter | special_TA:
+        ret = JS_NewArray(ctx);
+        if (JS_IsException(ret))
+            goto exception;
+        break;
+    }
+    n = 0;
+
+    for(k = 0; k < len; k++) {
+        present = JS_TryGetPropertyInt64(ctx, obj, k, &val);
+        if (present < 0)
+            goto exception;
+        if (present) {
+            index_val = JS_NewInt64(ctx, k);
+            if (JS_IsException(index_val))
+                goto exception;
+            args[0] = val;
+            args[1] = index_val;
+            args[2] = obj;
+            res = JS_Call(ctx, func, this_arg, 3, args);
+            JS_FreeValue(ctx, index_val);
+            if (JS_IsException(res))
+                goto exception;
+            switch (special) {
+            case special_every:
+            case special_every | special_TA:
+                if (!JS_ToBoolFree(ctx, res)) {
+                    ret = JS_FALSE;
+                    goto done;
+                }
+                break;
+            case special_some:
+            case special_some | special_TA:
+                if (JS_ToBoolFree(ctx, res)) {
+                    ret = JS_TRUE;
+                    goto done;
+                }
+                break;
+            case special_map:
+                if (JS_DefinePropertyValueInt64(ctx, ret, k, res,
+                                                JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                    goto exception;
+                break;
+            case special_map | special_TA:
+                if (JS_SetPropertyValue(ctx, ret, JS_NewInt32(ctx, k), res, JS_PROP_THROW) < 0)
+                    goto exception;
+                break;
+            case special_filter:
+            case special_filter | special_TA:
+                if (JS_ToBoolFree(ctx, res)) {
+                    if (JS_DefinePropertyValueInt64(ctx, ret, n++, JS_DupValue(ctx, val),
+                                                    JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                        goto exception;
+                }
+                break;
+            default:
+                JS_FreeValue(ctx, res);
+                break;
+            }
+            JS_FreeValue(ctx, val);
+            val = JS_UNDEFINED;
+        }
+    }
+done:
+    if (special == (special_filter | special_TA)) {
+        JSValue arr;
+        args[0] = obj;
+        args[1] = JS_NewInt32(ctx, n);
+        arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
+        if (JS_IsException(arr))
+            goto exception;
+        args[0] = ret;
+        res = JS_Invoke(ctx, arr, JS_ATOM_set, 1, args);
+        if (check_exception_free(ctx, res))
+            goto exception;
+        JS_FreeValue(ctx, ret);
+        ret = arr;
+    }
+    JS_FreeValue(ctx, val);
+    JS_FreeValue(ctx, obj);
+    return ret;
+
+exception:
+    JS_FreeValue(ctx, ret);
+    JS_FreeValue(ctx, val);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+#define special_reduce       0
+#define special_reduceRight  1
+
+static JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv, int special)
+{
+    JSValue obj, val, index_val, acc, acc1;
+    JSValueConst args[4];
+    JSValueConst func;
+    int64_t len, k, k1;
+    int present;
+
+    acc = JS_UNDEFINED;
+    val = JS_UNDEFINED;
+    if (special & special_TA) {
+        obj = JS_DupValue(ctx, this_val);
+        len = js_typed_array_get_length_internal(ctx, obj);
+        if (len < 0)
+            goto exception;
+    } else {
+        obj = JS_ToObject(ctx, this_val);
+        if (js_get_length64(ctx, &len, obj))
+            goto exception;
+    }
+    func = argv[0];
+
+    if (check_function(ctx, func))
+        goto exception;
+
+    k = 0;
+    if (argc > 1) {
+        acc = JS_DupValue(ctx, argv[1]);
+    } else {
+        for(;;) {
+            if (k >= len) {
+                JS_ThrowTypeError(ctx, "empty array");
+                goto exception;
+            }
+            k1 = (special & special_reduceRight) ? len - k - 1 : k;
+            k++;
+            present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc);
+            if (present < 0)
+                goto exception;
+            if (present)
+                break;
+        }
+    }
+    for (; k < len; k++) {
+        k1 = (special & special_reduceRight) ? len - k - 1 : k;
+        present = JS_TryGetPropertyInt64(ctx, obj, k1, &val);
+        if (present < 0)
+            goto exception;
+        if (present) {
+            index_val = JS_NewInt64(ctx, k1);
+            if (JS_IsException(index_val))
+                goto exception;
+            args[0] = acc;
+            args[1] = val;
+            args[2] = index_val;
+            args[3] = obj;
+            acc1 = JS_Call(ctx, func, JS_UNDEFINED, 4, args);
+            JS_FreeValue(ctx, index_val);
+            JS_FreeValue(ctx, val);
+            val = JS_UNDEFINED;
+            if (JS_IsException(acc1))
+                goto exception;
+            JS_FreeValue(ctx, acc);
+            acc = acc1;
+        }
+    }
+    JS_FreeValue(ctx, obj);
+    return acc;
+
+exception:
+    JS_FreeValue(ctx, acc);
+    JS_FreeValue(ctx, val);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_fill(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    int64_t len, start, end;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    start = 0;
+    if (argc > 1 && !JS_IsUndefined(argv[1])) {
+        if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len))
+            goto exception;
+    }
+
+    end = len;
+    if (argc > 2 && !JS_IsUndefined(argv[2])) {
+        if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len))
+            goto exception;
+    }
+
+    /* XXX: should special case fast arrays */
+    while (start < end) {
+        if (JS_SetPropertyInt64(ctx, obj, start,
+                                JS_DupValue(ctx, argv[0])) < 0)
+            goto exception;
+        start++;
+    }
+    return obj;
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSValue obj, val;
+    int64_t len, n, res;
+    JSValue *arrp;
+    uint32_t count;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    res = FALSE;
+    if (len > 0) {
+        n = 0;
+        if (argc > 1) {
+            if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len))
+                goto exception;
+        }
+        if (js_get_fast_array(ctx, obj, &arrp, &count)) {
+            for (; n < count; n++) {
+                if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]),
+                                  JS_DupValue(ctx, arrp[n]),
+                                  JS_EQ_SAME_VALUE_ZERO)) {
+                    res = TRUE;
+                    goto done;
+                }
+            }
+        }
+        for (; n < len; n++) {
+            val = JS_GetPropertyInt64(ctx, obj, n);
+            if (JS_IsException(val))
+                goto exception;
+            if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val,
+                              JS_EQ_SAME_VALUE_ZERO)) {
+                res = TRUE;
+                break;
+            }
+        }
+    }
+ done:
+    JS_FreeValue(ctx, obj);
+    return JS_NewBool(ctx, res);
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_indexOf(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue obj, val;
+    int64_t len, n, res;
+    JSValue *arrp;
+    uint32_t count;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    res = -1;
+    if (len > 0) {
+        n = 0;
+        if (argc > 1) {
+            if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len))
+                goto exception;
+        }
+        if (js_get_fast_array(ctx, obj, &arrp, &count)) {
+            for (; n < count; n++) {
+                if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]),
+                                  JS_DupValue(ctx, arrp[n]), JS_EQ_STRICT)) {
+                    res = n;
+                    goto done;
+                }
+            }
+        }
+        for (; n < len; n++) {
+            int present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
+            if (present < 0)
+                goto exception;
+            if (present) {
+                if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) {
+                    res = n;
+                    break;
+                }
+            }
+        }
+    }
+ done:
+    JS_FreeValue(ctx, obj);
+    return JS_NewInt64(ctx, res);
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSValue obj, val;
+    int64_t len, n, res;
+    int present;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    res = -1;
+    if (len > 0) {
+        n = len - 1;
+        if (argc > 1) {
+            if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len))
+                goto exception;
+        }
+        /* XXX: should special case fast arrays */
+        for (; n >= 0; n--) {
+            present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
+            if (present < 0)
+                goto exception;
+            if (present) {
+                if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) {
+                    res = n;
+                    break;
+                }
+            }
+        }
+    }
+    JS_FreeValue(ctx, obj);
+    return JS_NewInt64(ctx, res);
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_find(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv, int findIndex)
+{
+    JSValueConst func, this_arg;
+    JSValueConst args[3];
+    JSValue obj, val, index_val, res;
+    int64_t len, k;
+
+    index_val = JS_UNDEFINED;
+    val = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    func = argv[0];
+    if (check_function(ctx, func))
+        goto exception;
+
+    this_arg = JS_UNDEFINED;
+    if (argc > 1)
+        this_arg = argv[1];
+
+    for(k = 0; k < len; k++) {
+        index_val = JS_NewInt64(ctx, k);
+        if (JS_IsException(index_val))
+            goto exception;
+        val = JS_GetPropertyValue(ctx, obj, index_val);
+        if (JS_IsException(val))
+            goto exception;
+        args[0] = val;
+        args[1] = index_val;
+        args[2] = this_val;
+        res = JS_Call(ctx, func, this_arg, 3, args);
+        if (JS_IsException(res))
+            goto exception;
+        if (JS_ToBoolFree(ctx, res)) {
+            if (findIndex) {
+                JS_FreeValue(ctx, val);
+                JS_FreeValue(ctx, obj);
+                return index_val;
+            } else {
+                JS_FreeValue(ctx, index_val);
+                JS_FreeValue(ctx, obj);
+                return val;
+            }
+        }
+        JS_FreeValue(ctx, val);
+        JS_FreeValue(ctx, index_val);
+    }
+    JS_FreeValue(ctx, obj);
+    if (findIndex)
+        return JS_NewInt32(ctx, -1);
+    else
+        return JS_UNDEFINED;
+
+exception:
+    JS_FreeValue(ctx, index_val);
+    JS_FreeValue(ctx, val);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_toString(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSValue obj, method, ret;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    method = JS_GetProperty(ctx, obj, JS_ATOM_join);
+    if (JS_IsException(method)) {
+        ret = JS_EXCEPTION;
+    } else
+    if (!JS_IsFunction(ctx, method)) {
+        /* Use intrinsic Object.prototype.toString */
+        JS_FreeValue(ctx, method);
+        ret = js_object_toString(ctx, obj, 0, NULL);
+    } else {
+        ret = JS_CallFree(ctx, method, obj, 0, NULL);
+    }
+    JS_FreeValue(ctx, obj);
+    return ret;
+}
+
+static JSValue js_array_join(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv, int toLocaleString)
+{
+    JSValue obj, sep = JS_UNDEFINED, el;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p = NULL;
+    int64_t i, n;
+    int c;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &n, obj))
+        goto exception;
+
+    c = ',';    /* default separator */
+    if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) {
+        sep = JS_ToString(ctx, argv[0]);
+        if (JS_IsException(sep))
+            goto exception;
+        p = JS_VALUE_GET_STRING(sep);
+        if (p->len == 1 && !p->is_wide_char)
+            c = p->u.str8[0];
+        else
+            c = -1;
+    }
+    string_buffer_init(ctx, b, 0);
+
+    for(i = 0; i < n; i++) {
+        if (i > 0) {
+            if (c >= 0) {
+                string_buffer_putc8(b, c);
+            } else {
+                string_buffer_concat(b, p, 0, p->len);
+            }
+        }
+        el = JS_GetPropertyUint32(ctx, obj, i);
+        if (JS_IsException(el))
+            goto fail;
+        if (!JS_IsNull(el) && !JS_IsUndefined(el)) {
+            if (toLocaleString) {
+                el = JS_ToLocaleStringFree(ctx, el);
+            }
+            if (string_buffer_concat_value_free(b, el))
+                goto fail;
+        }
+    }
+    JS_FreeValue(ctx, sep);
+    JS_FreeValue(ctx, obj);
+    return string_buffer_end(b);
+
+fail:
+    string_buffer_free(b);
+    JS_FreeValue(ctx, sep);
+exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_pop(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv, int shift)
+{
+    JSValue obj, res = JS_UNDEFINED;
+    int64_t len, newLen;
+    JSValue *arrp;
+    uint32_t count32;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+    newLen = 0;
+    if (len > 0) {
+        newLen = len - 1;
+        /* Special case fast arrays */
+        if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
+            JSObject *p = JS_VALUE_GET_OBJ(obj);
+            if (shift) {
+                res = arrp[0];
+                memmove(arrp, arrp + 1, (count32 - 1) * sizeof(*arrp));
+                p->u.array.count--;
+            } else {
+                res = arrp[count32 - 1];
+                p->u.array.count--;
+            }
+        } else {
+            if (shift) {
+                res = JS_GetPropertyInt64(ctx, obj, 0);
+                if (JS_IsException(res))
+                    goto exception;
+                if (JS_CopySubArray(ctx, obj, 0, 1, len - 1, +1))
+                    goto exception;
+            } else {
+                res = JS_GetPropertyInt64(ctx, obj, newLen);
+                if (JS_IsException(res))
+                    goto exception;
+            }
+            if (JS_DeletePropertyInt64(ctx, obj, newLen, JS_PROP_THROW) < 0)
+                goto exception;
+        }
+    }
+    if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
+        goto exception;
+
+    JS_FreeValue(ctx, obj);
+    return res;
+
+ exception:
+    JS_FreeValue(ctx, res);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_push(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv, int unshift)
+{
+    JSValue obj;
+    int i;
+    int64_t len, from, newLen;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+    newLen = len + argc;
+    if (newLen > MAX_SAFE_INTEGER) {
+        JS_ThrowTypeError(ctx, "Array loo long");
+        goto exception;
+    }
+    from = len;
+    if (unshift && argc > 0) {
+        if (JS_CopySubArray(ctx, obj, argc, 0, len, -1))
+            goto exception;
+        from = 0;
+    }
+    for(i = 0; i < argc; i++) {
+        if (JS_SetPropertyInt64(ctx, obj, from + i,
+                                JS_DupValue(ctx, argv[i])) < 0)
+            goto exception;
+    }
+    if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
+        goto exception;
+
+    JS_FreeValue(ctx, obj);
+    return JS_NewInt64(ctx, newLen);
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_reverse(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue obj, lval, hval;
+    JSValue *arrp;
+    int64_t len, l, h;
+    int l_present, h_present;
+    uint32_t count32;
+
+    lval = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    /* Special case fast arrays */
+    if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
+        uint32_t ll, hh;
+
+        if (count32 > 1) {
+            for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) {
+                lval = arrp[ll];
+                arrp[ll] = arrp[hh];
+                arrp[hh] = lval;
+            }
+        }
+        return obj;
+    }
+
+    for (l = 0, h = len - 1; l < h; l++, h--) {
+        l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval);
+        if (l_present < 0)
+            goto exception;
+        h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval);
+        if (h_present < 0)
+            goto exception;
+        if (h_present) {
+            if (JS_SetPropertyInt64(ctx, obj, l, hval) < 0)
+                goto exception;
+
+            if (l_present) {
+                if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) {
+                    lval = JS_UNDEFINED;
+                    goto exception;
+                }
+                lval = JS_UNDEFINED;
+            } else {
+                if (JS_DeletePropertyInt64(ctx, obj, h, JS_PROP_THROW) < 0)
+                    goto exception;
+            }
+        } else {
+            if (l_present) {
+                if (JS_DeletePropertyInt64(ctx, obj, l, JS_PROP_THROW) < 0)
+                    goto exception;
+                if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) {
+                    lval = JS_UNDEFINED;
+                    goto exception;
+                }
+                lval = JS_UNDEFINED;
+            }
+        }
+    }
+    return obj;
+
+ exception:
+    JS_FreeValue(ctx, lval);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int splice)
+{
+    JSValue obj, arr, val, len_val;
+    int64_t len, start, k, final, n, count, del_count, new_len;
+    int kPresent;
+    JSValue *arrp;
+    uint32_t count32, i, item_count;
+
+    arr = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
+        goto exception;
+
+    if (splice) {
+        if (argc == 0) {
+            item_count = 0;
+            del_count = 0;
+        } else
+        if (argc == 1) {
+            item_count = 0;
+            del_count = len - start;
+        } else {
+            item_count = argc - 2;
+            if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0))
+                goto exception;
+        }
+        if (len + item_count - del_count > MAX_SAFE_INTEGER) {
+            JS_ThrowTypeError(ctx, "Array loo long");
+            goto exception;
+        }
+        count = del_count;
+    } else {
+        item_count = 0; /* avoid warning */
+        final = len;
+        if (!JS_IsUndefined(argv[1])) {
+            if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len))
+                goto exception;
+        }
+        count = max_int64(final - start, 0);
+    }
+    len_val = JS_NewInt64(ctx, count);
+    arr = JS_ArraySpeciesCreate(ctx, obj, len_val);
+    JS_FreeValue(ctx, len_val);
+    if (JS_IsException(arr))
+        goto exception;
+
+    k = start;
+    final = start + count;
+    n = 0;
+    /* The fast array test on arr ensures that
+       JS_CreateDataPropertyUint32() won't modify obj in case arr is
+       an exotic object */
+    /* Special case fast arrays */
+    if (js_get_fast_array(ctx, obj, &arrp, &count32) &&
+        js_is_fast_array(ctx, arr)) {
+        /* XXX: should share code with fast array constructor */
+        for (; k < final && k < count32; k++, n++) {
+            if (JS_CreateDataPropertyUint32(ctx, arr, n, JS_DupValue(ctx, arrp[k]), JS_PROP_THROW) < 0)
+                goto exception;
+        }
+    }
+    /* Copy the remaining elements if any (handle case of inherited properties) */
+    for (; k < final; k++, n++) {
+        kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val);
+        if (kPresent < 0)
+            goto exception;
+        if (kPresent) {
+            if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0)
+                goto exception;
+        }
+    }
+    if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0)
+        goto exception;
+
+    if (splice) {
+        new_len = len + item_count - del_count;
+        if (item_count != del_count) {
+            if (JS_CopySubArray(ctx, obj, start + item_count,
+                                start + del_count, len - (start + del_count),
+                                item_count <= del_count ? +1 : -1) < 0)
+                goto exception;
+
+            for (k = len; k-- > new_len; ) {
+                if (JS_DeletePropertyInt64(ctx, obj, k, JS_PROP_THROW) < 0)
+                    goto exception;
+            }
+        }
+        for (i = 0; i < item_count; i++) {
+            if (JS_SetPropertyInt64(ctx, obj, start + i, JS_DupValue(ctx, argv[i + 2])) < 0)
+                goto exception;
+        }
+        if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, new_len)) < 0)
+            goto exception;
+    }
+    JS_FreeValue(ctx, obj);
+    return arr;
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    JS_FreeValue(ctx, arr);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_copyWithin(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    int64_t len, from, to, final, count;
+
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len))
+        goto exception;
+
+    if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len))
+        goto exception;
+
+    final = len;
+    if (argc > 2 && !JS_IsUndefined(argv[2])) {
+        if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len))
+            goto exception;
+    }
+
+    count = min_int64(final - from, len - to);
+
+    if (JS_CopySubArray(ctx, obj, to, from, count,
+                        (from < to && to < from + count) ? -1 : +1))
+        goto exception;
+
+    return obj;
+
+ exception:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static int64_t JS_FlattenIntoArray(JSContext *ctx, JSValueConst target,
+                                   JSValueConst source, int64_t sourceLen,
+                                   int64_t targetIndex, int depth,
+                                   JSValueConst mapperFunction,
+                                   JSValueConst thisArg)
+{
+    JSValue element;
+    int64_t sourceIndex, elementLen;
+    int present, is_array;
+
+    for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) {
+        present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element);
+        if (present < 0)
+            return -1;
+        if (!present)
+            continue;
+        if (!JS_IsUndefined(mapperFunction)) {
+            JSValueConst args[3] = { element, JS_NewInt64(ctx, sourceIndex), source };
+            element = JS_Call(ctx, mapperFunction, thisArg, 3, args);
+            JS_FreeValue(ctx, (JSValue)args[0]);
+            JS_FreeValue(ctx, (JSValue)args[1]);
+            if (JS_IsException(element))
+                return -1;
+        }
+        if (depth > 0) {
+            is_array = JS_IsArray(ctx, element);
+            if (is_array < 0)
+                goto fail;
+            if (is_array) {
+                if (js_get_length64(ctx, &elementLen, element) < 0)
+                    goto fail;
+                targetIndex = JS_FlattenIntoArray(ctx, target, element,
+                                                  elementLen, targetIndex,
+                                                  depth - 1,
+                                                  JS_UNDEFINED, JS_UNDEFINED);
+                if (targetIndex < 0)
+                    goto fail;
+                JS_FreeValue(ctx, element);
+                continue;
+            }
+        }
+        if (targetIndex >= MAX_SAFE_INTEGER) {
+            JS_ThrowTypeError(ctx, "Array too long");
+            goto fail;
+        }
+        if (JS_SetPropertyInt64(ctx, target, targetIndex, element) < 0)
+            return -1;
+        targetIndex++;
+    }
+    return targetIndex;
+
+fail:
+    JS_FreeValue(ctx, element);
+    return -1;
+}
+
+static JSValue js_array_flatten(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv, int map)
+{
+    JSValue obj, arr;
+    JSValueConst mapperFunction, thisArg;
+    int64_t sourceLen;
+    int depthNum;
+
+    arr = JS_UNDEFINED;
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &sourceLen, obj))
+        goto exception;
+
+    depthNum = 1;
+    mapperFunction = JS_UNDEFINED;
+    thisArg = JS_UNDEFINED;
+    if (map) {
+        mapperFunction = argv[0];
+        if (argc > 1) {
+            thisArg = argv[1];
+        }
+        if (check_function(ctx, mapperFunction))
+            goto exception;
+    } else {
+        if (argc > 0 && !JS_IsUndefined(argv[0])) {
+            if (JS_ToInt32Sat(ctx, &depthNum, argv[0]) < 0)
+                goto exception;
+        }
+    }
+    arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0));
+    if (JS_IsException(arr))
+        goto exception;
+    if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum,
+                            mapperFunction, thisArg) < 0)
+        goto exception;
+    JS_FreeValue(ctx, obj);
+    return arr;
+
+exception:
+    JS_FreeValue(ctx, obj);
+    JS_FreeValue(ctx, arr);
+    return JS_EXCEPTION;
+}
+
+/* Array sort */
+
+typedef struct ValueSlot {
+    JSValue val;
+    JSString *str;
+    int64_t pos;
+} ValueSlot;
+
+struct array_sort_context {
+    JSContext *ctx;
+    int exception;
+    int has_method;
+    JSValueConst method;
+};
+
+static int js_array_cmp_generic(const void *a, const void *b, void *opaque) {
+    struct array_sort_context *psc = opaque;
+    JSContext *ctx = psc->ctx;
+    JSValueConst argv[2];
+    JSValue res;
+    ValueSlot *ap = (ValueSlot *)(void *)a;
+    ValueSlot *bp = (ValueSlot *)(void *)b;
+    int cmp;
+
+    if (psc->exception)
+        return 0;
+
+    if (psc->has_method) {
+        /* custom sort function is specified as returning 0 for identical
+         * objects: avoid method call overhead.
+         */
+        if (!memcmp(&ap->val, &bp->val, sizeof(ap->val)))
+            goto cmp_same;
+        argv[0] = ap->val;
+        argv[1] = bp->val;
+        res = JS_Call(ctx, psc->method, JS_UNDEFINED, 2, argv);
+        if (JS_IsException(res))
+            goto exception;
+        if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) {
+            int val = JS_VALUE_GET_INT(res);
+            cmp = (val > 0) - (val < 0);
+        } else {
+            double val;
+            if (JS_ToFloat64Free(ctx, &val, res) < 0)
+                goto exception;
+            cmp = (val > 0) - (val < 0);
+        }
+    } else {
+        /* Not supposed to bypass ToString even for identical objects as
+         * tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.js
+         */
+        if (!ap->str) {
+            JSValue str = JS_ToString(ctx, ap->val);
+            if (JS_IsException(str))
+                goto exception;
+            ap->str = JS_VALUE_GET_STRING(str);
+        }
+        if (!bp->str) {
+            JSValue str = JS_ToString(ctx, bp->val);
+            if (JS_IsException(str))
+                goto exception;
+            bp->str = JS_VALUE_GET_STRING(str);
+        }
+        cmp = js_string_compare(ctx, ap->str, bp->str);
+    }
+    if (cmp != 0)
+        return cmp;
+cmp_same:
+    /* make sort stable: compare array offsets */
+    return (ap->pos > bp->pos) - (ap->pos < bp->pos);
+
+exception:
+    psc->exception = 1;
+    return 0;
+}
+
+static JSValue js_array_sort(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    struct array_sort_context asc = { ctx, 0, 0, argv[0] };
+    JSValue obj = JS_UNDEFINED;
+    ValueSlot *array = NULL;
+    size_t array_size = 0, pos = 0, n = 0;
+    int64_t i, len, undefined_count = 0;
+    int present;
+
+    if (!JS_IsUndefined(asc.method)) {
+        if (check_function(ctx, asc.method))
+            goto exception;
+        asc.has_method = 1;
+    }
+    obj = JS_ToObject(ctx, this_val);
+    if (js_get_length64(ctx, &len, obj))
+        goto exception;
+
+    /* XXX: should special case fast arrays */
+    for (i = 0; i < len; i++) {
+        if (pos >= array_size) {
+            size_t new_size, slack;
+            ValueSlot *new_array;
+            new_size = (array_size + (array_size >> 1) + 31) & ~15;
+            new_array = js_realloc2(ctx, array, new_size * sizeof(*array), &slack);
+            if (new_array == NULL)
+                goto exception;
+            new_size += slack / sizeof(*new_array);
+            array = new_array;
+            array_size = new_size;
+        }
+        present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val);
+        if (present < 0)
+            goto exception;
+        if (present == 0)
+            continue;
+        if (JS_IsUndefined(array[pos].val)) {
+            undefined_count++;
+            continue;
+        }
+        array[pos].str = NULL;
+        array[pos].pos = i;
+        pos++;
+    }
+    rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc);
+    if (asc.exception)
+        goto exception;
+
+    /* XXX: should special case fast arrays */
+    while (n < pos) {
+        if (array[n].str)
+            JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
+        if (array[n].pos == n) {
+            JS_FreeValue(ctx, array[n].val);
+        } else {
+            if (JS_SetPropertyInt64(ctx, obj, n, array[n].val) < 0) {
+                n++;
+                goto exception;
+            }
+        }
+        n++;
+    }
+    js_free(ctx, array);
+    for (i = n; undefined_count-- > 0; i++) {
+        if (JS_SetPropertyInt64(ctx, obj, i, JS_UNDEFINED) < 0)
+            goto fail;
+    }
+    for (; i < len; i++) {
+        if (JS_DeletePropertyInt64(ctx, obj, i, JS_PROP_THROW) < 0)
+            goto fail;
+    }
+    return obj;
+
+exception:
+    for (; n < pos; n++) {
+        JS_FreeValue(ctx, array[n].val);
+        if (array[n].str)
+            JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
+    }
+    js_free(ctx, array);
+fail:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+typedef struct JSArrayIteratorData {
+    JSValue obj;
+    JSIteratorKindEnum kind;
+    uint32_t idx;
+} JSArrayIteratorData;
+
+static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSArrayIteratorData *it = p->u.array_iterator_data;
+    if (it) {
+        JS_FreeValueRT(rt, it->obj);
+        js_free_rt(rt, it);
+    }
+}
+
+static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                   JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSArrayIteratorData *it = p->u.array_iterator_data;
+    if (it) {
+        JS_MarkValue(rt, it->obj, mark_func);
+    }
+}
+
+static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab)
+{
+    JSValue obj;
+    int i;
+
+    obj = JS_NewArray(ctx);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    for(i = 0; i < len; i++) {
+        if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, tab[i]), 0) < 0) {
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+    }
+    return obj;
+}
+
+static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv, int magic)
+{
+    JSValue enum_obj, arr;
+    JSArrayIteratorData *it;
+    JSIteratorKindEnum kind;
+    int class_id;
+
+    kind = magic & 3;
+    if (magic & 4) {
+        /* string iterator case */
+        arr = JS_ToStringCheckObject(ctx, this_val);
+        class_id = JS_CLASS_STRING_ITERATOR;
+    } else {
+        arr = JS_ToObject(ctx, this_val);
+        class_id = JS_CLASS_ARRAY_ITERATOR;
+    }
+    if (JS_IsException(arr))
+        goto fail;
+    enum_obj = JS_NewObjectClass(ctx, class_id);
+    if (JS_IsException(enum_obj))
+        goto fail;
+    it = js_malloc(ctx, sizeof(*it));
+    if (!it)
+        goto fail1;
+    it->obj = arr;
+    it->kind = kind;
+    it->idx = 0;
+    JS_SetOpaque(enum_obj, it);
+    return enum_obj;
+ fail1:
+    JS_FreeValue(ctx, enum_obj);
+ fail:
+    JS_FreeValue(ctx, arr);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv,
+                                      BOOL *pdone, int magic)
+{
+    JSArrayIteratorData *it;
+    uint32_t len, idx;
+    JSValue val, obj;
+    JSObject *p;
+
+    it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_ITERATOR);
+    if (!it)
+        goto fail1;
+    if (JS_IsUndefined(it->obj))
+        goto done;
+    p = JS_VALUE_GET_OBJ(it->obj);
+    if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+        p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+        if (typed_array_is_detached(ctx, p)) {
+            JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+            goto fail1;
+        }
+        len = p->u.array.count;
+    } else {
+        if (js_get_length32(ctx, &len, it->obj)) {
+        fail1:
+            *pdone = FALSE;
+            return JS_EXCEPTION;
+        }
+    }
+    idx = it->idx;
+    if (idx >= len) {
+        JS_FreeValue(ctx, it->obj);
+        it->obj = JS_UNDEFINED;
+    done:
+        *pdone = TRUE;
+        return JS_UNDEFINED;
+    }
+    it->idx = idx + 1;
+    *pdone = FALSE;
+    if (it->kind == JS_ITERATOR_KIND_KEY) {
+        return JS_NewUint32(ctx, idx);
+    } else {
+        val = JS_GetPropertyUint32(ctx, it->obj, idx);
+        if (JS_IsException(val))
+            return JS_EXCEPTION;
+        if (it->kind == JS_ITERATOR_KIND_VALUE) {
+            return val;
+        } else {
+            JSValueConst args[2];
+            JSValue num;
+            num = JS_NewUint32(ctx, idx);
+            args[0] = num;
+            args[1] = val;
+            obj = js_create_array(ctx, 2, args);
+            JS_FreeValue(ctx, val);
+            JS_FreeValue(ctx, num);
+            return obj;
+        }
+    }
+}
+
+static JSValue js_iterator_proto_iterator(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv)
+{
+    return JS_DupValue(ctx, this_val);
+}
+
+static const JSCFunctionListEntry js_iterator_proto_funcs[] = {
+    JS_CFUNC_DEF("[Symbol.iterator]", 0, js_iterator_proto_iterator ),
+};
+
+static const JSCFunctionListEntry js_array_proto_funcs[] = {
+    JS_CFUNC_DEF("concat", 1, js_array_concat ),
+    JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ),
+    JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ),
+    JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach ),
+    JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map ),
+    JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter ),
+    JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ),
+    JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ),
+    JS_CFUNC_DEF("fill", 1, js_array_fill ),
+    JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, 0 ),
+    JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, 1 ),
+    JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ),
+    JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ),
+    JS_CFUNC_DEF("includes", 1, js_array_includes ),
+    JS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0 ),
+    JS_CFUNC_DEF("toString", 0, js_array_toString ),
+    JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1 ),
+    JS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0 ),
+    JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ),
+    JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ),
+    JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ),
+    JS_CFUNC_DEF("reverse", 0, js_array_reverse ),
+    JS_CFUNC_DEF("sort", 1, js_array_sort ),
+    JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ),
+    JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ),
+    JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ),
+    JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ),
+    JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ),
+    JS_CFUNC_MAGIC_DEF("flatten", 0, js_array_flatten, 0 ),
+    JS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE ),
+    JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
+    JS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY ),
+    JS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ),
+};
+
+static const JSCFunctionListEntry js_array_iterator_proto_funcs[] = {
+    JS_ITERATOR_NEXT_DEF("next", 0, js_array_iterator_next, 0 ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Array Iterator", JS_PROP_CONFIGURABLE ),
+};
+
+/* Number */
+
+static JSValue js_number_constructor(JSContext *ctx, JSValueConst new_target,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue val, obj;
+#ifdef CONFIG_BIGNUM
+    if (argc == 0) {
+        if (is_bignum_mode(ctx))
+            val = __JS_NewFloat64(ctx, 0);
+        else
+            val = JS_NewInt32(ctx, 0);
+    } else {
+        val = JS_ToNumeric(ctx, argv[0]);
+        if (JS_IsException(val))
+            return val;
+        switch(JS_VALUE_GET_TAG(val)) {
+        case JS_TAG_BIG_INT:
+        case JS_TAG_BIG_FLOAT:
+            {
+                JSBigFloat *p = JS_VALUE_GET_PTR(val);
+                double d;
+                bf_get_float64(&p->num, &d, BF_RNDN);
+                JS_FreeValue(ctx, val);
+                val = __JS_NewFloat64(ctx, d);
+            }
+            break;
+        case JS_TAG_INT:
+            if (is_bignum_mode(ctx)) {
+                /* always return a number in bignum mode */
+                val = __JS_NewFloat64(ctx, JS_VALUE_GET_INT(val));
+            }
+            break;
+        default:
+            break;
+        }
+    }
+#else
+    if (argc == 0) {
+        val = JS_NewInt32(ctx, 0);
+    } else {
+        val = JS_ToNumber(ctx, argv[0]);
+        if (JS_IsException(val))
+            return val;
+    }
+#endif
+    if (!JS_IsUndefined(new_target)) {
+        obj = js_create_from_ctor(ctx, new_target, JS_CLASS_NUMBER);
+        if (!JS_IsException(obj))
+            JS_SetObjectData(ctx, obj, val);
+        return obj;
+    } else {
+        return val;
+    }
+}
+
+#if 0
+static JSValue js_number___toInteger(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    return JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv[0]));
+}
+
+static JSValue js_number___toLength(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    int64_t v;
+    if (JS_ToLengthFree(ctx, &v, JS_DupValue(ctx, argv[0])))
+        return JS_EXCEPTION;
+    return JS_NewInt64(ctx, v);
+}
+#endif
+
+static JSValue js_number_isNaN(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    /* XXX: should just check for float and big_float */
+    if (!JS_IsNumber(argv[0]))
+        return JS_FALSE;
+    return js_global_isNaN(ctx, this_val, argc, argv);
+}
+
+static JSValue js_number_isFinite(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    if (!JS_IsNumber(argv[0]))
+        return JS_FALSE;
+    return js_global_isFinite(ctx, this_val, argc, argv);
+}
+
+static JSValue js_number_isInteger(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    int ret;
+    ret = JS_NumberIsInteger(ctx, argv[0]);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_number_isSafeInteger(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    double d;
+    if (!JS_IsNumber(argv[0]))
+        return JS_FALSE;
+    if (unlikely(JS_ToFloat64(ctx, &d, argv[0])))
+        return JS_EXCEPTION;
+    return JS_NewBool(ctx, isfinite(d) && floor(d) == d &&
+                      fabs(d) <= (double)MAX_SAFE_INTEGER);
+}
+
+static const JSCFunctionListEntry js_number_funcs[] = {
+    /* global ParseInt and parseFloat should be defined already or delayed */
+    JS_ALIAS_BASE_DEF("parseInt", "parseInt", 0 ),
+    JS_ALIAS_BASE_DEF("parseFloat", "parseFloat", 0 ),
+    JS_CFUNC_DEF("isNaN", 1, js_number_isNaN ),
+    JS_CFUNC_DEF("isFinite", 1, js_number_isFinite ),
+    JS_CFUNC_DEF("isInteger", 1, js_number_isInteger ),
+    JS_CFUNC_DEF("isSafeInteger", 1, js_number_isSafeInteger ),
+    JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ),
+    JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ),
+    JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ),
+    JS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0 ),
+    JS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0 ),
+    JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0 ), /* ES6 */
+    JS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0 ), /* ES6 */
+    JS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0 ), /* ES6 */
+    //JS_CFUNC_DEF("__toInteger", 1, js_number___toInteger ),
+    //JS_CFUNC_DEF("__toLength", 1, js_number___toLength ),
+};
+
+static JSValue js_thisNumberValue(JSContext *ctx, JSValueConst this_val)
+{
+    if (JS_IsNumber(this_val))
+        return JS_DupValue(ctx, this_val);
+
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_NUMBER) {
+            if (JS_IsNumber(p->u.object_data))
+                return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a number");
+}
+
+static JSValue js_number_valueOf(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    return js_thisNumberValue(ctx, this_val);
+}
+
+static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv, int magic)
+{
+    JSValue val;
+    int base;
+
+    val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (magic || JS_IsUndefined(argv[0])) {
+        base = 10;
+    } else {
+        if (JS_ToInt32Sat(ctx, &base, argv[0]))
+            goto fail;
+        if (base < 2 || base > 36) {
+            JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
+            goto fail;
+        }
+    }
+#ifdef CONFIG_BIGNUM
+    {
+        JSValue ret = js_ftoa(ctx, val, base, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN);
+        JS_FreeValue(ctx, val);
+        return ret;
+    }
+#else
+    {
+        double d;
+        if (JS_ToFloat64Free(ctx, &d, val))
+            return JS_EXCEPTION;
+        return js_dtoa(ctx, d, base, 0, JS_DTOA_VAR_FORMAT);
+    }
+#endif
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+#ifdef CONFIG_BIGNUM
+
+static JSValue js_thisBigFloatValue(JSContext *ctx, JSValueConst this_val);
+
+static int64_t js_number_get_prec_max(JSContext *ctx, int magic)
+{
+    if (magic)
+        return BF_PREC_MAX;
+    else
+        return 100;
+}
+
+static int get_rnd_mode(JSContext *ctx, JSValueConst val)
+{
+    int rnd_mode;
+    if (JS_ToInt32Sat(ctx, &rnd_mode, val))
+        return -1;
+    if (rnd_mode < BF_RNDN || rnd_mode > BF_RNDF) {
+        JS_ThrowRangeError(ctx, "invalid rounding mode");
+        return -1;
+    }
+    return rnd_mode;
+}
+
+static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv, int magic)
+{
+    JSValue val, ret;
+    int64_t f;
+    int res, rnd_mode;
+    bf_t a_s, *a, b;
+    BOOL is_float;
+
+    if (magic)
+        val = js_thisBigFloatValue(ctx, this_val);
+    else
+        val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (JS_ToInt64Sat(ctx, &f, argv[0]))
+        goto fail;
+    if (f < 0 || f > js_number_get_prec_max(ctx, magic)) {
+        JS_ThrowRangeError(ctx, "invalid number of digits");
+        goto fail;
+    }
+    rnd_mode = BF_RNDNA;
+    if (magic && argc > 1) {
+        rnd_mode = get_rnd_mode(ctx, argv[1]);
+        if (rnd_mode < 0)
+            goto fail;
+    }
+
+    a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
+    if (!a)
+        goto fail;
+    bf_init(ctx->bf_ctx, &b);
+    bf_set_float64(&b, 1e21);
+    res = bf_cmpu(a, &b);
+    bf_delete(&b);
+    if (a == &a_s)
+        bf_delete(a);
+    if (res >= 0) {
+        ret = JS_ToString(ctx, val);
+    } else {
+        ret = js_ftoa(ctx, val, 10, f, rnd_mode | BF_FTOA_FORMAT_FRAC);
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv, int magic)
+{
+    JSValue val, ret;
+    int64_t f;
+    int rnd_mode;
+
+    if (magic)
+        val = js_thisBigFloatValue(ctx, this_val);
+    else
+        val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (JS_ToInt64Sat(ctx, &f, argv[0]))
+        goto fail;
+    if (!js_number_is_finite(ctx, val)) {
+        ret = JS_ToString(ctx, val);
+    } else if (JS_IsUndefined(argv[0])) {
+        ret = js_ftoa(ctx, val, 10, 0,
+                      BF_RNDN | BF_FTOA_FORMAT_FREE_MIN | BF_FTOA_FORCE_EXP);
+    } else {
+        if (f < 0 || f > js_number_get_prec_max(ctx, magic)) {
+            JS_ThrowRangeError(ctx, "invalid number of digits");
+            goto fail;
+        }
+        rnd_mode = BF_RNDNA;
+        if (magic && argc > 1) {
+            rnd_mode = get_rnd_mode(ctx, argv[1]);
+            if (rnd_mode < 0)
+                goto fail;
+        }
+        ret = js_ftoa(ctx, val, 10, f + 1,
+                      rnd_mode | BF_FTOA_FORMAT_FIXED | BF_FTOA_FORCE_EXP);
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv, int magic)
+{
+    JSValue val, ret;
+    int64_t p;
+    int rnd_mode;
+
+    if (magic)
+        val = js_thisBigFloatValue(ctx, this_val);
+    else
+        val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (JS_IsUndefined(argv[0]))
+        goto to_string;
+    if (JS_ToInt64Sat(ctx, &p, argv[0]))
+        goto fail;
+    if (!js_number_is_finite(ctx, val)) {
+    to_string:
+        ret = JS_ToString(ctx, this_val);
+    } else {
+        if (p < 1 || p > js_number_get_prec_max(ctx, magic)) {
+            JS_ThrowRangeError(ctx, "invalid number of digits");
+            goto fail;
+        }
+        rnd_mode = BF_RNDNA;
+        if (magic && argc > 1) {
+            rnd_mode = get_rnd_mode(ctx, argv[1]);
+            if (rnd_mode < 0)
+                goto fail;
+        }
+        ret = js_ftoa(ctx, val, 10, p, rnd_mode | BF_FTOA_FORMAT_FIXED);
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+
+}
+
+static const JSCFunctionListEntry js_number_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("toExponential", 1, js_number_toExponential, 0 ),
+    JS_CFUNC_MAGIC_DEF("toFixed", 1, js_number_toFixed, 0 ),
+    JS_CFUNC_MAGIC_DEF("toPrecision", 1, js_number_toPrecision, 0 ),
+    JS_CFUNC_MAGIC_DEF("toString", 1, js_number_toString, 0 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_number_toString, 1 ),
+    JS_CFUNC_DEF("valueOf", 0, js_number_valueOf ),
+};
+
+#else /* !CONFIG_BIGNUM */
+
+static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSValue val;
+    int f;
+    double d;
+
+    val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (JS_ToFloat64Free(ctx, &d, val))
+        return JS_EXCEPTION;
+    if (JS_ToInt32Sat(ctx, &f, argv[0]))
+        return JS_EXCEPTION;
+    if (f < 0 || f > 100)
+        return JS_ThrowRangeError(ctx, "invalid number of digits");
+    if (fabs(d) >= 1e21) {
+        return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d));
+    } else {
+        return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT);
+    }
+}
+
+static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSValue val;
+    int f, flags;
+    double d;
+
+    val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (JS_ToFloat64Free(ctx, &d, val))
+        return JS_EXCEPTION;
+    if (JS_ToInt32Sat(ctx, &f, argv[0]))
+        return JS_EXCEPTION;
+    if (!isfinite(d)) {
+        return JS_ToStringFree(ctx,  __JS_NewFloat64(ctx, d));
+    }
+    if (JS_IsUndefined(argv[0])) {
+        flags = 0;
+        f = 0;
+    } else {
+        if (f < 0 || f > 100)
+            return JS_ThrowRangeError(ctx, "invalid number of digits");
+        f++;
+        flags = JS_DTOA_FIXED_FORMAT;
+    }
+    return js_dtoa(ctx, d, 10, f, flags | JS_DTOA_FORCE_EXP);
+}
+
+static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue val;
+    int p;
+    double d;
+
+    val = js_thisNumberValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (JS_ToFloat64Free(ctx, &d, val))
+        return JS_EXCEPTION;
+    if (JS_IsUndefined(argv[0]))
+        goto to_string;
+    if (JS_ToInt32Sat(ctx, &p, argv[0]))
+        return JS_EXCEPTION;
+    if (!isfinite(d)) {
+    to_string:
+        return JS_ToStringFree(ctx,  __JS_NewFloat64(ctx, d));
+    }
+    if (p < 1 || p > 100)
+        return JS_ThrowRangeError(ctx, "invalid number of digits");
+    return js_dtoa(ctx, d, 10, p, JS_DTOA_FIXED_FORMAT);
+}
+
+static const JSCFunctionListEntry js_number_proto_funcs[] = {
+    JS_CFUNC_DEF("toExponential", 1, js_number_toExponential ),
+    JS_CFUNC_DEF("toFixed", 1, js_number_toFixed ),
+    JS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision ),
+    JS_CFUNC_MAGIC_DEF("toString", 1, js_number_toString, 0 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_number_toString, 1 ),
+    JS_CFUNC_DEF("valueOf", 0, js_number_valueOf ),
+};
+
+#endif /* !CONFIG_BIGNUM */
+
+
+static JSValue js_parseInt(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    const char *str;
+    int radix;
+    JSValue ret;
+
+    str = JS_ToCString(ctx, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &radix, argv[1])) {
+        JS_FreeCString(ctx, str);
+        return JS_EXCEPTION;
+    }
+    if (radix != 0 && (radix < 2 || radix > 36)) {
+        ret = JS_NAN;
+    } else {
+#ifdef CONFIG_BIGNUM
+        int flags;
+        flags = BF_ATOF_INT_ONLY | BF_ATOF_NAN_IF_EMPTY | BF_ATOF_FLOAT64;
+        if (is_bignum_mode(ctx))
+            flags |= BF_ATOF_INT_PREC_INF;
+        ret = js_atof(ctx, str, NULL, radix, flags);
+#else
+        ret = js_atod(ctx, str, NULL, radix, ATOD_INT_ONLY | ATOD_NAN_IF_EMPTY);
+#endif
+    }
+    JS_FreeCString(ctx, str);
+    return ret;
+}
+
+static JSValue js_parseFloat(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    const char *str;
+    JSValue ret;
+
+    str = JS_ToCString(ctx, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+#ifdef CONFIG_BIGNUM
+    ret = js_atof(ctx, str, NULL, 10,
+                  BF_ATOF_JS_QUIRKS | BF_ATOF_NAN_IF_EMPTY | BF_ATOF_FLOAT64);
+#else
+    ret = js_atod(ctx, str, NULL, 10, ATOD_NAN_IF_EMPTY);
+#endif
+    JS_FreeCString(ctx, str);
+    return ret;
+}
+
+/* Boolean */
+static JSValue js_boolean_constructor(JSContext *ctx, JSValueConst new_target,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue val, obj;
+    val = JS_NewBool(ctx, JS_ToBool(ctx, argv[0]));
+    if (!JS_IsUndefined(new_target)) {
+        obj = js_create_from_ctor(ctx, new_target, JS_CLASS_BOOLEAN);
+        if (!JS_IsException(obj))
+            JS_SetObjectData(ctx, obj, val);
+        return obj;
+    } else {
+        return val;
+    }
+}
+
+static JSValue js_thisBooleanValue(JSContext *ctx, JSValueConst this_val)
+{
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_BOOL)
+        return JS_DupValue(ctx, this_val);
+
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_BOOLEAN) {
+            if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_BOOL)
+                return p->u.object_data;
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a boolean");
+}
+
+static JSValue js_boolean_toString(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    JSValue val = js_thisBooleanValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ?
+                       JS_ATOM_true : JS_ATOM_false);
+}
+
+static JSValue js_boolean_valueOf(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    return js_thisBooleanValue(ctx, this_val);
+}
+
+static const JSCFunctionListEntry js_boolean_proto_funcs[] = {
+    JS_CFUNC_DEF("toString", 0, js_boolean_toString ),
+    JS_CFUNC_DEF("valueOf", 0, js_boolean_valueOf ),
+};
+
+/* String */
+
+static int js_string_get_own_property(JSContext *ctx,
+                                      JSPropertyDescriptor *desc,
+                                      JSValueConst obj, JSAtom prop)
+{
+    JSObject *p;
+    JSString *p1;
+    uint32_t idx, ch;
+
+    /* This is a class exotic method: obj class_id is JS_CLASS_STRING */
+    if (__JS_AtomIsTaggedInt(prop)) {
+        p = JS_VALUE_GET_OBJ(obj);
+        if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) {
+            p1 = JS_VALUE_GET_STRING(p->u.object_data);
+            idx = __JS_AtomToUInt32(prop);
+            if (idx < p1->len) {
+                if (desc) {
+                    if (p1->is_wide_char)
+                        ch = p1->u.str16[idx];
+                    else
+                        ch = p1->u.str8[idx];
+                    desc->flags = JS_PROP_ENUMERABLE;
+                    desc->value = js_new_string_char(ctx, ch);
+                    desc->getter = JS_UNDEFINED;
+                    desc->setter = JS_UNDEFINED;
+                }
+                return TRUE;
+            }
+        }
+    }
+    return FALSE;
+}
+
+static uint32_t js_string_obj_get_length(JSContext *ctx,
+                                         JSValueConst obj)
+{
+    JSObject *p;
+    JSString *p1;
+    uint32_t len = 0;
+
+    /* This is a class exotic method: obj class_id is JS_CLASS_STRING */
+    p = JS_VALUE_GET_OBJ(obj);
+    if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) {
+        p1 = JS_VALUE_GET_STRING(p->u.object_data);
+        len = p1->len;
+    }
+    return len;
+}
+
+static int js_string_get_own_property_names(JSContext *ctx,
+                                            JSPropertyEnum **ptab,
+                                            uint32_t *plen,
+                                            JSValueConst obj)
+{
+    JSPropertyEnum *tab;
+    uint32_t len, i;
+
+    len = js_string_obj_get_length(ctx, obj);
+    tab = NULL;
+    if (len > 0) {
+        /* do not allocate 0 bytes */
+        tab = js_malloc(ctx, sizeof(JSPropertyEnum) * len);
+        if (!tab)
+            return -1;
+        for(i = 0; i < len; i++) {
+            tab[i].atom = __JS_AtomFromUInt32(i);
+        }
+    }
+    *ptab = tab;
+    *plen = len;
+    return 0;
+}
+
+static int js_string_define_own_property(JSContext *ctx,
+                                         JSValueConst this_obj,
+                                         JSAtom prop, JSValueConst val,
+                                         JSValueConst getter,
+                                         JSValueConst setter, int flags)
+{
+    uint32_t idx;
+
+    if (__JS_AtomIsTaggedInt(prop)) {
+        idx = __JS_AtomToUInt32(prop);
+        if (idx >= js_string_obj_get_length(ctx, this_obj))
+            goto def;
+        if (!check_define_prop_flags(JS_PROP_ENUMERABLE, flags))
+            return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable");
+        /* XXX: should check if same value is configured */
+        return TRUE;
+    } else {
+    def:
+        return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter,
+                                 flags | JS_PROP_NO_EXOTIC);
+    }
+}
+
+static int js_string_delete_property(JSContext *ctx,
+                                     JSValueConst obj, JSAtom prop)
+{
+    uint32_t idx;
+
+    if (__JS_AtomIsTaggedInt(prop)) {
+        idx = __JS_AtomToUInt32(prop);
+        if (idx < js_string_obj_get_length(ctx, obj)) {
+            return FALSE;
+        }
+    }
+    return TRUE;
+}
+
+static const JSClassExoticMethods js_string_exotic_methods = {
+    .get_own_property = js_string_get_own_property,
+    .get_own_property_names = js_string_get_own_property_names,
+    .define_own_property = js_string_define_own_property,
+    .delete_property = js_string_delete_property,
+};
+
+static JSValue js_string_constructor(JSContext *ctx, JSValueConst new_target,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue val, obj;
+    if (argc == 0) {
+        val = JS_AtomToString(ctx, JS_ATOM_empty_string);
+    } else {
+        if (JS_IsUndefined(new_target) && JS_IsSymbol(argv[0])) {
+            JSAtomStruct *p = JS_VALUE_GET_PTR(argv[0]);
+            val = JS_ConcatString3(ctx, "Symbol(", JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p)), ")");
+        } else {
+            val = JS_ToString(ctx, argv[0]);
+        }
+        if (JS_IsException(val))
+            return val;
+    }
+    if (!JS_IsUndefined(new_target)) {
+        JSString *p1 = JS_VALUE_GET_STRING(val);
+
+        obj = js_create_from_ctor(ctx, new_target, JS_CLASS_STRING);
+        if (!JS_IsException(obj)) {
+            JS_SetObjectData(ctx, obj, val);
+            JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, JS_NewInt32(ctx, p1->len), 0);
+        }
+        return obj;
+    } else {
+        return val;
+    }
+}
+
+static JSValue js_thisStringValue(JSContext *ctx, JSValueConst this_val)
+{
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING)
+        return JS_DupValue(ctx, this_val);
+
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_STRING) {
+            if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING)
+                return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a string");
+}
+
+static JSValue js_string_fromCharCode(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv)
+{
+    int i;
+    StringBuffer b_s, *b = &b_s;
+
+    string_buffer_init(ctx, b, argc);
+
+    for(i = 0; i < argc; i++) {
+        int32_t c;
+        if (JS_ToInt32(ctx, &c, argv[i]) || string_buffer_putc16(b, c & 0xffff)) {
+            string_buffer_free(b);
+            return JS_EXCEPTION;
+        }
+    }
+    return string_buffer_end(b);
+}
+
+static JSValue js_string_fromCodePoint(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    double d;
+    int i, c;
+    StringBuffer b_s, *b = &b_s;
+
+    /* XXX: could pre-compute string length if all arguments are JS_TAG_INT */
+
+    if (string_buffer_init(ctx, b, argc))
+        goto fail;
+    for(i = 0; i < argc; i++) {
+        if (JS_VALUE_GET_TAG(argv[i]) == JS_TAG_INT) {
+            c = JS_VALUE_GET_INT(argv[i]);
+            if (c < 0 || c > 0x10ffff)
+                goto range_error;
+        } else {
+            if (JS_ToFloat64(ctx, &d, argv[i]))
+                goto fail;
+            if (d < 0 || d > 0x10ffff || (c = (int)d) != d)
+                goto range_error;
+        }
+        if (string_buffer_putc(b, c))
+            goto fail;
+    }
+    return string_buffer_end(b);
+
+ range_error:
+    JS_ThrowRangeError(ctx, "invalid code point");
+ fail:
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_raw(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    // raw(temp,...a)
+    JSValue cooked, val, raw;
+    StringBuffer b_s, *b = &b_s;
+    int64_t i, n;
+
+    string_buffer_init(ctx, b, 0);
+    raw = JS_UNDEFINED;
+    cooked = JS_ToObject(ctx, argv[0]);
+    if (JS_IsException(cooked))
+        goto exception;
+    raw = JS_ToObjectFree(ctx, JS_GetProperty(ctx, cooked, JS_ATOM_raw));
+    if (JS_IsException(raw))
+        goto exception;
+    if (js_get_length64(ctx, &n, raw) < 0)
+        goto exception;
+        
+    for (i = 0; i < n; i++) {
+        val = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, raw, i));
+        if (JS_IsException(val))
+            goto exception;
+        string_buffer_concat_value_free(b, val);
+        if (i < n - 1 && i + 1 < argc) {
+            if (string_buffer_concat_value(b, argv[i + 1]))
+                goto exception;
+        }
+    }
+    JS_FreeValue(ctx, cooked);
+    JS_FreeValue(ctx, raw);
+    return string_buffer_end(b);
+
+exception:
+    JS_FreeValue(ctx, cooked);
+    JS_FreeValue(ctx, raw);
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+/* only used in test262 */
+JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    uint32_t start, end, i, n;
+    StringBuffer b_s, *b = &b_s;
+
+    if (JS_ToUint32(ctx, &start, argv[0]) ||
+        JS_ToUint32(ctx, &end, argv[1]))
+        return JS_EXCEPTION;
+    end = min_uint32(end, 0x10ffff + 1);
+
+    if (start > end) {
+        start = end;
+    }
+    n = end - start;
+    if (end > 0x10000) {
+        n += end - max_uint32(start, 0x10000);
+    }
+    if (string_buffer_init2(ctx, b, n, end >= 0x100))
+        return JS_EXCEPTION;
+    for(i = start; i < end; i++) {
+        string_buffer_putc(b, i);
+    }
+    return string_buffer_end(b);
+}
+
+#if 0
+static JSValue js_string___isSpace(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    int c;
+    if (JS_ToInt32(ctx, &c, argv[0]))
+        return JS_EXCEPTION;
+    return JS_NewBool(ctx, lre_is_space(c));
+}
+#endif
+
+static JSValue js_string_charCodeAt(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue val, ret;
+    JSString *p;
+    int idx, c;
+
+    val = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_STRING(val);
+    if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
+        JS_FreeValue(ctx, val);
+        return JS_EXCEPTION;
+    }
+    if (idx < 0 || idx >= p->len) {
+        ret = JS_NAN;
+    } else {
+        if (p->is_wide_char)
+            c = p->u.str16[idx];
+        else
+            c = p->u.str8[idx];
+        ret = JS_NewInt32(ctx, c);
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static JSValue js_string_charAt(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue val, ret;
+    JSString *p;
+    int idx, c;
+
+    val = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_STRING(val);
+    if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
+        JS_FreeValue(ctx, val);
+        return JS_EXCEPTION;
+    }
+    if (idx < 0 || idx >= p->len) {
+        ret = js_new_string8(ctx, NULL, 0);
+    } else {
+        if (p->is_wide_char)
+            c = p->u.str16[idx];
+        else
+            c = p->u.str8[idx];
+        ret = js_new_string_char(ctx, c);
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static JSValue js_string_codePointAt(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue val, ret;
+    JSString *p;
+    int idx, c;
+
+    val = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_STRING(val);
+    if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
+        JS_FreeValue(ctx, val);
+        return JS_EXCEPTION;
+    }
+    if (idx < 0 || idx >= p->len) {
+        ret = JS_UNDEFINED;
+    } else {
+        c = string_getc(p, &idx);
+        ret = JS_NewInt32(ctx, c);
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static JSValue js_string_concat(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue r;
+    int i;
+
+    /* XXX: Use more efficient method */
+    /* XXX: This method is OK if r has a single refcount */
+    /* XXX: should use string_buffer? */
+    r = JS_ToStringCheckObject(ctx, this_val);
+    for (i = 0; i < argc; i++) {
+        if (JS_IsException(r))
+            break;
+        r = JS_ConcatString(ctx, r, JS_DupValue(ctx, argv[i]));
+    }
+    return r;
+}
+
+static int string_cmp(JSString *p1, JSString *p2, int x1, int x2, int len)
+{
+    int i, c1, c2;
+    for (i = 0; i < len; i++) {
+        if ((c1 = string_get(p1, x1 + i)) != (c2 = string_get(p2, x2 + i)))
+            return c1 - c2;
+    }
+    return 0;
+}
+
+static int string_indexof_char(JSString *p, int c, int from)
+{
+    /* assuming 0 <= from <= p->len */
+    int i, len = p->len;
+    if (p->is_wide_char) {
+        for (i = from; i < len; i++) {
+            if (p->u.str16[i] == c)
+                return i;
+        }
+    } else {
+        if ((c & ~0xff) == 0) {
+            for (i = from; i < len; i++) {
+                if (p->u.str8[i] == (uint8_t)c)
+                    return i;
+            }
+        }
+    }
+    return -1;
+}
+
+static int string_indexof(JSString *p1, JSString *p2, int from)
+{
+    /* assuming 0 <= from <= p1->len */
+    int c, i, j, len1 = p1->len, len2 = p2->len;
+    if (len2 == 0)
+        return from;
+    for (i = from, c = string_get(p2, 0); i + len2 <= len1; i = j + 1) {
+        j = string_indexof_char(p1, c, i);
+        if (j < 0 || j + len2 > len1)
+            break;
+        if (!string_cmp(p1, p2, j + 1, 1, len2 - 1))
+            return j;
+    }
+    return -1;
+}
+
+static int string_advance_index(JSString *p, int index, BOOL unicode)
+{
+    if (!unicode || (unsigned)index >= p->len || !p->is_wide_char) {
+        index++;
+    } else {
+        string_getc(p, &index);
+    }
+    return index;
+}
+
+static JSValue js_string_indexOf(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv, int lastIndexOf)
+{
+    JSValue str, v;
+    int i, len, v_len, pos, start, stop, ret, inc;
+    JSString *p;
+    JSString *p1;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return str;
+    v = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(v))
+        goto fail;
+    p = JS_VALUE_GET_STRING(str);
+    p1 = JS_VALUE_GET_STRING(v);
+    len = p->len;
+    v_len = p1->len;
+    if (lastIndexOf) {
+        pos = len - v_len;
+        if (argc > 1) {
+            double d;
+            if (JS_ToFloat64(ctx, &d, argv[1]))
+                goto fail;
+            if (!isnan(d)) {
+                if (d <= 0)
+                    pos = 0;
+                else if (d < pos)
+                    pos = d;
+            }
+        }
+        start = pos;
+        stop = 0;
+        inc = -1;
+    } else {
+        pos = 0;
+        if (argc > 1) {
+            if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0))
+                goto fail;
+        }
+        start = pos;
+        stop = len - v_len;
+        inc = 1;
+    }
+    ret = -1;
+    if (len >= v_len && inc * (stop - start) >= 0) {
+        for (i = start;; i += inc) {
+            if (!string_cmp(p, p1, i, 0, v_len)) {
+                ret = i;
+                break;
+            }
+            if (i == stop)
+                break;
+        }
+    }
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, v);
+    return JS_NewInt32(ctx, ret);
+
+fail:
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, v);
+    return JS_EXCEPTION;
+}
+
+/* return < 0 if exception or TRUE/FALSE */
+static int js_is_regexp(JSContext *ctx, JSValueConst obj);
+
+static JSValue js_string_includes(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv, int magic)
+{
+    JSValue str, v = JS_UNDEFINED;
+    int i, len, v_len, pos, start, stop, ret;
+    JSString *p;
+    JSString *p1;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return str;
+    ret = js_is_regexp(ctx, argv[0]);
+    if (ret) {
+        if (ret > 0)
+            JS_ThrowTypeError(ctx, "regex not supported");
+        goto fail;
+    }
+    v = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(v))
+        goto fail;
+    p = JS_VALUE_GET_STRING(str);
+    p1 = JS_VALUE_GET_STRING(v);
+    len = p->len;
+    v_len = p1->len;
+    pos = (magic & 2) ? len : 0;
+    if (argc > 1 && !JS_IsUndefined(argv[1])) {
+        if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0))
+            goto fail;
+    }
+    len -= v_len;
+    start = pos;
+    stop = len;
+    if (magic & 1) {
+        stop = pos;
+    }
+    if (magic & 2) {
+        pos -= v_len;
+        start = stop = pos;
+    }
+    ret = 0;
+    if (start >= 0 && start <= stop) {
+        for (i = start;; i++) {
+            if (!string_cmp(p, p1, i, 0, v_len)) {
+                ret = 1;
+                break;
+            }
+            if (i == stop)
+                break;
+        }
+    }
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, v);
+    return JS_NewBool(ctx, ret);
+
+fail:
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, v);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_match(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv, int atom)
+{
+    // match(rx), search(rx), matchAll(rx)
+    // atom is JS_ATOM_Symbol_match, JS_ATOM_Symbol_search, or JS_ATOM_Symbol_matchAll
+    JSValueConst O = this_val, regexp = argv[0], args[2];
+    JSValue matcher, S, rx, result, str;
+    int args_len;
+
+    if (JS_IsUndefined(O) || JS_IsNull(O))
+        return JS_ThrowTypeError(ctx, "cannot convert to object");
+
+    if (!JS_IsUndefined(regexp) && !JS_IsNull(regexp)) {
+        matcher = JS_GetProperty(ctx, regexp, atom);
+        if (JS_IsException(matcher))
+            return JS_EXCEPTION;
+        if (!JS_IsUndefined(matcher) && !JS_IsNull(matcher)) {
+            return JS_CallFree(ctx, matcher, regexp, 1, &O);
+        }
+    }
+    S = JS_ToString(ctx, O);
+    if (JS_IsException(S))
+        return JS_EXCEPTION;
+    args_len = 1;
+    args[0] = regexp;
+    str = JS_UNDEFINED;
+    if (atom == JS_ATOM_Symbol_matchAll) {
+        str = JS_NewString(ctx, "g");
+        if (JS_IsException(str))
+            goto fail;
+        args[args_len++] = (JSValueConst)str;
+    }
+    rx = JS_CallConstructor(ctx, ctx->regexp_ctor, args_len, args);
+    JS_FreeValue(ctx, str);
+    if (JS_IsException(rx)) {
+    fail:
+        JS_FreeValue(ctx, S);
+        return JS_EXCEPTION;
+    }
+    result = JS_InvokeFree(ctx, rx, atom, 1, (JSValueConst *)&S);
+    JS_FreeValue(ctx, S);
+    return result;
+}
+
+static JSValue js_string___GetSubstitution(JSContext *ctx, JSValueConst this_val,
+                                           int argc, JSValueConst *argv)
+{
+    // GetSubstitution(matched, str, position, captures, namedCaptures, rep)
+    JSValueConst matched, str, captures, namedCaptures, rep;
+    JSValue capture, name, s;
+    uint32_t position, len, matched_len, captures_len;
+    int i, j, j0, k, k1;
+    int c, c1;
+    StringBuffer b_s, *b = &b_s;
+    JSString *sp, *rp;
+
+    matched = argv[0];
+    str = argv[1];
+    captures = argv[3];
+    namedCaptures = argv[4];
+    rep = argv[5];
+
+    if (!JS_IsString(rep) || !JS_IsString(str))
+        return JS_ThrowTypeError(ctx, "not a string");
+
+    sp = JS_VALUE_GET_STRING(str);
+    rp = JS_VALUE_GET_STRING(rep);
+
+    string_buffer_init(ctx, b, 0);
+
+    captures_len = 0;
+    if (!JS_IsUndefined(captures)) {
+        if (js_get_length32(ctx, &captures_len, captures))
+            goto exception;
+    }
+    if (js_get_length32(ctx, &matched_len, matched))
+        goto exception;
+    if (JS_ToUint32(ctx, &position, argv[2]) < 0)
+        goto exception;
+
+    len = rp->len;
+    i = 0;
+    for(;;) {
+        j = string_indexof_char(rp, '$', i);
+        if (j < 0 || j + 1 >= len)
+            break;
+        string_buffer_concat(b, rp, i, j);
+        j0 = j++;
+        c = string_get(rp, j++);
+        if (c == '$') {
+            string_buffer_putc8(b, '$');
+        } else if (c == '&') {
+            if (string_buffer_concat_value(b, matched))
+                goto exception;
+        } else if (c == '`') {
+            string_buffer_concat(b, sp, 0, position);
+        } else if (c == '\'') {
+            string_buffer_concat(b, sp, position + matched_len, sp->len);
+        } else if (c >= '0' && c <= '9') {
+            k = c - '0';
+            c1 = string_get(rp, j);
+            if (c1 >= '0' && c1 <= '9') {
+                /* This behavior is specified in ES6 and refined in ECMA 2019 */
+                /* ECMA 2019 does not have the extra test, but
+                   Test262 S15.5.4.11_A3_T1..3 require this behavior */
+                k1 = k * 10 + c1 - '0';
+                if (k1 >= 1 && k1 < captures_len) {
+                    k = k1;
+                    j++;
+                }
+            }
+            if (k >= 1 && k < captures_len) {
+                s = JS_GetPropertyInt64(ctx, captures, k);
+                if (JS_IsException(s))
+                    goto exception;
+                if (!JS_IsUndefined(s)) {
+                    if (string_buffer_concat_value_free(b, s))
+                        goto exception;
+                }
+            } else {
+                goto norep;
+            }
+        } else if (c == '<' && !JS_IsUndefined(namedCaptures)) {
+            k = string_indexof_char(rp, '>', j);
+            if (k < 0)
+                goto norep;
+            name = js_sub_string(ctx, rp, j, k);
+            if (JS_IsException(name))
+                goto exception;
+            capture = JS_GetPropertyValue(ctx, namedCaptures, name);
+            if (JS_IsException(capture))
+                goto exception;
+            if (!JS_IsUndefined(capture)) {
+                if (string_buffer_concat_value_free(b, capture))
+                    goto exception;
+            }
+            j = k + 1;
+        } else {
+        norep:
+            string_buffer_concat(b, rp, j0, j);
+        }
+        i = j;
+    }
+    string_buffer_concat(b, rp, i, rp->len);
+    return string_buffer_end(b);
+exception:
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_replace(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    // replace(rx, rep)
+    JSValueConst O = this_val, searchValue = argv[0], replaceValue = argv[1];
+    JSValueConst args[6];
+    JSValue str, search_str, replaceValue_str, repl_str;
+    JSString *sp, *searchp;
+    StringBuffer b_s, *b = &b_s;
+    int pos, functionalReplace;
+
+    if (JS_IsUndefined(O) || JS_IsNull(O))
+        return JS_ThrowTypeError(ctx, "cannot convert to object");
+
+    search_str = JS_UNDEFINED;
+    replaceValue_str = JS_UNDEFINED;
+    repl_str = JS_UNDEFINED;
+
+    if (!JS_IsUndefined(searchValue) && !JS_IsNull(searchValue)) {
+        JSValue replacer;
+        replacer = JS_GetProperty(ctx, searchValue, JS_ATOM_Symbol_replace);
+        if (JS_IsException(replacer))
+            return JS_EXCEPTION;
+        if (!JS_IsUndefined(replacer)) {
+            args[0] = O;
+            args[1] = replaceValue;
+            return JS_CallFree(ctx, replacer, searchValue, 2, args);
+        }
+    }
+    string_buffer_init(ctx, b, 0);
+
+    str = JS_ToString(ctx, O);
+    if (JS_IsException(str))
+        goto exception;
+    search_str = JS_ToString(ctx, searchValue);
+    if (JS_IsException(search_str))
+        goto exception;
+    functionalReplace = JS_IsFunction(ctx, replaceValue);
+    if (!functionalReplace) {
+        replaceValue_str = JS_ToString(ctx, replaceValue);
+        if (JS_IsException(replaceValue_str))
+            goto exception;
+    }
+
+    sp = JS_VALUE_GET_STRING(str);
+    searchp = JS_VALUE_GET_STRING(search_str);
+
+    pos = string_indexof(sp, searchp, 0);
+    if (pos < 0) {
+        string_buffer_free(b);
+        JS_FreeValue(ctx, search_str);
+        JS_FreeValue(ctx, replaceValue_str);
+        return str;
+    }
+    if (functionalReplace) {
+        args[0] = search_str;
+        args[1] = JS_NewInt32(ctx, pos);
+        args[2] = str;
+        repl_str = JS_ToStringFree(ctx, JS_Call(ctx, replaceValue, JS_UNDEFINED, 3, args));
+    } else {
+        args[0] = search_str;
+        args[1] = str;
+        args[2] = JS_NewInt32(ctx, pos);
+        args[3] = JS_UNDEFINED;
+        args[4] = JS_UNDEFINED;
+        args[5] = replaceValue_str;
+        repl_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args);
+    }
+    if (JS_IsException(repl_str))
+        goto exception;
+
+    string_buffer_concat(b, sp, 0, pos);
+    string_buffer_concat_value_free(b, repl_str);
+    string_buffer_concat(b, sp, pos + searchp->len, sp->len);
+    JS_FreeValue(ctx, search_str);
+    JS_FreeValue(ctx, replaceValue_str);
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+
+exception:
+    string_buffer_free(b);
+    JS_FreeValue(ctx, search_str);
+    JS_FreeValue(ctx, replaceValue_str);
+    JS_FreeValue(ctx, str);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_split(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    // split(sep, limit)
+    JSValueConst O = this_val, separator = argv[0], limit = argv[1];
+    JSValueConst args[2];
+    JSValue S, A, R, T;
+    uint32_t lim, lengthA;
+    int64_t p, q, s, r, e;
+    JSString *sp, *rp;
+
+    if (JS_IsUndefined(O) || JS_IsNull(O))
+        return JS_ThrowTypeError(ctx, "cannot convert to object");
+
+    S = JS_UNDEFINED;
+    A = JS_UNDEFINED;
+    R = JS_UNDEFINED;
+
+    if (!JS_IsUndefined(separator) && !JS_IsNull(separator)) {
+        JSValue splitter;
+        splitter = JS_GetProperty(ctx, separator, JS_ATOM_Symbol_split);
+        if (JS_IsException(splitter))
+            return JS_EXCEPTION;
+        if (!JS_IsUndefined(splitter) && !JS_IsNull(splitter)) {
+            args[0] = O;
+            args[1] = limit;
+            return JS_CallFree(ctx, splitter, separator, 2, args);
+        }
+    }
+    S = JS_ToString(ctx, O);
+    if (JS_IsException(S))
+        goto exception;
+    A = JS_NewArray(ctx);
+    if (JS_IsException(A))
+        goto exception;
+    lengthA = 0;
+    if (JS_IsUndefined(limit)) {
+        lim = 0xffffffff;
+    } else {
+        if (JS_ToUint32(ctx, &lim, limit) < 0)
+            goto exception;
+    }
+    sp = JS_VALUE_GET_STRING(S);
+    s = sp->len;
+    R = JS_ToString(ctx, separator);
+    if (JS_IsException(R))
+        goto exception;
+    rp = JS_VALUE_GET_STRING(R);
+    r = rp->len;
+    p = 0;
+    if (lim == 0)
+        goto done;
+    if (JS_IsUndefined(separator))
+        goto add_tail;
+    if (s == 0) {
+        if (r != 0)
+            goto add_tail;
+        goto done;
+    }
+    q = p;
+    for (q = p; (q += !r) <= s - r - !r; q = p = e + r) {
+        e = string_indexof(sp, rp, q);
+        if (e < 0)
+            break;
+        T = js_sub_string(ctx, sp, p, e);
+        if (JS_IsException(T))
+            goto exception;
+        if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0)
+            goto exception;
+        if (lengthA == lim)
+            goto done;
+    }
+add_tail:
+    T = js_sub_string(ctx, sp, p, s);
+    if (JS_IsException(T))
+        goto exception;
+    if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T,0 ) < 0)
+        goto exception;
+done:
+    JS_FreeValue(ctx, S);
+    JS_FreeValue(ctx, R);
+    return A;
+
+exception:
+    JS_FreeValue(ctx, A);
+    JS_FreeValue(ctx, S);
+    JS_FreeValue(ctx, R);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_substring(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    JSValue str, ret;
+    int a, b, start, end;
+    JSString *p;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return str;
+    p = JS_VALUE_GET_STRING(str);
+    if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, p->len, 0)) {
+        JS_FreeValue(ctx, str);
+        return JS_EXCEPTION;
+    }
+    b = p->len;
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, p->len, 0)) {
+            JS_FreeValue(ctx, str);
+            return JS_EXCEPTION;
+        }
+    }
+    if (a < b) {
+        start = a;
+        end = b;
+    } else {
+        start = b;
+        end = a;
+    }
+    ret = js_sub_string(ctx, p, start, end);
+    JS_FreeValue(ctx, str);
+    return ret;
+}
+
+static JSValue js_string_substr(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue str, ret;
+    int a, len, n;
+    JSString *p;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return str;
+    p = JS_VALUE_GET_STRING(str);
+    len = p->len;
+    if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, len)) {
+        JS_FreeValue(ctx, str);
+        return JS_EXCEPTION;
+    }
+    n = len - a;
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_ToInt32Clamp(ctx, &n, argv[1], 0, len - a, 0)) {
+            JS_FreeValue(ctx, str);
+            return JS_EXCEPTION;
+        }
+    }
+    ret = js_sub_string(ctx, p, a, a + n);
+    JS_FreeValue(ctx, str);
+    return ret;
+}
+
+static JSValue js_string_slice(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    JSValue str, ret;
+    int len, start, end;
+    JSString *p;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return str;
+    p = JS_VALUE_GET_STRING(str);
+    len = p->len;
+    if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) {
+        JS_FreeValue(ctx, str);
+        return JS_EXCEPTION;
+    }
+    end = len;
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) {
+            JS_FreeValue(ctx, str);
+            return JS_EXCEPTION;
+        }
+    }
+    ret = js_sub_string(ctx, p, start, max_int(end, start));
+    JS_FreeValue(ctx, str);
+    return ret;
+}
+
+static JSValue js_string_pad(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv, int padEnd)
+{
+    JSValue str, v = JS_UNDEFINED;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p, *p1 = NULL;
+    int n, len, c = ' ';
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        goto fail1;
+    if (JS_ToInt32Sat(ctx, &n, argv[0]))
+        goto fail2;
+    p = JS_VALUE_GET_STRING(str);
+    len = p->len;
+    if (len >= n)
+        return str;
+    if (n > JS_STRING_LEN_MAX) {
+        JS_ThrowInternalError(ctx, "string too long");
+        goto fail2;
+    }
+    if (argc > 1 && !JS_IsUndefined(argv[1])) {
+        v = JS_ToString(ctx, argv[1]);
+        if (JS_IsException(v))
+            goto fail2;
+        p1 = JS_VALUE_GET_STRING(v);
+        if (p1->len == 0) {
+            JS_FreeValue(ctx, v);
+            return str;
+        }
+        if (p1->len == 1) {
+            c = string_get(p1, 0);
+            p1 = NULL;
+        }
+    }
+    if (string_buffer_init(ctx, b, n))
+        goto fail3;
+    n -= len;
+    if (padEnd) {
+        if (string_buffer_concat(b, p, 0, len))
+            goto fail;
+    }
+    if (p1) {
+        while (n > 0) {
+            int chunk = min_int(n, p1->len);
+            if (string_buffer_concat(b, p1, 0, chunk))
+                goto fail;
+            n -= chunk;
+        }
+    } else {
+        if (string_buffer_fill(b, c, n))
+            goto fail;
+    }
+    if (!padEnd) {
+        if (string_buffer_concat(b, p, 0, len))
+            goto fail;
+    }
+    JS_FreeValue(ctx, v);
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+
+fail:
+    string_buffer_free(b);
+fail3:
+    JS_FreeValue(ctx, v);
+fail2:
+    JS_FreeValue(ctx, str);
+fail1:
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_repeat(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue str;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p;
+    int64_t val;
+    int n, len;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        goto fail;
+    if (JS_ToInt64Sat(ctx, &val, argv[0]))
+        goto fail;
+    if (val < 0 || val > 2147483647) {
+        JS_ThrowRangeError(ctx, "invalid repeat count");
+        goto fail;
+    }
+    n = val;
+    p = JS_VALUE_GET_STRING(str);
+    len = p->len;
+    if (len == 0 || n == 1)
+        return str;
+    if (val * len > JS_STRING_LEN_MAX) {
+        JS_ThrowInternalError(ctx, "string too long");
+        goto fail;
+    }
+    if (string_buffer_init2(ctx, b, n * len, p->is_wide_char))
+        goto fail;
+    if (len == 1) {
+        string_buffer_fill(b, string_get(p, 0), n);
+    } else {
+        while (n-- > 0) {
+            string_buffer_concat(b, p, 0, len);
+        }
+    }
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+
+fail:
+    JS_FreeValue(ctx, str);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_trim(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int magic)
+{
+    JSValue str, ret;
+    int a, b, len;
+    JSString *p;
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return str;
+    p = JS_VALUE_GET_STRING(str);
+    a = 0;
+    b = len = p->len;
+    if (magic & 1) {
+        while (a < len && lre_is_space(string_get(p, a)))
+            a++;
+    }
+    if (magic & 2) {
+        while (b > a && lre_is_space(string_get(p, b - 1)))
+            b--;
+    }
+    ret = js_sub_string(ctx, p, a, b);
+    JS_FreeValue(ctx, str);
+    return ret;
+}
+
+static JSValue js_string___quote(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    return JS_ToQuotedString(ctx, this_val);
+}
+
+/* return 0 if before the first char */
+static int string_prevc(JSString *p, int *pidx)
+{
+    int idx, c, c1;
+
+    idx = *pidx;
+    if (idx <= 0)
+        return 0;
+    idx--;
+    if (p->is_wide_char) {
+        c = p->u.str16[idx];
+        if (c >= 0xdc00 && c < 0xe000 && idx > 0) {
+            c1 = p->u.str16[idx - 1];
+            if (c1 >= 0xd800 && c1 <= 0xdc00) {
+                c = (((c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000;
+                idx--;
+            }
+        }
+    } else {
+        c = p->u.str8[idx];
+    }
+    *pidx = idx;
+    return c;
+}
+
+static BOOL test_final_sigma(JSString *p, int sigma_pos)
+{
+    int k, c1;
+
+    /* before C: skip case ignorable chars and check there is
+       a cased letter */
+    k = sigma_pos;
+    for(;;) {
+        c1 = string_prevc(p, &k);
+        if (!lre_is_case_ignorable(c1))
+            break;
+    }
+    if (!lre_is_cased(c1))
+        return FALSE;
+
+    /* after C: skip case ignorable chars and check there is
+       no cased letter */
+    k = sigma_pos + 1;
+    for(;;) {
+        if (k >= p->len)
+            return TRUE;
+        c1 = string_getc(p, &k);
+        if (!lre_is_case_ignorable(c1))
+            break;
+    }
+    return !lre_is_cased(c1);
+}
+
+static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSValue a, b;
+    int cmp;
+
+    a = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(a))
+        return JS_EXCEPTION;
+    b = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(b)) {
+        JS_FreeValue(ctx, a);
+        return JS_EXCEPTION;
+    }
+    cmp = js_string_compare(ctx, JS_VALUE_GET_STRING(a), JS_VALUE_GET_STRING(b));
+    JS_FreeValue(ctx, a);
+    JS_FreeValue(ctx, b);
+    return JS_NewInt32(ctx, cmp);
+}
+
+static JSValue js_string_toLowerCase(JSContext *ctx, JSValueConst this_val,
+                                     int argc, JSValueConst *argv, int to_lower)
+{
+    JSValue val;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p;
+    int i, c, j, l;
+    uint32_t res[LRE_CC_RES_LEN_MAX];
+
+    val = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_STRING(val);
+    if (p->len == 0)
+        return val;
+    if (string_buffer_init(ctx, b, p->len))
+        goto fail;
+    for(i = 0; i < p->len;) {
+        c = string_getc(p, &i);
+        if (c == 0x3a3 && to_lower && test_final_sigma(p, i - 1)) {
+            res[0] = 0x3c2; /* final sigma */
+            l = 1;
+        } else {
+            l = lre_case_conv(res, c, to_lower);
+        }
+        for(j = 0; j < l; j++) {
+            if (string_buffer_putc(b, res[j]))
+                goto fail;
+        }
+    }
+    JS_FreeValue(ctx, val);
+    return string_buffer_end(b);
+ fail:
+    JS_FreeValue(ctx, val);
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+#ifdef CONFIG_ALL_UNICODE
+
+/* return (-1, NULL) if exception, otherwise (len, buf) */
+static int JS_ToUTF32String(JSContext *ctx, uint32_t **pbuf, JSValueConst val1)
+{
+    JSValue val;
+    JSString *p;
+    uint32_t *buf;
+    int i, j, len;
+
+    val = JS_ToString(ctx, val1);
+    if (JS_IsException(val))
+        return -1;
+    p = JS_VALUE_GET_STRING(val);
+    len = p->len;
+    /* UTF32 buffer length is len minus the number of correct surrogates pairs */
+    buf = js_malloc(ctx, sizeof(buf[0]) * max_int(len, 1));
+    if (!buf) {
+        JS_FreeValue(ctx, val);
+        goto fail;
+    }
+    for(i = j = 0; i < len;)
+        buf[j++] = string_getc(p, &i);
+    JS_FreeValue(ctx, val);
+    *pbuf = buf;
+    return j;
+ fail:
+    *pbuf = NULL;
+    return -1;
+}
+
+static JSValue JS_NewUTF32String(JSContext *ctx, const uint32_t *buf, int len)
+{
+    int i;
+    StringBuffer b_s, *b = &b_s;
+    if (string_buffer_init(ctx, b, len))
+        return JS_EXCEPTION;
+    for(i = 0; i < len; i++) {
+        if (string_buffer_putc(b, buf[i]))
+            goto fail;
+    }
+    return string_buffer_end(b);
+ fail:
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_string_normalize(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    const char *form, *p;
+    size_t form_len;
+    int is_compat, buf_len, out_len;
+    UnicodeNormalizationEnum n_type;
+    JSValue val;
+    uint32_t *buf, *out_buf;
+
+    val = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    buf_len = JS_ToUTF32String(ctx, &buf, val);
+    JS_FreeValue(ctx, val);
+    if (buf_len < 0)
+        return JS_EXCEPTION;
+
+    if (argc == 0 || JS_IsUndefined(argv[0])) {
+        n_type = UNICODE_NFC;
+    } else {
+        form = JS_ToCStringLen(ctx, &form_len, argv[0]);
+        if (!form)
+            goto fail1;
+        p = form;
+        if (p[0] != 'N' || p[1] != 'F')
+            goto bad_form;
+        p += 2;
+        is_compat = FALSE;
+        if (*p == 'K') {
+            is_compat = TRUE;
+            p++;
+        }
+        if (*p == 'C' || *p == 'D') {
+            n_type = UNICODE_NFC + is_compat * 2 + (*p - 'C');
+            if ((p + 1 - form) != form_len)
+                goto bad_form;
+        } else {
+        bad_form:
+            JS_FreeCString(ctx, form);
+            JS_ThrowRangeError(ctx, "bad normalization form");
+        fail1:
+            js_free(ctx, buf);
+            return JS_EXCEPTION;
+        }
+        JS_FreeCString(ctx, form);
+    }
+
+    out_len = unicode_normalize(&out_buf, buf, buf_len, n_type,
+                                ctx->rt, (DynBufReallocFunc *)js_realloc_rt);
+    js_free(ctx, buf);
+    if (out_len < 0)
+        return JS_EXCEPTION;
+    val = JS_NewUTF32String(ctx, out_buf, out_len);
+    js_free(ctx, out_buf);
+    return val;
+}
+#endif /* CONFIG_ALL_UNICODE */
+
+/* also used for String.prototype.valueOf */
+static JSValue js_string_toString(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    return js_thisStringValue(ctx, this_val);
+}
+
+#if 0
+static JSValue js_string___toStringCheckObject(JSContext *ctx, JSValueConst this_val,
+                                               int argc, JSValueConst *argv)
+{
+    return JS_ToStringCheckObject(ctx, argv[0]);
+}
+
+static JSValue js_string___toString(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    return JS_ToString(ctx, argv[0]);
+}
+
+static JSValue js_string___advanceStringIndex(JSContext *ctx, JSValueConst
+                                              this_val,
+                                              int argc, JSValueConst *argv)
+{
+    JSValue str;
+    int idx;
+    BOOL is_unicode;
+    JSString *p;
+
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        return str;
+    if (JS_ToInt32Sat(ctx, &idx, argv[1])) {
+        JS_FreeValue(ctx, str);
+        return JS_EXCEPTION;
+    }
+    is_unicode = JS_ToBool(ctx, argv[2]);
+    p = JS_VALUE_GET_STRING(str);
+    if (!is_unicode || (unsigned)idx >= p->len || !p->is_wide_char) {
+        idx++;
+    } else {
+        string_getc(p, &idx);
+    }
+    JS_FreeValue(ctx, str);
+    return JS_NewInt32(ctx, idx);
+}
+#endif
+
+/* String Iterator */
+
+static JSValue js_string_iterator_next(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv,
+                                       BOOL *pdone, int magic)
+{
+    JSArrayIteratorData *it;
+    uint32_t idx, c, start;
+    JSString *p;
+
+    it = JS_GetOpaque2(ctx, this_val, JS_CLASS_STRING_ITERATOR);
+    if (!it) {
+        *pdone = FALSE;
+        return JS_EXCEPTION;
+    }
+    if (JS_IsUndefined(it->obj))
+        goto done;
+    p = JS_VALUE_GET_STRING(it->obj);
+    idx = it->idx;
+    if (idx >= p->len) {
+        JS_FreeValue(ctx, it->obj);
+        it->obj = JS_UNDEFINED;
+    done:
+        *pdone = TRUE;
+        return JS_UNDEFINED;
+    }
+
+    start = idx;
+    c = string_getc(p, (int *)&idx);
+    it->idx = idx;
+    *pdone = FALSE;
+    if (c <= 0xffff) {
+        return js_new_string_char(ctx, c);
+    } else {
+        return js_new_string16(ctx, p->u.str16 + start, 2);
+    }
+}
+
+/* ES6 Annex B 2.3.2 etc. */
+enum {
+    magic_string_anchor,
+    magic_string_big,
+    magic_string_blink,
+    magic_string_bold,
+    magic_string_fixed,
+    magic_string_fontcolor,
+    magic_string_fontsize,
+    magic_string_italics,
+    magic_string_link,
+    magic_string_small,
+    magic_string_strike,
+    magic_string_sub,
+    magic_string_sup,
+};
+
+static JSValue js_string_CreateHTML(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv, int magic)
+{
+    JSValue str;
+    const JSString *p;
+    StringBuffer b_s, *b = &b_s;
+    static struct { const char *tag, *attr; } const defs[] = {
+        { "a", "name" }, { "big", NULL }, { "blink", NULL }, { "b", NULL },
+        { "tt", NULL }, { "font", "color" }, { "font", "size" }, { "i", NULL },
+        { "a", "href" }, { "small", NULL }, { "strike", NULL }, 
+        { "sub", NULL }, { "sup", NULL },
+    };
+
+    str = JS_ToStringCheckObject(ctx, this_val);
+    if (JS_IsException(str))
+        return JS_EXCEPTION;
+    string_buffer_init(ctx, b, 7);
+    string_buffer_putc8(b, '<');
+    string_buffer_puts8(b, defs[magic].tag);
+    if (defs[magic].attr) {
+        // r += " " + attr + "=\"" + value + "\"";
+        JSValue value;
+        int i;
+
+        string_buffer_putc8(b, ' ');
+        string_buffer_puts8(b, defs[magic].attr);
+        string_buffer_puts8(b, "=\"");
+        value = JS_ToStringCheckObject(ctx, argv[0]);
+        if (JS_IsException(value)) {
+            JS_FreeValue(ctx, str);
+            string_buffer_free(b);
+            return JS_EXCEPTION;
+        }
+        p = JS_VALUE_GET_STRING(value);
+        for (i = 0; i < p->len; i++) {
+            int c = string_get(p, i);
+            if (c == '"') {
+                string_buffer_puts8(b, "&quot;");
+            } else {
+                string_buffer_putc16(b, c);
+            }
+        }
+        JS_FreeValue(ctx, value);
+        string_buffer_putc8(b, '\"');
+    }
+    // return r + ">" + str + "</" + tag + ">";
+    string_buffer_putc8(b, '>');
+    string_buffer_concat_value_free(b, str);
+    string_buffer_puts8(b, "</");
+    string_buffer_puts8(b, defs[magic].tag);
+    string_buffer_putc8(b, '>');
+    return string_buffer_end(b);
+}
+
+static const JSCFunctionListEntry js_string_funcs[] = {
+    JS_CFUNC_DEF("fromCharCode", 1, js_string_fromCharCode ),
+    JS_CFUNC_DEF("fromCodePoint", 1, js_string_fromCodePoint ),
+    JS_CFUNC_DEF("raw", 1, js_string_raw ),
+    //JS_CFUNC_DEF("__toString", 1, js_string___toString ),
+    //JS_CFUNC_DEF("__isSpace", 1, js_string___isSpace ),
+    //JS_CFUNC_DEF("__toStringCheckObject", 1, js_string___toStringCheckObject ),
+    //JS_CFUNC_DEF("__advanceStringIndex", 3, js_string___advanceStringIndex ),
+    //JS_CFUNC_DEF("__GetSubstitution", 6, js_string___GetSubstitution ),
+};
+
+static const JSCFunctionListEntry js_string_proto_funcs[] = {
+    JS_PROP_INT32_DEF("length", 0, JS_PROP_CONFIGURABLE ),
+    JS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt ),
+    JS_CFUNC_DEF("charAt", 1, js_string_charAt ),
+    JS_CFUNC_DEF("concat", 1, js_string_concat ),
+    JS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt ),
+    JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ),
+    JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ),
+    JS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0 ),
+    JS_CFUNC_MAGIC_DEF("endsWith", 1, js_string_includes, 2 ),
+    JS_CFUNC_MAGIC_DEF("startsWith", 1, js_string_includes, 1 ),
+    JS_CFUNC_MAGIC_DEF("match", 1, js_string_match, JS_ATOM_Symbol_match ),
+    JS_CFUNC_MAGIC_DEF("matchAll", 1, js_string_match, JS_ATOM_Symbol_matchAll ),
+    JS_CFUNC_MAGIC_DEF("search", 1, js_string_match, JS_ATOM_Symbol_search ),
+    JS_CFUNC_DEF("split", 2, js_string_split ),
+    JS_CFUNC_DEF("substring", 2, js_string_substring ),
+    JS_CFUNC_DEF("substr", 2, js_string_substr ),
+    JS_CFUNC_DEF("slice", 2, js_string_slice ),
+    JS_CFUNC_DEF("repeat", 1, js_string_repeat ),
+    JS_CFUNC_DEF("replace", 2, js_string_replace ),
+    JS_CFUNC_MAGIC_DEF("padEnd", 1, js_string_pad, 1 ),
+    JS_CFUNC_MAGIC_DEF("padStart", 1, js_string_pad, 0 ),
+    JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ),
+    JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ),
+    JS_ALIAS_DEF("trimRight", "trimEnd" ),
+    JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ),
+    JS_ALIAS_DEF("trimLeft", "trimStart" ),
+    JS_CFUNC_DEF("toString", 0, js_string_toString ),
+    JS_CFUNC_DEF("valueOf", 0, js_string_toString ),
+    JS_CFUNC_DEF("__quote", 1, js_string___quote ),
+    JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ),
+    JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ),
+    JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleUpperCase", 0, js_string_toLowerCase, 0 ),
+    JS_CFUNC_MAGIC_DEF("[Symbol.iterator]", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE | 4 ),
+    /* ES6 Annex B 2.3.2 etc. */
+    JS_CFUNC_MAGIC_DEF("anchor", 1, js_string_CreateHTML, magic_string_anchor ),
+    JS_CFUNC_MAGIC_DEF("big", 0, js_string_CreateHTML, magic_string_big ),
+    JS_CFUNC_MAGIC_DEF("blink", 0, js_string_CreateHTML, magic_string_blink ),
+    JS_CFUNC_MAGIC_DEF("bold", 0, js_string_CreateHTML, magic_string_bold ),
+    JS_CFUNC_MAGIC_DEF("fixed", 0, js_string_CreateHTML, magic_string_fixed ),
+    JS_CFUNC_MAGIC_DEF("fontcolor", 1, js_string_CreateHTML, magic_string_fontcolor ),
+    JS_CFUNC_MAGIC_DEF("fontsize", 1, js_string_CreateHTML, magic_string_fontsize ),
+    JS_CFUNC_MAGIC_DEF("italics", 0, js_string_CreateHTML, magic_string_italics ),
+    JS_CFUNC_MAGIC_DEF("link", 1, js_string_CreateHTML, magic_string_link ),
+    JS_CFUNC_MAGIC_DEF("small", 0, js_string_CreateHTML, magic_string_small ),
+    JS_CFUNC_MAGIC_DEF("strike", 0, js_string_CreateHTML, magic_string_strike ),
+    JS_CFUNC_MAGIC_DEF("sub", 0, js_string_CreateHTML, magic_string_sub ),
+    JS_CFUNC_MAGIC_DEF("sup", 0, js_string_CreateHTML, magic_string_sup ),
+};
+
+static const JSCFunctionListEntry js_string_iterator_proto_funcs[] = {
+    JS_ITERATOR_NEXT_DEF("next", 0, js_string_iterator_next, 0 ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "String Iterator", JS_PROP_CONFIGURABLE ),
+};
+
+#ifdef CONFIG_ALL_UNICODE
+static const JSCFunctionListEntry js_string_proto_normalize[] = {
+    JS_CFUNC_DEF("normalize", 0, js_string_normalize ),
+};
+#endif
+
+void JS_AddIntrinsicStringNormalize(JSContext *ctx)
+{
+#ifdef CONFIG_ALL_UNICODE
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_normalize,
+                               countof(js_string_proto_normalize));
+#endif
+}
+
+/* Math */
+
+/* precondition: a and b are not NaN */
+static double js_fmin(double a, double b)
+{
+    if (a == 0 && b == 0) {
+        JSFloat64Union a1, b1;
+        a1.d = a;
+        b1.d = b;
+        a1.u64 |= b1.u64;
+        return a1.d;
+    } else {
+        return fmin(a, b);
+    }
+}
+
+/* precondition: a and b are not NaN */
+static double js_fmax(double a, double b)
+{
+    if (a == 0 && b == 0) {
+        JSFloat64Union a1, b1;
+        a1.d = a;
+        b1.d = b;
+        a1.u64 &= b1.u64;
+        return a1.d;
+    } else {
+        return fmax(a, b);
+    }
+}
+
+#ifdef CONFIG_BIGNUM
+
+enum {
+    MATH_OP_ABS,
+    MATH_OP_FLOOR,
+    MATH_OP_CEIL,
+    MATH_OP_ROUND,
+    MATH_OP_TRUNC,
+    MATH_OP_SQRT,
+    MATH_OP_FPROUND,
+    MATH_OP_ACOS,
+    MATH_OP_ASIN,
+    MATH_OP_ATAN,
+    MATH_OP_ATAN2,
+    MATH_OP_COS,
+    MATH_OP_EXP,
+    MATH_OP_LOG,
+    MATH_OP_POW,
+    MATH_OP_SIN,
+    MATH_OP_TAN,
+    MATH_OP_FMOD,
+    MATH_OP_REM,
+    MATH_OP_SIGN,
+
+    MATH_OP_ADD,
+    MATH_OP_SUB,
+    MATH_OP_MUL,
+    MATH_OP_DIV,
+};
+
+static JSValue js_math_fop(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv, int magic)
+{
+    bf_t a_s, *a, r_s, *r = &r_s;
+    BOOL is_float;
+    JSFloatEnv *fe;
+    int rnd_mode;
+    JSValue op1;
+
+    op1 = JS_ToNumber(ctx, argv[0]);
+    if (JS_IsException(op1))
+        return op1;
+    a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+    fe = &ctx->fp_env;
+    if (argc > 1) {
+        fe = JS_GetOpaque2(ctx, argv[1], JS_CLASS_FLOAT_ENV);
+        if (!fe) {
+            if (a == &a_s)
+                bf_delete(a);
+            JS_FreeValue(ctx, op1);
+            return JS_EXCEPTION;
+        }
+    }
+
+    bf_init(ctx->bf_ctx, r);
+    switch (magic) {
+    case MATH_OP_ABS:
+        bf_set(r, a);
+        r->sign = 0;
+        break;
+    case MATH_OP_FLOOR:
+        rnd_mode = BF_RNDD;
+        goto rint;
+    case MATH_OP_CEIL:
+        rnd_mode = BF_RNDU;
+        goto rint;
+    case MATH_OP_ROUND:
+        rnd_mode = BF_RNDNU;
+        goto rint;
+    case MATH_OP_TRUNC:
+        rnd_mode = BF_RNDZ;
+    rint:
+        bf_set(r, a);
+        fe->status |= bf_rint(r, fe->prec,
+                              (fe->flags & ~BF_RND_MASK) | rnd_mode);
+        break;
+    case MATH_OP_SQRT:
+        fe->status |= bf_sqrt(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_FPROUND:
+        bf_set(r, a);
+        fe->status |= bf_round(r, fe->prec, fe->flags);
+        break;
+    case MATH_OP_ACOS:
+        fe->status |= bf_acos(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_ASIN:
+        fe->status |= bf_asin(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_ATAN:
+        fe->status |= bf_atan(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_COS:
+        fe->status |= bf_cos(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_EXP:
+        fe->status |= bf_exp(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_LOG:
+        fe->status |= bf_log(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_SIN:
+        fe->status |= bf_sin(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_TAN:
+        fe->status |= bf_tan(r, a, fe->prec, fe->flags);
+        break;
+    case MATH_OP_SIGN:
+        if (bf_is_nan(a) || bf_is_zero(a)) {
+            bf_set(r, a);
+        } else {
+            bf_set_si(r, 1 - 2 * a->sign);
+        }
+        break;
+    default:
+        abort();
+    }
+    if (a == &a_s)
+        bf_delete(a);
+    JS_FreeValue(ctx, op1);
+    return JS_NewBigFloat(ctx, r);
+}
+
+static JSValue js_math_fop2(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv, int magic)
+{
+    bf_t a_s, *a, b_s, *b, r_s, *r = &r_s;
+    BOOL is_float;
+    JSFloatEnv *fe;
+    JSValue op1, op2;
+
+    op1 = JS_ToNumber(ctx, argv[0]);
+    if (JS_IsException(op1))
+        return op1;
+    op2 = JS_ToNumber(ctx, argv[1]);
+    if (JS_IsException(op2)) {
+        JS_FreeValue(ctx, op1);
+        return op2;
+    }
+    a = JS_ToBigFloat(ctx, &is_float, &a_s, op1);
+    b = JS_ToBigFloat(ctx, &is_float, &b_s, op2);
+    fe = &ctx->fp_env;
+    if (argc > 2) {
+        fe = JS_GetOpaque2(ctx, argv[2], JS_CLASS_FLOAT_ENV);
+        if (!fe) {
+            if (a == &a_s)
+                bf_delete(a);
+            if (b == &b_s)
+                bf_delete(b);
+            JS_FreeValue(ctx, op1);
+            JS_FreeValue(ctx, op2);
+            return JS_EXCEPTION;
+        }
+    }
+
+    bf_init(ctx->bf_ctx, r);
+    switch (magic) {
+    case MATH_OP_ATAN2:
+        fe->status |= bf_atan2(r, a, b, fe->prec, fe->flags);
+        break;
+    case MATH_OP_POW:
+        fe->status |= bf_pow(r, a, b, fe->prec, fe->flags | BF_POW_JS_QUICKS);
+        break;
+    case MATH_OP_FMOD:
+        fe->status |= bf_fmod(r, a, b, fe->prec, fe->flags);
+        break;
+    case MATH_OP_REM:
+        fe->status |= bf_remainder(r, a, b, fe->prec, fe->flags);
+        break;
+    case MATH_OP_ADD:
+        fe->status |= bf_add(r, a, b, fe->prec, fe->flags);
+        break;
+    case MATH_OP_SUB:
+        fe->status |= bf_sub(r, a, b, fe->prec, fe->flags);
+        break;
+    case MATH_OP_MUL:
+        fe->status |= bf_mul(r, a, b, fe->prec, fe->flags);
+        break;
+    case MATH_OP_DIV:
+        fe->status |= bf_div(r, a, b, fe->prec, fe->flags);
+        break;
+    default:
+        abort();
+    }
+    if (a == &a_s)
+        bf_delete(a);
+    if (b == &b_s)
+        bf_delete(b);
+    JS_FreeValue(ctx, op1);
+    JS_FreeValue(ctx, op2);
+    return JS_NewBigFloat(ctx, r);
+}
+
+static JSValue js_math_min_max(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv, int magic)
+{
+    BOOL is_max = magic;
+    JSValue val, ret;
+    int i;
+    uint32_t tag;
+
+    if (unlikely(argc == 0)) {
+        return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0);
+    }
+
+    tag = JS_VALUE_GET_TAG(argv[0]);
+    if (tag == JS_TAG_INT) {
+        int a1, r1 = JS_VALUE_GET_INT(argv[0]);
+        for(i = 1; i < argc; i++) {
+            tag = JS_VALUE_GET_TAG(argv[i]);
+            if (tag != JS_TAG_INT) {
+                ret = JS_NewInt32(ctx, r1);
+                goto generic_case;
+            }
+            a1 = JS_VALUE_GET_INT(argv[i]);
+            if (is_max)
+                r1 = max_int(r1, a1);
+            else
+                r1 = min_int(r1, a1);
+        }
+        ret = JS_NewInt32(ctx, r1);
+    } else {
+        ret = JS_ToNumber(ctx, argv[0]);
+        if (JS_IsException(ret))
+            return ret;
+        i = 1;
+    generic_case:
+        for(; i < argc; i++) {
+            val = JS_ToNumber(ctx, argv[i]);
+            if (JS_IsException(val)) {
+                JS_FreeValue(ctx, ret);
+                return val;
+            }
+            if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(ret)) &&
+                JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(val))) {
+                double r, a;
+                r = JS_VALUE_GET_FLOAT64(ret);
+                a = JS_VALUE_GET_FLOAT64(val);
+                if (!isnan(r)) {
+                    if (isnan(a)) {
+                        r = a;
+                    } else {
+                        if (is_max)
+                            r = js_fmax(r, a);
+                        else
+                            r = js_fmin(r, a);
+                    }
+                    ret = __JS_NewFloat64(ctx, r);
+                }
+            } else {
+                bf_t a_s, *a, r_s, *r;
+                BOOL is_float;
+                int res;
+
+                r = JS_ToBigFloat(ctx, &is_float, &r_s, ret);
+                if (!bf_is_nan(r)) {
+                    a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
+                    res = bf_cmp_full(a, r);
+                    if (is_max)
+                        res = -res;
+                    if (bf_is_nan(a) || res < 0) {
+                        JS_FreeValue(ctx, ret);
+                        ret = JS_DupValue(ctx, val);
+                    }
+                    if (a == &a_s)
+                        bf_delete(a);
+                }
+                if (r == &r_s)
+                    bf_delete(r);
+            }
+        }
+    }
+    return ret;
+}
+
+static JSValue js_math_abs(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    JSValue val;
+    uint32_t tag;
+
+    val = JS_ToNumeric(ctx, argv[0]);
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+        if (JS_VALUE_GET_INT(val) < 0)
+            val = JS_NewInt64(ctx, -(int64_t)JS_VALUE_GET_INT(val));
+        break;
+    case JS_TAG_FLOAT64:
+        val = __JS_NewFloat64(ctx, fabs(JS_VALUE_GET_FLOAT64(val)));
+        break;
+    case JS_TAG_BIG_FLOAT:
+    case JS_TAG_BIG_INT:
+        {
+            JSBigFloat *p = JS_VALUE_GET_PTR(val);
+            bf_t r_s, *r = &r_s;
+            bf_init(ctx->bf_ctx, r);
+            bf_set(r, &p->num);
+            r->sign = 0;
+            JS_FreeValue(ctx, val);
+            if (tag == JS_TAG_BIG_FLOAT)
+                val = JS_NewBigFloat(ctx, r);
+            else
+                val = JS_NewBigInt2(ctx, r, TRUE);
+        }
+        break;
+    default:
+        break;
+    }
+    return val;
+}
+
+#if 0
+/* XXX: should give exact rounding */
+/* XXX: correct NaN/Infinity handling */
+static JSValue js_math_hypot(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    bf_t a_s, *a, r_s, *r = &r_s, r2_s, *r2 = &r2_s;
+    JSValue val;
+    int i;
+    BOOL is_float;
+
+    bf_init(ctx->bf_ctx, r);
+    bf_set_si(r, 0);
+    for(i = 0; i < argc; i++) {
+        val = JS_ToNumber(ctx, argv[i]);
+        if (JS_IsException(val)) {
+            bf_delete(r);
+            return val;
+        }
+        a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
+        bf_add(r, r, a, ctx->fp_env.prec, ctx->fp_env.flags);
+        if (a == &a_s)
+            bf_delete(a);
+    }
+    bf_init(ctx->bf_ctx, r2);
+    bf_sqrt(r2, r, ctx->fp_env.prec, ctx->fp_env.flags);
+    bf_delete(r);
+    return JS_NewBigFloat(ctx, r2);
+}
+#endif
+
+#else
+
+static JSValue js_math_min_max(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv, int magic)
+{
+    BOOL is_max = magic;
+    double r, a;
+    int i;
+    uint32_t tag;
+
+    if (unlikely(argc == 0)) {
+        return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0);
+    }
+
+    tag = JS_VALUE_GET_TAG(argv[0]);
+    if (tag == JS_TAG_INT) {
+        int a1, r1 = JS_VALUE_GET_INT(argv[0]);
+        for(i = 1; i < argc; i++) {
+            tag = JS_VALUE_GET_TAG(argv[i]);
+            if (tag != JS_TAG_INT) {
+                r = r1;
+                goto generic_case;
+            }
+            a1 = JS_VALUE_GET_INT(argv[i]);
+            if (is_max)
+                r1 = max_int(r1, a1);
+            else
+                r1 = min_int(r1, a1);
+
+        }
+        return JS_NewInt32(ctx, r1);
+    } else {
+        if (JS_ToFloat64(ctx, &r, argv[0]))
+            return JS_EXCEPTION;
+        i = 1;
+    generic_case:
+        while (i < argc) {
+            if (JS_ToFloat64(ctx, &a, argv[i]))
+                return JS_EXCEPTION;
+            if (!isnan(r)) {
+                if (isnan(a)) {
+                    r = a;
+                } else {
+                    if (is_max)
+                        r = js_fmax(r, a);
+                    else
+                        r = js_fmin(r, a);
+                }
+            }
+            i++;
+        }
+        return JS_NewFloat64(ctx, r);
+    }
+}
+
+#endif /* !CONFIG_BIGNUM */
+
+static double js_math_sign(double a)
+{
+    if (isnan(a) || a == 0.0)
+        return a;
+    if (a < 0)
+        return -1;
+    else
+        return 1;
+}
+
+static double js_math_round(double a)
+{
+    JSFloat64Union u;
+    uint64_t frac_mask, one;
+    unsigned int e, s;
+
+    u.d = a;
+    e = (u.u64 >> 52) & 0x7ff;
+    if (e < 1023) {
+        /* abs(a) < 1 */
+        if (e == (1023 - 1) && u.u64 != 0xbfe0000000000000) {
+            /* abs(a) > 0.5 or a = 0.5: return +/-1.0 */
+            u.u64 = (u.u64 & ((uint64_t)1 << 63)) | ((uint64_t)1023 << 52);
+        } else {
+            /* return +/-0.0 */
+            u.u64 &= (uint64_t)1 << 63;
+        }
+    } else if (e < (1023 + 52)) {
+        s = u.u64 >> 63;
+        one = (uint64_t)1 << (52 - (e - 1023));
+        frac_mask = one - 1;
+        u.u64 += (one >> 1) - s;
+        u.u64 &= ~frac_mask; /* truncate to an integer */
+    }
+    /* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */
+    return u.d;
+}
+
+static JSValue js_math_hypot(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    double r, a, b;
+    int i;
+
+    if (argc == 2) {
+        /* use the more precise built-in function when possible */
+        if (JS_ToFloat64(ctx, &a, argv[0]))
+            return JS_EXCEPTION;
+        if (JS_ToFloat64(ctx, &b, argv[1]))
+            return JS_EXCEPTION;
+        r = hypot(a, b);
+    } else {
+        r = 0;
+        for(i = 0; i < argc; i++) {
+            if (JS_ToFloat64(ctx, &a, argv[i]))
+                return JS_EXCEPTION;
+            r += a;
+        }
+        r = sqrt(r);
+    }
+    return JS_NewFloat64(ctx, r);
+}
+
+static double js_math_fround(double a)
+{
+    return (float)a;
+}
+
+static JSValue js_math_imul(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv)
+{
+    int a, b;
+
+    if (JS_ToInt32(ctx, &a, argv[0]))
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &b, argv[1]))
+        return JS_EXCEPTION;
+    /* purposely ignoring overflow */
+    return JS_NewInt32(ctx, a * b);
+}
+
+static JSValue js_math_clz32(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    uint32_t a, r;
+
+    if (JS_ToUint32(ctx, &a, argv[0]))
+        return JS_EXCEPTION;
+    if (a == 0)
+        r = 32;
+    else
+        r = clz32(a);
+    return JS_NewInt32(ctx, r);
+}
+
+/* xorshift* random number generator by Marsaglia */
+static uint64_t xorshift64star(uint64_t *pstate)
+{
+    uint64_t x;
+    x = *pstate;
+    x ^= x >> 12;
+    x ^= x << 25;
+    x ^= x >> 27;
+    *pstate = x;
+    return x * 0x2545F4914F6CDD1D;
+}
+
+static void js_random_init(JSContext *ctx)
+{
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    ctx->random_state = ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec;
+    /* the state must be non zero */
+    if (ctx->random_state == 0)
+        ctx->random_state = 1;
+}
+
+static JSValue js_math_random(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    JSFloat64Union u;
+    uint64_t v;
+
+    v = xorshift64star(&ctx->random_state);
+    /* 1.0 <= u.d < 2 */
+    u.u64 = ((uint64_t)0x3ff << 52) | (v >> 12);
+    return __JS_NewFloat64(ctx, u.d - 1.0);
+}
+
+static const JSCFunctionListEntry js_math_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0 ),
+    JS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1 ),
+#ifdef CONFIG_BIGNUM
+    JS_CFUNC_DEF("abs", 1, js_math_abs ),
+#else
+    JS_CFUNC_SPECIAL_DEF("abs", 1, f_f, fabs ),
+#endif
+    JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, floor ),
+    JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, ceil ),
+    JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_math_round ),
+    JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, sqrt ),
+
+    JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, acos ),
+    JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, asin ),
+    JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, atan ),
+    JS_CFUNC_SPECIAL_DEF("atan2", 2, f_f_f, atan2 ),
+    JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, cos ),
+    JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, exp ),
+    JS_CFUNC_SPECIAL_DEF("log", 1, f_f, log ),
+    JS_CFUNC_SPECIAL_DEF("pow", 2, f_f_f, js_pow ),
+    JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, sin ),
+    JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, tan ),
+    /* ES6 */
+    JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, trunc ),
+    JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ),
+    JS_CFUNC_SPECIAL_DEF("cosh", 1, f_f, cosh ),
+    JS_CFUNC_SPECIAL_DEF("sinh", 1, f_f, sinh ),
+    JS_CFUNC_SPECIAL_DEF("tanh", 1, f_f, tanh ),
+    JS_CFUNC_SPECIAL_DEF("acosh", 1, f_f, acosh ),
+    JS_CFUNC_SPECIAL_DEF("asinh", 1, f_f, asinh ),
+    JS_CFUNC_SPECIAL_DEF("atanh", 1, f_f, atanh ),
+    JS_CFUNC_SPECIAL_DEF("expm1", 1, f_f, expm1 ),
+    JS_CFUNC_SPECIAL_DEF("log1p", 1, f_f, log1p ),
+    JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, log2 ),
+    JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, log10 ),
+    JS_CFUNC_SPECIAL_DEF("cbrt", 1, f_f, cbrt ),
+    JS_CFUNC_DEF("hypot", 2, js_math_hypot ),
+    JS_CFUNC_DEF("random", 0, js_math_random ),
+    JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ),
+    JS_CFUNC_DEF("imul", 2, js_math_imul ),
+    JS_CFUNC_DEF("clz32", 1, js_math_clz32 ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Math", JS_PROP_CONFIGURABLE ),
+    JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ),
+    JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ),
+    JS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0 ),
+    JS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0 ),
+    JS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0 ),
+    JS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0 ),
+    JS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0 ),
+    JS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0 ),
+};
+
+static const JSCFunctionListEntry js_math_obj[] = {
+    JS_OBJECT_DEF("Math", js_math_funcs, countof(js_math_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
+};
+
+/* Date */
+
+#if 0
+/* OS dependent: return the UTC time in ms since 1970. */
+static JSValue js___date_now(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    int64_t d;
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    d = (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
+    return JS_NewInt64(ctx, d);
+}
+#endif
+
+/* OS dependent: return the UTC time in microseconds since 1970. */
+static JSValue js___date_clock(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    int64_t d;
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    d = (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
+    return JS_NewInt64(ctx, d);
+}
+
+/* OS dependent. d = argv[0] is in ms from 1970. Return the difference
+   between local time and UTC time 'd' in minutes */
+static int getTimezoneOffset(int64_t time) {
+#if defined(_WIN32)
+    /* XXX: TODO */
+    return 0;
+#else
+    time_t ti;
+    struct tm tm;
+
+    time /= 1000; /* convert to seconds */
+    if (sizeof(time_t) == 4) {
+        /* on 32-bit systems, we need to clamp the time value to the
+           range of `time_t`. This is better than truncating values to
+           32 bits and hopefully provides the same result as 64-bit
+           implementation of localtime_r.
+         */
+        if ((time_t)-1 < 0) {
+            if (time < INT32_MIN) {
+                time = INT32_MIN;
+            } else if (time > INT32_MAX) {
+                time = INT32_MAX;
+            }
+        } else {
+            if (time < 0) {
+                time = 0;
+            } else if (time > UINT32_MAX) {
+                time = UINT32_MAX;
+            }
+        }
+    }
+    ti = time;
+    localtime_r(&ti, &tm);
+    return -tm.tm_gmtoff / 60;
+#endif
+}
+
+#if 0
+static JSValue js___date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val,
+                                           int argc, JSValueConst *argv)
+{
+    double dd;
+
+    if (JS_ToFloat64(ctx, &dd, argv[0]))
+        return JS_EXCEPTION;
+    if (isnan(dd))
+        return __JS_NewFloat64(ctx, dd);
+    else
+        return JS_NewInt32(ctx, getTimezoneOffset((int64_t)dd));
+}
+
+/* create a new date object */
+static JSValue js___date_create(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue obj, proto;
+    proto = js_get_prototype_from_ctor(ctx, argv[0], argv[1]);
+    if (JS_IsException(proto))
+        return proto;
+    obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_DATE);
+    JS_FreeValue(ctx, proto);
+    if (!JS_IsException(obj))
+        JS_SetObjectData(ctx, obj, JS_DupValue(ctx, argv[2]));
+    return obj;
+}
+#endif
+
+/* RegExp */
+
+static void js_regexp_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSRegExp *re = &p->u.regexp;
+    JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->bytecode));
+    JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->pattern));
+}
+
+/* create a string containing the RegExp bytecode */
+static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
+                                 JSValueConst flags)
+{
+    const char *str;
+    int re_flags, mask;
+    uint8_t *re_bytecode_buf;
+    size_t i, len;
+    int re_bytecode_len;
+    JSValue ret;
+    char error_msg[64];
+
+    re_flags = 0;
+    if (!JS_IsUndefined(flags)) {
+        str = JS_ToCStringLen(ctx, &len, flags);
+        if (!str)
+            return JS_EXCEPTION;
+        /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */
+        for (i = 0; i < len; i++) {
+            switch(str[i]) {
+            case 'g':
+                mask = LRE_FLAG_GLOBAL;
+                break;
+            case 'i':
+                mask = LRE_FLAG_IGNORECASE;
+                break;
+            case 'm':
+                mask = LRE_FLAG_MULTILINE;
+                break;
+            case 's':
+                mask = LRE_FLAG_DOTALL;
+                break;
+            case 'u':
+                mask = LRE_FLAG_UTF16;
+                break;
+            case 'y':
+                mask = LRE_FLAG_STICKY;
+                break;
+            default:
+                goto bad_flags;
+            }
+            if ((re_flags & mask) != 0) {
+            bad_flags:
+                JS_FreeCString(ctx, str);
+                return JS_ThrowSyntaxError(ctx, "invalid regular expression flags");
+            }
+            re_flags |= mask;
+        }
+        JS_FreeCString(ctx, str);
+    }
+
+    str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & LRE_FLAG_UTF16));
+    if (!str)
+        return JS_EXCEPTION;
+    re_bytecode_buf = lre_compile(&re_bytecode_len, error_msg,
+                                  sizeof(error_msg), str, len, re_flags, ctx);
+    JS_FreeCString(ctx, str);
+    if (!re_bytecode_buf) {
+        JS_ThrowSyntaxError(ctx, "%s", error_msg);
+        return JS_EXCEPTION;
+    }
+
+    ret = js_new_string8(ctx, re_bytecode_buf, re_bytecode_len);
+    js_free(ctx, re_bytecode_buf);
+    return ret;
+}
+
+/* create a RegExp object from a string containing the RegExp bytecode
+   and the source pattern */
+static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor,
+                                              JSValue pattern, JSValue bc)
+{
+    JSValue obj;
+    JSObject *p;
+    JSRegExp *re;
+
+    /* sanity check */
+    if (JS_VALUE_GET_TAG(bc) != JS_TAG_STRING ||
+        JS_VALUE_GET_TAG(pattern) != JS_TAG_STRING) {
+        JS_ThrowTypeError(ctx, "string expected");
+    fail:
+        JS_FreeValue(ctx, bc);
+        JS_FreeValue(ctx, pattern);
+        return JS_EXCEPTION;
+    }
+
+    obj = js_create_from_ctor(ctx, ctor, JS_CLASS_REGEXP);
+    if (JS_IsException(obj))
+        goto fail;
+    p = JS_VALUE_GET_OBJ(obj);
+    re = &p->u.regexp;
+    re->pattern = JS_VALUE_GET_STRING(pattern);
+    re->bytecode = JS_VALUE_GET_STRING(bc);
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0),
+                           JS_PROP_WRITABLE);
+    return obj;
+}
+
+static JSRegExp *js_get_regexp(JSContext *ctx, JSValueConst obj, BOOL throw_error)
+{
+    if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(obj);
+        if (p->class_id == JS_CLASS_REGEXP)
+            return &p->u.regexp;
+    }
+    if (throw_error) {
+        JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP);
+    }
+    return NULL;
+}
+
+/* return < 0 if exception or TRUE/FALSE */
+static int js_is_regexp(JSContext *ctx, JSValueConst obj)
+{
+    JSValue m;
+
+    if (!JS_IsObject(obj))
+        return FALSE;
+    m = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_match);
+    if (JS_IsException(m))
+        return -1;
+    if (!JS_IsUndefined(m))
+        return JS_ToBoolFree(ctx, m);
+    return js_get_regexp(ctx, obj, FALSE) != NULL;
+}
+
+static JSValue js_regexp_constructor(JSContext *ctx, JSValueConst new_target,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue pattern, flags, bc, val;
+    JSValueConst pat, flags1;
+    JSRegExp *re;
+    int pat_is_regexp;
+
+    pat = argv[0];
+    flags1 = argv[1];
+    pat_is_regexp = js_is_regexp(ctx, pat);
+    if (pat_is_regexp < 0)
+        return JS_EXCEPTION;
+    if (JS_IsUndefined(new_target)) {
+        /* called as a function */
+        new_target = JS_GetActiveFunction(ctx);
+        if (pat_is_regexp && JS_IsUndefined(flags1)) {
+            JSValue ctor;
+            BOOL res;
+            ctor = JS_GetProperty(ctx, pat, JS_ATOM_constructor);
+            if (JS_IsException(ctor))
+                return ctor;
+            res = js_same_value(ctx, ctor, new_target);
+            JS_FreeValue(ctx, ctor);
+            if (res)
+                return JS_DupValue(ctx, pat);
+        }
+    }
+    re = js_get_regexp(ctx, pat, FALSE);
+    if (re) {
+        pattern = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern));
+        if (JS_IsUndefined(flags1)) {
+            bc = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode));
+            goto no_compilation;
+        } else {
+            flags = JS_ToString(ctx, flags1);
+            if (JS_IsException(flags))
+                goto fail;
+        }
+    } else {
+        flags = JS_UNDEFINED;
+        if (pat_is_regexp) {
+            pattern = JS_GetProperty(ctx, pat, JS_ATOM_source);
+            if (JS_IsException(pattern))
+                goto fail;
+            if (JS_IsUndefined(flags1)) {
+                flags = JS_GetProperty(ctx, pat, JS_ATOM_flags);
+                if (JS_IsException(flags))
+                    goto fail;
+            } else {
+                flags = JS_DupValue(ctx, flags1);
+            }
+        } else {
+            pattern = JS_DupValue(ctx, pat);
+            flags = JS_DupValue(ctx, flags1);
+        }
+        if (JS_IsUndefined(pattern)) {
+            pattern = JS_AtomToString(ctx, JS_ATOM_empty_string);
+        } else {
+            val = pattern;
+            pattern = JS_ToString(ctx, val);
+            JS_FreeValue(ctx, val);
+            if (JS_IsException(pattern))
+                goto fail;
+        }
+    }
+    bc = js_compile_regexp(ctx, pattern, flags);
+    if (JS_IsException(bc))
+        goto fail;
+    JS_FreeValue(ctx, flags);
+ no_compilation:
+    return js_regexp_constructor_internal(ctx, new_target, pattern, bc);
+ fail:
+    JS_FreeValue(ctx, pattern);
+    JS_FreeValue(ctx, flags);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_regexp_compile(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSRegExp *re1, *re;
+    JSValueConst pattern1, flags1;
+    JSValue bc, pattern;
+
+    re = js_get_regexp(ctx, this_val, TRUE);
+    if (!re)
+        return JS_EXCEPTION;
+    pattern1 = argv[0];
+    flags1 = argv[1];
+    re1 = js_get_regexp(ctx, pattern1, FALSE);
+    if (re1) {
+        if (!JS_IsUndefined(flags1))
+            return JS_ThrowTypeError(ctx, "flags must be undefined");
+        pattern = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re1->pattern));
+        bc = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re1->bytecode));
+    } else {
+        bc = JS_UNDEFINED;
+        if (JS_IsUndefined(pattern1))
+            pattern = JS_AtomToString(ctx, JS_ATOM_empty_string);
+        else
+            pattern = JS_ToString(ctx, pattern1);
+        if (JS_IsException(pattern))
+            goto fail;
+        bc = js_compile_regexp(ctx, pattern, flags1);
+        if (JS_IsException(bc))
+            goto fail;
+    }
+    JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern));
+    JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode));
+    re->pattern = JS_VALUE_GET_STRING(pattern);
+    re->bytecode = JS_VALUE_GET_STRING(bc);
+    if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
+                       JS_NewInt32(ctx, 0)) < 0)
+        return JS_EXCEPTION;
+    return JS_DupValue(ctx, this_val);
+ fail:
+    JS_FreeValue(ctx, pattern);
+    JS_FreeValue(ctx, bc);
+    return JS_EXCEPTION;
+}
+
+#if 0
+static JSValue js_regexp_get___source(JSContext *ctx, JSValueConst this_val)
+{
+    JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
+    if (!re)
+        return JS_EXCEPTION;
+    return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern));
+}
+
+static JSValue js_regexp_get___flags(JSContext *ctx, JSValueConst this_val)
+{
+    JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
+    int flags;
+
+    if (!re)
+        return JS_EXCEPTION;
+    flags = lre_get_flags(re->bytecode->u.str8);
+    return JS_NewInt32(ctx, flags);
+}
+#endif
+
+static JSValue js_regexp_get_source(JSContext *ctx, JSValueConst this_val)
+{
+    JSRegExp *re;
+    JSString *p;
+    StringBuffer b_s, *b = &b_s;
+    int i, n, c, c2, bra;
+
+    if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP]))
+        goto empty_regex;
+
+    re = js_get_regexp(ctx, this_val, TRUE);
+    if (!re)
+        return JS_EXCEPTION;
+
+    p = re->pattern;
+
+    if (p->len == 0) {
+    empty_regex:
+        return JS_NewString(ctx, "(?:)");
+    }    
+    string_buffer_init2(ctx, b, p->len, p->is_wide_char);
+
+    /* Escape '/' and newline sequences as needed */
+    bra = 0;
+    for (i = 0, n = p->len; i < n;) {
+        c2 = -1;
+        switch (c = string_get(p, i++)) {
+        case '\\':
+            if (i < n)
+                c2 = string_get(p, i++);
+            break;
+        case ']':
+            bra = 0;
+            break;
+        case '[':
+            if (!bra) {
+                if (i < n && string_get(p, i) == ']')
+                    c2 = string_get(p, i++);
+                bra = 1;
+            }
+            break;
+        case '\n':
+            c = '\\';
+            c2 = 'n';
+            break;
+        case '\r':
+            c = '\\';
+            c2 = 'r';
+            break;
+        case '/':
+            if (!bra) {
+                c = '\\';
+                c2 = '/';
+            }
+            break;
+        }
+        string_buffer_putc16(b, c);
+        if (c2 >= 0)
+            string_buffer_putc16(b, c2);
+    }
+    return string_buffer_end(b);
+}
+
+static JSValue js_regexp_get_flag(JSContext *ctx, JSValueConst this_val, int mask)
+{
+    JSRegExp *re;
+    int flags;
+
+    if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP]))
+        return JS_UNDEFINED;
+
+    re = js_get_regexp(ctx, this_val, TRUE);
+    if (!re)
+        return JS_EXCEPTION;
+
+    flags = lre_get_flags(re->bytecode->u.str8);
+    return JS_NewBool(ctx, (flags & mask) != 0);
+}
+
+static JSValue js_regexp_get_flags(JSContext *ctx, JSValueConst this_val)
+{
+    char str[8], *p = str;
+    int res;
+
+    if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_global));
+    if (res < 0)
+        goto exception;
+    if (res)
+        *p++ = 'g';
+    res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "ignoreCase"));
+    if (res < 0)
+        goto exception;
+    if (res)
+        *p++ = 'i';
+    res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "multiline"));
+    if (res < 0)
+        goto exception;
+    if (res)
+        *p++ = 'm';
+    res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "dotAll"));
+    if (res < 0)
+        goto exception;
+    if (res)
+        *p++ = 's';
+    res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_unicode));
+    if (res < 0)
+        goto exception;
+    if (res)
+        *p++ = 'u';
+    res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "sticky"));
+    if (res < 0)
+        goto exception;
+    if (res)
+        *p++ = 'y';
+    return JS_NewStringLen(ctx, str, p - str);
+
+exception:
+    return JS_EXCEPTION;
+}
+
+static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValue pattern, flags;
+    StringBuffer b_s, *b = &b_s;
+
+    if (!JS_IsObject(this_val))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    string_buffer_init(ctx, b, 0);
+    string_buffer_putc8(b, '/');
+    pattern = JS_GetProperty(ctx, this_val, JS_ATOM_source);
+    if (string_buffer_concat_value_free(b, pattern))
+        goto fail;
+    string_buffer_putc8(b, '/');
+    flags = JS_GetProperty(ctx, this_val, JS_ATOM_flags);
+    if (string_buffer_concat_value_free(b, flags))
+        goto fail;
+    return string_buffer_end(b);
+
+fail:
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size)
+{
+    JSContext *ctx = opaque;
+    return js_check_stack_overflow(ctx, alloca_size);
+}
+
+void *lre_realloc(void *opaque, void *ptr, size_t size)
+{
+    JSContext *ctx = opaque;
+    /* No JS exception is raised here */
+    return js_realloc_rt(ctx->rt, ptr, size);
+}
+
+static JSValue js_regexp_exec(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
+    JSString *str;
+    JSValue str_val, obj, val, groups = JS_UNDEFINED;
+    uint8_t *re_bytecode;
+    int ret;
+    uint8_t **capture, *str_buf;
+    int capture_count, shift, i, re_flags;
+    int64_t last_index;
+    const char *group_name_ptr;
+
+    if (!re)
+        return JS_EXCEPTION;
+    str_val = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str_val))
+        return str_val;
+    val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex);
+    if (JS_IsException(val) ||
+        JS_ToLengthFree(ctx, &last_index, val)) {
+        JS_FreeValue(ctx, str_val);
+        return JS_EXCEPTION;
+    }
+    re_bytecode = re->bytecode->u.str8;
+    re_flags = lre_get_flags(re_bytecode);
+    if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
+        last_index = 0;
+    }
+    str = JS_VALUE_GET_STRING(str_val);
+    capture_count = lre_get_capture_count(re_bytecode);
+    capture = NULL;
+    if (capture_count > 0) {
+        capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2);
+        if (!capture) {
+            JS_FreeValue(ctx, str_val);
+            return JS_EXCEPTION;
+        }
+    }
+    shift = str->is_wide_char;
+    str_buf = str->u.str8;
+    if (last_index > str->len) {
+        ret = 2;
+    } else {
+        ret = lre_exec(capture, re_bytecode,
+                       str_buf, last_index, str->len,
+                       shift, ctx);
+    }
+    obj = JS_NULL;
+    if (ret != 1) {
+        if (ret >= 0) {
+            if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
+                if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
+                                   JS_NewInt32(ctx, 0)) < 0)
+                    goto fail;
+            }
+        } else {
+            JS_ThrowInternalError(ctx, "out of memory in regexp execution");
+            goto fail;
+        }
+        JS_FreeValue(ctx, str_val);
+    } else {
+        int prop_flags;
+        if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
+            if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
+                               JS_NewInt32(ctx, (capture[1] - str_buf) >> shift)) < 0)
+                goto fail;
+        }
+        obj = JS_NewArray(ctx);
+        if (JS_IsException(obj))
+            goto fail;
+        prop_flags = JS_PROP_C_W_E | JS_PROP_THROW;
+        group_name_ptr = NULL;
+        if (re_flags & LRE_FLAG_NAMED_GROUPS) {
+            uint32_t re_bytecode_len;
+            groups = JS_NewObjectProto(ctx, JS_NULL);
+            if (JS_IsException(groups))
+                goto fail;
+            re_bytecode_len = get_u32(re_bytecode + 3);
+            group_name_ptr = (char *)(re_bytecode + 7 + re_bytecode_len);
+        }
+
+        for(i = 0; i < capture_count; i++) {
+            int start, end;
+            JSValue val;
+            if (capture[2 * i] == NULL ||
+                capture[2 * i + 1] == NULL) {
+                val = JS_UNDEFINED;
+            } else {
+                start = (capture[2 * i] - str_buf) >> shift;
+                end = (capture[2 * i + 1] - str_buf) >> shift;
+                val = js_sub_string(ctx, str, start, end);
+                if (JS_IsException(val))
+                    goto fail;
+            }
+            if (group_name_ptr && i > 0) {
+                if (*group_name_ptr) {
+                    if (JS_DefinePropertyValueStr(ctx, groups, group_name_ptr,
+                                                  JS_DupValue(ctx, val),
+                                                  prop_flags) < 0) {
+                        JS_FreeValue(ctx, val);
+                        goto fail;
+                    }
+                }
+                group_name_ptr += strlen(group_name_ptr) + 1;
+            }
+            if (JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags) < 0)
+                goto fail;
+        }
+        if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups,
+                                   groups, prop_flags) < 0)
+            goto fail;
+        if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index,
+                                   JS_NewInt32(ctx, (capture[0] - str_buf) >> shift), prop_flags) < 0)
+            goto fail;
+        if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, str_val, prop_flags) < 0)
+            goto fail1;
+    }
+    js_free(ctx, capture);
+    return obj;
+fail:
+    JS_FreeValue(ctx, groups);
+    JS_FreeValue(ctx, str_val);
+fail1:
+    JS_FreeValue(ctx, obj);
+    js_free(ctx, capture);
+    return JS_EXCEPTION;
+}
+
+/* delete partions of a string that match a given regex */
+static JSValue JS_RegExpDelete(JSContext *ctx, JSValueConst this_val, JSValueConst arg)
+{
+    JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
+    JSString *str;
+    JSValue str_val, val;
+    uint8_t *re_bytecode;
+    int ret;
+    uint8_t **capture, *str_buf;
+    int capture_count, shift, re_flags;
+    int next_src_pos, start, end;
+    int64_t last_index;
+    StringBuffer b_s, *b = &b_s;
+
+    if (!re)
+        return JS_EXCEPTION;
+
+    string_buffer_init(ctx, b, 0);
+
+    capture = NULL;
+    str_val = JS_ToString(ctx, arg);
+    if (JS_IsException(str_val))
+        goto fail;
+    str = JS_VALUE_GET_STRING(str_val);
+    re_bytecode = re->bytecode->u.str8;
+    re_flags = lre_get_flags(re_bytecode);
+    if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
+        last_index = 0;
+    } else {
+        val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex);
+        if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val))
+            goto fail;
+    }
+    capture_count = lre_get_capture_count(re_bytecode);
+    if (capture_count > 0) {
+        capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2);
+        if (!capture)
+            goto fail;
+    }
+    shift = str->is_wide_char;
+    str_buf = str->u.str8;
+    next_src_pos = 0;
+    for (;;) {
+        if (last_index > str->len)
+            break;
+
+        ret = lre_exec(capture, re_bytecode,
+                       str_buf, last_index, str->len, shift, ctx);
+        if (ret != 1) {
+            if (ret >= 0) {
+                if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
+                    if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
+                                       JS_NewInt32(ctx, 0)) < 0)
+                        goto fail;
+                }
+            } else {
+                JS_ThrowInternalError(ctx, "out of memory in regexp execution");
+                goto fail;
+            }
+            break;
+        }
+        start = (capture[0] - str_buf) >> shift;
+        end = (capture[1] - str_buf) >> shift;
+        last_index = end;
+        if (next_src_pos < start) {
+            if (string_buffer_concat(b, str, next_src_pos, start))
+                goto fail;
+        }
+        next_src_pos = end;
+        if (!(re_flags & LRE_FLAG_GLOBAL)) {
+            if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
+                               JS_NewInt32(ctx, end)) < 0)
+                goto fail;
+            break;
+        }
+        if (end == start) {
+            if (!(re_flags & LRE_FLAG_UTF16) || (unsigned)end >= str->len || !str->is_wide_char) {
+                end++;
+            } else {
+                string_getc(str, &end);
+            }
+        }
+        last_index = end;
+    }
+    if (string_buffer_concat(b, str, next_src_pos, str->len))
+        goto fail;
+    JS_FreeValue(ctx, str_val);
+    js_free(ctx, capture);
+    return string_buffer_end(b);
+fail:
+    JS_FreeValue(ctx, str_val);
+    js_free(ctx, capture);
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static JSValue JS_RegExpExec(JSContext *ctx, JSValueConst r, JSValueConst s)
+{
+    JSValue method, ret;
+
+    method = JS_GetProperty(ctx, r, JS_ATOM_exec);
+    if (JS_IsException(method))
+        return method;
+    if (JS_IsFunction(ctx, method)) {
+        ret = JS_CallFree(ctx, method, r, 1, &s);
+        if (JS_IsException(ret))
+            return ret;
+        if (!JS_IsObject(ret) && !JS_IsNull(ret)) {
+            JS_FreeValue(ctx, ret);
+            return JS_ThrowTypeError(ctx, "RegExp exec method must return an object or null");
+        }
+        return ret;
+    }
+    JS_FreeValue(ctx, method);
+    return js_regexp_exec(ctx, r, 1, &s);
+}
+
+#if 0
+static JSValue js_regexp___RegExpExec(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv)
+{
+    return JS_RegExpExec(ctx, argv[0], argv[1]);
+}
+static JSValue js_regexp___RegExpDelete(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    return JS_RegExpDelete(ctx, argv[0], argv[1]);
+}
+#endif
+
+static JSValue js_regexp_test(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    JSValue val;
+    BOOL ret;
+
+    val = JS_RegExpExec(ctx, this_val, argv[0]);
+    if (JS_IsException(val))
+        return JS_EXCEPTION;
+    ret = !JS_IsNull(val);
+    JS_FreeValue(ctx, val);
+    return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_regexp_Symbol_match(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv)
+{
+    // [Symbol.match](str)
+    JSValueConst rx = this_val;
+    JSValue A, S, result, matchStr;
+    int global, n, fullUnicode, isEmpty;
+    JSString *p;
+
+    if (!JS_IsObject(rx))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    A = JS_UNDEFINED;
+    result = JS_UNDEFINED;
+    matchStr = JS_UNDEFINED;
+    S = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(S))
+        goto exception;
+
+    global = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_global));
+    if (global < 0)
+        goto exception;
+
+    if (!global) {
+        A = JS_RegExpExec(ctx, rx, S);
+    } else {
+        fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode));
+        if (fullUnicode < 0)
+            goto exception;
+
+        if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0)
+            goto exception;
+        A = JS_NewArray(ctx);
+        if (JS_IsException(A))
+            goto exception;
+        n = 0;
+        for(;;) {
+            JS_FreeValue(ctx, result);
+            result = JS_RegExpExec(ctx, rx, S);
+            if (JS_IsException(result))
+                goto exception;
+            if (JS_IsNull(result))
+                break;
+            matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
+            if (JS_IsException(matchStr))
+                goto exception;
+            isEmpty = JS_IsEmptyString(matchStr);
+            if (JS_SetPropertyInt64(ctx, A, n++, matchStr) < 0)
+                goto exception;
+            if (isEmpty) {
+                int64_t thisIndex, nextIndex;
+                if (JS_ToLengthFree(ctx, &thisIndex,
+                                    JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0)
+                    goto exception;
+                p = JS_VALUE_GET_STRING(S);
+                nextIndex = thisIndex + 1;
+                if (thisIndex < p->len)
+                    nextIndex = string_advance_index(p, thisIndex, fullUnicode);
+                if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt64(ctx, nextIndex)) < 0)
+                    goto exception;
+            }
+        }
+        if (n == 0) {
+            JS_FreeValue(ctx, A);
+            A = JS_NULL;
+        }
+    }
+    JS_FreeValue(ctx, result);
+    JS_FreeValue(ctx, S);
+    return A;
+
+exception:
+    JS_FreeValue(ctx, A);
+    JS_FreeValue(ctx, result);
+    JS_FreeValue(ctx, S);
+    return JS_EXCEPTION;
+}
+
+typedef struct JSRegExpStringIteratorData {
+    JSValue iterating_regexp;
+    JSValue iterated_string;
+    BOOL global;
+    BOOL unicode;
+    BOOL done;
+} JSRegExpStringIteratorData;
+
+static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data;
+    if (it) {
+        JS_FreeValueRT(rt, it->iterating_regexp);
+        JS_FreeValueRT(rt, it->iterated_string);
+        js_free_rt(rt, it);
+    }
+}
+
+static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                           JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data;
+    if (it) {
+        JS_MarkValue(rt, it->iterating_regexp, mark_func);
+        JS_MarkValue(rt, it->iterated_string, mark_func);
+    }
+}
+
+static JSValue js_regexp_string_iterator_next(JSContext *ctx,
+                                              JSValueConst this_val,
+                                              int argc, JSValueConst *argv,
+                                              BOOL *pdone, int magic)
+{
+    JSRegExpStringIteratorData *it;
+    JSValueConst R, S;
+    JSValue matchStr = JS_UNDEFINED, match = JS_UNDEFINED;
+    JSString *sp;
+
+    it = JS_GetOpaque2(ctx, this_val, JS_CLASS_REGEXP_STRING_ITERATOR);
+    if (!it)
+        goto exception;
+    if (it->done) {
+        *pdone = TRUE;
+        return JS_UNDEFINED;
+    }
+    R = it->iterating_regexp;
+    S = it->iterated_string;
+    match = JS_RegExpExec(ctx, R, S);
+    if (JS_IsException(match))
+        goto exception;
+    if (JS_IsNull(match)) {
+        it->done = TRUE;
+        *pdone = TRUE;
+        return JS_UNDEFINED;
+    } else if (it->global) {
+        matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, match, 0));
+        if (JS_IsException(matchStr))
+            goto exception;
+        if (JS_IsEmptyString(matchStr)) {
+            int64_t thisIndex, nextIndex;
+            if (JS_ToLengthFree(ctx, &thisIndex,
+                                JS_GetProperty(ctx, R, JS_ATOM_lastIndex)) < 0)
+                goto exception;
+            sp = JS_VALUE_GET_STRING(S);
+            nextIndex = string_advance_index(sp, thisIndex, it->unicode);
+            if (JS_SetProperty(ctx, R, JS_ATOM_lastIndex,
+                               JS_NewInt32(ctx, nextIndex)) < 0)
+                goto exception;
+        }
+        JS_FreeValue(ctx, matchStr);
+    } else {
+        it->done = TRUE;
+    }
+    *pdone = FALSE;
+    return match;
+ exception:
+    JS_FreeValue(ctx, match);
+    JS_FreeValue(ctx, matchStr);
+    *pdone = FALSE;
+    return JS_EXCEPTION;
+}
+
+static JSValue js_regexp_Symbol_matchAll(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    // [Symbol.matchAll](str)
+    JSValueConst R = this_val;
+    JSValue S, C, flags, matcher, iter;
+    JSValueConst args[2];
+    JSString *strp;
+    int64_t lastIndex;
+    JSRegExpStringIteratorData *it;
+    
+    if (!JS_IsObject(R))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    C = JS_UNDEFINED;
+    flags = JS_UNDEFINED;
+    matcher = JS_UNDEFINED;
+    iter = JS_UNDEFINED;
+    
+    S = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(S))
+        goto exception;
+    C = JS_SpeciesConstructor(ctx, R, ctx->regexp_ctor);
+    if (JS_IsException(C))
+        goto exception;
+    flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, R, JS_ATOM_flags));
+    if (JS_IsException(flags))
+        goto exception;
+    args[0] = R;
+    args[1] = flags;
+    matcher = JS_CallConstructor(ctx, C, 2, args);
+    if (JS_IsException(matcher))
+        goto exception;
+    if (JS_ToLengthFree(ctx, &lastIndex,
+                        JS_GetProperty(ctx, R, JS_ATOM_lastIndex)))
+        goto exception;
+    if (JS_SetProperty(ctx, matcher, JS_ATOM_lastIndex,
+                       JS_NewInt32(ctx, lastIndex)) < 0)
+        goto exception;
+    
+    iter = JS_NewObjectClass(ctx, JS_CLASS_REGEXP_STRING_ITERATOR);
+    if (JS_IsException(iter))
+        goto exception;
+    it = js_malloc(ctx, sizeof(*it));
+    if (!it)
+        goto exception;
+    it->iterating_regexp = matcher;
+    it->iterated_string = S;
+    strp = JS_VALUE_GET_STRING(flags);
+    it->global = string_indexof_char(strp, 'g', 0) >= 0;
+    it->unicode = string_indexof_char(strp, 'u', 0) >= 0;
+    it->done = FALSE;
+    JS_SetOpaque(iter, it);
+
+    JS_FreeValue(ctx, C);
+    JS_FreeValue(ctx, flags);
+    return iter;
+ exception:
+    JS_FreeValue(ctx, S);
+    JS_FreeValue(ctx, C);
+    JS_FreeValue(ctx, flags);
+    JS_FreeValue(ctx, matcher);
+    JS_FreeValue(ctx, iter);
+    return JS_EXCEPTION;
+}
+
+typedef struct ValueBuffer {
+    JSContext *ctx;
+    JSValue *arr;
+    JSValue def[4];
+    int len;
+    int size;
+    int error_status;
+} ValueBuffer;
+
+static int value_buffer_init(JSContext *ctx, ValueBuffer *b)
+{
+    b->ctx = ctx;
+    b->len = 0;
+    b->size = 4;
+    b->error_status = 0;
+    b->arr = b->def;
+    return 0;
+}
+
+static void value_buffer_free(ValueBuffer *b)
+{
+    while (b->len > 0)
+        JS_FreeValue(b->ctx, b->arr[--b->len]);
+    if (b->arr != b->def)
+        js_free(b->ctx, b->arr);
+    b->arr = b->def;
+    b->size = 4;
+}
+
+static int value_buffer_append(ValueBuffer *b, JSValue val)
+{
+    if (b->error_status)
+        return -1;
+
+    if (b->len >= b->size) {
+        int new_size = (b->len + (b->len >> 1) + 31) & ~16;
+        size_t slack;
+        JSValue *new_arr;
+
+        if (b->arr == b->def) {
+            new_arr = js_realloc2(b->ctx, NULL, sizeof(*b->arr) * new_size, &slack);
+            if (new_arr)
+                memcpy(new_arr, b->def, sizeof b->def);
+        } else {
+            new_arr = js_realloc2(b->ctx, b->arr, sizeof(*b->arr) * new_size, &slack);
+        }
+        if (!new_arr) {
+            value_buffer_free(b);
+            JS_FreeValue(b->ctx, val);
+            b->error_status = -1;
+            return -1;
+        }
+        new_size += slack / sizeof(*new_arr);
+        b->arr = new_arr;
+        b->size = new_size;
+    }
+    b->arr[b->len++] = val;
+    return 0;
+}
+
+static int js_is_standard_regexp(JSContext *ctx, JSValueConst rx)
+{
+    JSValue val;
+    int res;
+
+    val = JS_GetProperty(ctx, rx, JS_ATOM_constructor);
+    if (JS_IsException(val))
+        return -1;
+    // rx.constructor === RegExp
+    res = js_same_value(ctx, val, ctx->regexp_ctor);
+    JS_FreeValue(ctx, val);
+    if (res) {
+        val = JS_GetProperty(ctx, rx, JS_ATOM_exec);
+        if (JS_IsException(val))
+            return -1;
+        // rx.exec === RE_exec
+        res = JS_IsCFunction(ctx, val, js_regexp_exec, 0);
+        JS_FreeValue(ctx, val);
+    }
+    return res;
+}
+
+static JSValue js_regexp_Symbol_replace(JSContext *ctx, JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    // [Symbol.replace](str, rep)
+    JSValueConst rx = this_val, rep = argv[1];
+    JSValueConst args[6];
+    JSValue str, rep_val, matched, tab, rep_str, namedCaptures, res;
+    JSString *sp, *rp;
+    StringBuffer b_s, *b = &b_s;
+    ValueBuffer v_b, *results = &v_b;
+    int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode;
+    uint32_t nCaptures;
+    int64_t position;
+
+    if (!JS_IsObject(rx))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    string_buffer_init(ctx, b, 0);
+    value_buffer_init(ctx, results);
+
+    rep_val = JS_UNDEFINED;
+    matched = JS_UNDEFINED;
+    tab = JS_UNDEFINED;
+    rep_str = JS_UNDEFINED;
+    namedCaptures = JS_UNDEFINED;
+
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        goto exception;
+        
+    sp = JS_VALUE_GET_STRING(str);
+    rp = NULL;
+    functionalReplace = JS_IsFunction(ctx, rep);
+    if (!functionalReplace) {
+        rep_val = JS_ToString(ctx, rep);
+        if (JS_IsException(rep_val))
+            goto exception;
+        rp = JS_VALUE_GET_STRING(rep_val);
+    }
+    fullUnicode = 0;
+    is_global = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_global));
+    if (is_global < 0)
+        goto exception;
+    if (is_global) {
+        fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode));
+        if (fullUnicode < 0)
+            goto exception;
+        if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0)
+            goto exception;
+    }
+
+    if (rp && rp->len == 0 && is_global && js_is_standard_regexp(ctx, rx)) {
+        /* use faster version for simple cases */
+        res = JS_RegExpDelete(ctx, rx, str);
+        goto done;
+    }
+    for(;;) {
+        JSValue result;
+        result = JS_RegExpExec(ctx, rx, str);
+        if (JS_IsException(result))
+            goto exception;
+        if (JS_IsNull(result))
+            break;
+        if (value_buffer_append(results, result) < 0)
+            goto exception;
+        if (!is_global)
+            break;
+        JS_FreeValue(ctx, matched);
+        matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
+        if (JS_IsException(matched))
+            goto exception;
+        if (JS_IsEmptyString(matched)) {
+            /* always advance of at least one char */
+            int64_t thisIndex, nextIndex;
+            if (JS_ToLengthFree(ctx, &thisIndex, JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0)
+                goto exception;
+            nextIndex = string_advance_index(sp, thisIndex, fullUnicode);
+            if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, nextIndex)) < 0)
+                goto exception;
+        }
+    }
+    nextSourcePosition = 0;
+    for(j = 0; j < results->len; j++) {
+        JSValueConst result;
+        result = results->arr[j];
+        if (js_get_length32(ctx, &nCaptures, result) < 0)
+            goto exception;
+        JS_FreeValue(ctx, matched);
+        matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
+        if (JS_IsException(matched))
+            goto exception;
+        if (JS_ToLengthFree(ctx, &position, JS_GetProperty(ctx, result, JS_ATOM_index)))
+            goto exception;
+        if (position > sp->len)
+            position = sp->len;
+        else if (position < 0)
+            position = 0;
+        /* ignore substition if going backward (can happen
+           with custom regexp object) */
+        JS_FreeValue(ctx, tab);
+        tab = JS_NewArray(ctx);
+        if (JS_IsException(tab))
+            goto exception;
+        if (JS_SetPropertyInt64(ctx, tab, 0, JS_DupValue(ctx, matched)) < 0)
+            goto exception;
+        for(n = 1; n < nCaptures; n++) {
+            JSValue capN;
+            capN = JS_GetPropertyInt64(ctx, result, n);
+            if (JS_IsException(capN))
+                goto exception;
+            if (!JS_IsUndefined(capN)) {
+                capN = JS_ToStringFree(ctx, capN);
+                if (JS_IsException(capN))
+                    goto exception;
+            }
+            if (JS_SetPropertyInt64(ctx, tab, n, capN) < 0)
+                goto exception;
+        }
+        JS_FreeValue(ctx, namedCaptures);
+        namedCaptures = JS_GetProperty(ctx, result, JS_ATOM_groups);
+        if (JS_IsException(namedCaptures))
+            goto exception;
+        if (functionalReplace) {
+            if (JS_SetPropertyInt64(ctx, tab, n++, JS_NewInt32(ctx, position)) < 0)
+                goto exception;
+            if (JS_SetPropertyInt64(ctx, tab, n++, JS_DupValue(ctx, str)) < 0)
+                goto exception;
+            if (!JS_IsUndefined(namedCaptures)) {
+                if (JS_SetPropertyInt64(ctx, tab, n++, JS_DupValue(ctx, namedCaptures)) < 0)
+                    goto exception;
+            }
+            args[0] = JS_UNDEFINED;
+            args[1] = tab;
+            JS_FreeValue(ctx, rep_str);
+            rep_str = JS_ToStringFree(ctx, js_function_apply(ctx, rep, 2, args, 0));
+        } else {
+            args[0] = matched;
+            args[1] = str;
+            args[2] = JS_NewInt32(ctx, position);
+            args[3] = tab;
+            args[4] = namedCaptures;
+            args[5] = rep_val;
+            JS_FreeValue(ctx, rep_str);
+            rep_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args);
+        }
+        if (JS_IsException(rep_str))
+            goto exception;
+        if (position >= nextSourcePosition) {
+            string_buffer_concat(b, sp, nextSourcePosition, position);
+            string_buffer_concat_value(b, rep_str);
+            nextSourcePosition = position + JS_VALUE_GET_STRING(matched)->len;
+        }
+    }
+    string_buffer_concat(b, sp, nextSourcePosition, sp->len);
+    res = string_buffer_end(b);
+    goto done1;
+
+exception:
+    res = JS_EXCEPTION;
+done:
+    string_buffer_free(b);
+done1:
+    value_buffer_free(results);
+    JS_FreeValue(ctx, rep_val);
+    JS_FreeValue(ctx, matched);
+    JS_FreeValue(ctx, tab);
+    JS_FreeValue(ctx, rep_str);
+    JS_FreeValue(ctx, namedCaptures);
+    JS_FreeValue(ctx, str);
+    return res;
+}
+
+static JSValue js_regexp_Symbol_search(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSValueConst rx = this_val;
+    JSValue str, previousLastIndex, currentLastIndex, result, index;
+
+    if (!JS_IsObject(rx))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    result = JS_UNDEFINED;
+    currentLastIndex = JS_UNDEFINED;
+    previousLastIndex = JS_UNDEFINED;
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        goto exception;
+
+    previousLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex);
+    if (JS_IsException(previousLastIndex))
+        goto exception;
+
+    if (!js_same_value(ctx, previousLastIndex, JS_NewInt32(ctx, 0))) {
+        if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) {
+            goto exception;
+        }
+    }
+    result = JS_RegExpExec(ctx, rx, str);
+    if (JS_IsException(result))
+        goto exception;
+    currentLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex);
+    if (JS_IsException(currentLastIndex))
+        goto exception;
+    if (js_same_value(ctx, currentLastIndex, previousLastIndex)) {
+        JS_FreeValue(ctx, previousLastIndex);
+    } else {
+        if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, previousLastIndex) < 0)
+            goto exception;
+    }
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, currentLastIndex);
+
+    if (JS_IsNull(result)) {
+        return JS_NewInt32(ctx, -1);
+    } else {
+        index = JS_GetProperty(ctx, result, JS_ATOM_index);
+        JS_FreeValue(ctx, result);
+        return index;
+    }
+
+exception:
+    JS_FreeValue(ctx, result);
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, currentLastIndex);
+    JS_FreeValue(ctx, previousLastIndex);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_regexp_Symbol_split(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    // [Symbol.split](str, limit)
+    JSValueConst rx = this_val;
+    JSValueConst args[2];
+    JSValue str, ctor, splitter, A, flags, z, sub;
+    JSString *strp;
+    uint32_t lim, size, p, q;
+    int unicodeMatching;
+    int64_t lengthA, e, numberOfCaptures, i;
+
+    if (!JS_IsObject(rx))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    ctor = JS_UNDEFINED;
+    splitter = JS_UNDEFINED;
+    A = JS_UNDEFINED;
+    flags = JS_UNDEFINED;
+    z = JS_UNDEFINED;
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        goto exception;
+    ctor = JS_SpeciesConstructor(ctx, rx, ctx->regexp_ctor);
+    if (JS_IsException(ctor))
+        goto exception;
+    flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_flags));
+    if (JS_IsException(flags))
+        goto exception;
+    strp = JS_VALUE_GET_STRING(flags);
+    unicodeMatching = string_indexof_char(strp, 'u', 0) >= 0;
+    if (string_indexof_char(strp, 'y', 0) < 0) {
+        flags = JS_ConcatString3(ctx, "", flags, "y");
+        if (JS_IsException(flags))
+            goto exception;
+    }
+    args[0] = rx;
+    args[1] = flags;
+    splitter = JS_CallConstructor(ctx, ctor, 2, args);
+    if (JS_IsException(splitter))
+        goto exception;
+    A = JS_NewArray(ctx);
+    if (JS_IsException(A))
+        goto exception;
+    lengthA = 0;
+    if (JS_IsUndefined(argv[1])) {
+        lim = 0xffffffff;
+    } else {
+        if (JS_ToUint32(ctx, &lim, argv[1]) < 0)
+            goto exception;
+        if (lim == 0)
+            goto done;
+    }
+    strp = JS_VALUE_GET_STRING(str);
+    p = q = 0;
+    size = strp->len;
+    if (size == 0) {
+        z = JS_RegExpExec(ctx, splitter, str);
+        if (JS_IsException(z))
+            goto exception;
+        if (JS_IsNull(z))
+            goto add_tail;
+        goto done;
+    }
+    while (q < size) {
+        if (JS_SetProperty(ctx, splitter, JS_ATOM_lastIndex, JS_NewInt32(ctx, q)) < 0)
+            goto exception;
+        JS_FreeValue(ctx, z);    
+        z = JS_RegExpExec(ctx, splitter, str);
+        if (JS_IsException(z))
+            goto exception;
+        if (JS_IsNull(z)) {
+            q = string_advance_index(strp, q, unicodeMatching);
+        } else {
+            if (JS_ToLengthFree(ctx, &e, JS_GetProperty(ctx, splitter, JS_ATOM_lastIndex)))
+                goto exception;
+            if (e > size)
+                e = size;
+            if (e == p) {
+                q = string_advance_index(strp, q, unicodeMatching);
+            } else {
+                sub = js_sub_string(ctx, strp, p, q);
+                if (JS_IsException(sub))
+                    goto exception;
+                if (JS_SetPropertyInt64(ctx, A, lengthA++, sub) < 0)
+                    goto exception;
+                if (lengthA == lim)
+                    goto done;
+                p = e;
+                if (js_get_length64(ctx, &numberOfCaptures, z))
+                    goto exception;
+                for(i = 1; i < numberOfCaptures; i++) {
+                    sub = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, z, i));
+                    if (JS_IsException(sub))
+                        goto exception;
+                    if (JS_SetPropertyInt64(ctx, A, lengthA++, sub) < 0)
+                        goto exception;
+                    if (lengthA == lim)
+                        goto done;
+                }
+                q = p;
+            }
+        }
+    }
+add_tail:
+    if (p > size)
+        p = size;
+    sub = js_sub_string(ctx, strp, p, size);
+    if (JS_IsException(sub))
+        goto exception;
+    if (JS_SetPropertyInt64(ctx, A, lengthA++, sub) < 0)
+        goto exception;
+    goto done;
+exception:
+    JS_FreeValue(ctx, A);
+    A = JS_EXCEPTION;
+done:
+    JS_FreeValue(ctx, str);
+    JS_FreeValue(ctx, ctor);
+    JS_FreeValue(ctx, splitter);
+    JS_FreeValue(ctx, flags);
+    JS_FreeValue(ctx, z);    
+    return A;
+}
+
+static const JSCFunctionListEntry js_regexp_funcs[] = {
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
+    //JS_CFUNC_DEF("__RegExpExec", 2, js_regexp___RegExpExec ),
+    //JS_CFUNC_DEF("__RegExpDelete", 2, js_regexp___RegExpDelete ),
+};
+
+static const JSCFunctionListEntry js_regexp_proto_funcs[] = {
+    JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ),
+    JS_CGETSET_DEF("source", js_regexp_get_source, NULL ),
+    JS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, 1 ),
+    JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, 2 ),
+    JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, 4 ),
+    JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, 8 ),
+    JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, 16 ),
+    JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, 32 ),
+    JS_CFUNC_DEF("exec", 1, js_regexp_exec ),
+    JS_CFUNC_DEF("compile", 2, js_regexp_compile ),
+    JS_CFUNC_DEF("test", 1, js_regexp_test ),
+    JS_CFUNC_DEF("toString", 0, js_regexp_toString ),
+    JS_CFUNC_DEF("[Symbol.replace]", 2, js_regexp_Symbol_replace ),
+    JS_CFUNC_DEF("[Symbol.match]", 1, js_regexp_Symbol_match ),
+    JS_CFUNC_DEF("[Symbol.matchAll]", 1, js_regexp_Symbol_matchAll ),
+    JS_CFUNC_DEF("[Symbol.search]", 1, js_regexp_Symbol_search ),
+    JS_CFUNC_DEF("[Symbol.split]", 2, js_regexp_Symbol_split ),
+    //JS_CGETSET_DEF("__source", js_regexp_get___source, NULL ),
+    //JS_CGETSET_DEF("__flags", js_regexp_get___flags, NULL ),
+};
+
+static const JSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = {
+    JS_ITERATOR_NEXT_DEF("next", 0, js_regexp_string_iterator_next, 0 ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "RegExp String Iterator", JS_PROP_CONFIGURABLE ),
+};
+
+void JS_AddIntrinsicRegExpCompiler(JSContext *ctx)
+{
+    ctx->compile_regexp = js_compile_regexp;
+}
+
+void JS_AddIntrinsicRegExp(JSContext *ctx)
+{
+    JSValueConst obj;
+
+    JS_AddIntrinsicRegExpCompiler(ctx);
+
+    ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs,
+                               countof(js_regexp_proto_funcs));
+    obj = JS_NewGlobalCConstructor(ctx, "RegExp", js_regexp_constructor, 2,
+                                   ctx->class_proto[JS_CLASS_REGEXP]);
+    ctx->regexp_ctor = JS_DupValue(ctx, obj);
+    JS_SetPropertyFunctionList(ctx, obj, js_regexp_funcs, countof(js_regexp_funcs));
+
+    ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR] =
+        JS_NewObjectProto(ctx, ctx->iterator_proto);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR],
+                               js_regexp_string_iterator_proto_funcs,
+                               countof(js_regexp_string_iterator_proto_funcs));
+}
+
+/* JSON */
+
+/* XXX: this parser is less strict than the JSON standard because we
+   reuse the Javascript tokenizer. It could be improved by adding a
+   specific JSON parse flag. */
+static JSValue json_parse_value(JSParseState *s)
+{
+    JSContext *ctx = s->ctx;
+    JSValue val = JS_NULL;
+    BOOL is_neg;
+    int ret;
+
+    switch(s->token.val) {
+    case '{':
+        {
+            JSValue prop_val, prop_str;
+
+            if (next_token(s))
+                goto fail;
+            val = JS_NewObject(ctx);
+            if (JS_IsException(val))
+                goto fail;
+            if (s->token.val != '}') {
+                for(;;) {
+                    if (s->token.val != TOK_STRING) {
+                        js_parse_error(s, "expecting property name");
+                        goto fail;
+                    }
+                    prop_str = JS_DupValue(ctx, s->token.u.str.str);
+                    if (next_token(s)) {
+                        JS_FreeValue(ctx, prop_str);
+                        goto fail;
+                    }
+                    if (js_parse_expect(s, ':')) {
+                        JS_FreeValue(ctx, prop_str);
+                        goto fail;
+                    }
+                    prop_val = json_parse_value(s);
+                    if (JS_IsException(prop_val)) {
+                        JS_FreeValue(ctx, prop_str);
+                        goto fail;
+                    }
+                    ret = JS_DefinePropertyValueValue(ctx, val, prop_str,
+                                                      prop_val, JS_PROP_C_W_E);
+                    if (ret < 0)
+                        goto fail;
+
+                    if (s->token.val != ',')
+                        break;
+                    if (next_token(s))
+                        goto fail;
+                }
+            }
+            if (js_parse_expect(s, '}'))
+                goto fail;
+        }
+        break;
+    case '[':
+        {
+            JSValue el;
+            uint32_t idx;
+
+            if (next_token(s))
+                goto fail;
+            val = JS_NewArray(ctx);
+            if (JS_IsException(val))
+                goto fail;
+            if (s->token.val != ']') {
+                idx = 0;
+                for(;;) {
+                    el = json_parse_value(s);
+                    if (JS_IsException(el))
+                        goto fail;
+                    ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E);
+                    if (ret < 0)
+                        goto fail;
+                    if (s->token.val != ',')
+                        break;
+                    if (next_token(s))
+                        goto fail;
+                    idx++;
+                }
+            }
+            if (js_parse_expect(s, ']'))
+                goto fail;
+        }
+        break;
+    case TOK_STRING:
+        val = JS_DupValue(ctx, s->token.u.str.str);
+        if (next_token(s))
+            goto fail;
+        break;
+    case TOK_NUMBER:
+        is_neg = 0;
+        goto number;
+    case '-':
+        if (next_token(s))
+            goto fail;
+        if (s->token.val != TOK_NUMBER) {
+            js_parse_error(s, "number expected");
+            goto fail;
+        }
+        is_neg = 1;
+    number:
+#ifdef CONFIG_BIGNUM
+        val = JS_DupValue(ctx, s->token.u.num.val);
+        if (is_neg) {
+            switch(JS_VALUE_GET_NORM_TAG(val)) {
+            case JS_TAG_BIG_INT:
+            case JS_TAG_BIG_FLOAT:
+                {
+                    JSBigFloat *p;
+                    p = JS_VALUE_GET_PTR(val);
+                    bf_neg(&p->num);
+                }
+                break;
+            case JS_TAG_FLOAT64:
+                {
+                    double d;
+                    d = JS_VALUE_GET_FLOAT64(val);
+                    val = __JS_NewFloat64(ctx, -d);
+                }
+                break;
+            default:
+            case JS_TAG_INT:
+                {
+                    int v;
+                    v = JS_VALUE_GET_INT(val);
+                    if (v == 0 && !is_bignum_mode(s->ctx))
+                        val = __JS_NewFloat64(ctx, -0.0);
+                    else
+                        val = JS_NewInt64(ctx, -(int64_t)v);
+                }
+                break;
+            }
+        }
+#else
+        val = s->token.u.num.val;
+        if (is_neg) {
+            double d;
+            JS_ToFloat64(ctx, &d, val);  /* no exception possible */
+            val = JS_NewFloat64(ctx, -d);
+        }
+#endif
+        if (next_token(s))
+            goto fail;
+        break;
+    case TOK_FALSE:
+    case TOK_TRUE:
+        val = JS_NewBool(ctx, s->token.val - TOK_FALSE);
+        if (next_token(s))
+            goto fail;
+        break;
+    case TOK_NULL:
+        if (next_token(s))
+            goto fail;
+        break;
+    default:
+        if (s->token.val == TOK_EOF) {
+            js_parse_error(s, "unexpected end of input");
+        } else {
+            js_parse_error(s, "unexpected token: '%.*s'",
+                           (int)(s->buf_ptr - s->token.ptr), s->token.ptr);
+        }
+        goto fail;
+    }
+    return val;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len,
+                     const char *filename)
+{
+    JSParseState s1, *s = &s1;
+    JSValue val;
+
+    js_parse_init(ctx, s, buf, buf_len, filename);
+
+    if (next_token(s))
+        goto fail;
+    val = json_parse_value(s);
+    if (JS_IsException(val))
+        goto fail;
+    if (s->token.val != TOK_EOF) {
+        if (js_parse_error(s, "unexpected data at the end"))
+            goto fail;
+    }
+    return val;
+ fail:
+    free_token(s, &s->token);
+    return JS_EXCEPTION;
+}
+
+static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder,
+                                         JSAtom name, JSValueConst reviver)
+{
+    JSValue val, new_el, name_val, res;
+    JSValueConst args[2];
+    int ret, is_array;
+    uint32_t i, len = 0;
+    JSAtom prop;
+    JSPropertyEnum *atoms = NULL;
+
+    if (js_check_stack_overflow(ctx, 0)) {
+        return JS_ThrowStackOverflow(ctx);
+    }
+
+    val = JS_GetProperty(ctx, holder, name);
+    if (JS_IsException(val))
+        return val;
+    if (JS_IsObject(val)) {
+        is_array = JS_IsArray(ctx, val);
+        if (is_array < 0)
+            goto fail;
+        if (is_array) {
+            if (js_get_length32(ctx, &len, val))
+                goto fail;
+        } else {
+            ret = JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, JS_VALUE_GET_OBJ(val), JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK);
+            if (ret < 0)
+                goto fail;
+        }
+        for(i = 0; i < len; i++) {
+            if (is_array) {
+                prop = JS_NewAtomUInt32(ctx, i);
+                if (prop == JS_ATOM_NULL)
+                    goto fail;
+            } else {
+                prop = JS_DupAtom(ctx, atoms[i].atom);
+            }
+            new_el = internalize_json_property(ctx, val, prop, reviver);
+            if (JS_IsException(new_el)) {
+                JS_FreeAtom(ctx, prop);
+                goto fail;
+            }
+            if (JS_IsUndefined(new_el)) {
+                ret = JS_DeleteProperty(ctx, val, prop, 0);
+            } else {
+                ret = JS_DefinePropertyValue(ctx, val, prop, new_el, JS_PROP_C_W_E);
+            }
+            JS_FreeAtom(ctx, prop);
+            if (ret < 0)
+                goto fail;
+        }
+    }
+    js_free_prop_enum(ctx, atoms, len);
+    atoms = NULL;
+    name_val = JS_AtomToValue(ctx, name);
+    if (JS_IsException(name_val))
+        goto fail;
+    args[0] = name_val;
+    args[1] = val;
+    res = JS_Call(ctx, reviver, holder, 2, args);
+    JS_FreeValue(ctx, name_val);
+    JS_FreeValue(ctx, val);
+    return res;
+ fail:
+    js_free_prop_enum(ctx, atoms, len);
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    JSValue obj, root;
+    JSValueConst reviver;
+    const char *str;
+    size_t len;
+
+    str = JS_ToCStringLen(ctx, &len, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+    obj = JS_ParseJSON(ctx, str, len, "<input>");
+    JS_FreeCString(ctx, str);
+    if (JS_IsException(obj))
+        return obj;
+    if (argc > 1 && JS_IsFunction(ctx, argv[1])) {
+        reviver = argv[1];
+        root = JS_NewObject(ctx);
+        if (JS_IsException(root)) {
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+        if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj,
+                                   JS_PROP_C_W_E) < 0) {
+            JS_FreeValue(ctx, root);
+            return JS_EXCEPTION;
+        }
+        obj = internalize_json_property(ctx, root, JS_ATOM_empty_string,
+                                        reviver);
+        JS_FreeValue(ctx, root);
+    }
+    return obj;
+}
+
+typedef struct JSONStringifyContext {
+    JSValueConst replacer_func;
+    JSValue stack;
+    JSValue property_list;
+    JSValue gap;
+    JSValue empty;
+    StringBuffer *b;
+} JSONStringifyContext;
+
+static JSValue JS_ToQuotedStringFree(JSContext *ctx, JSValue val) {
+    JSValue r = JS_ToQuotedString(ctx, val);
+    JS_FreeValue(ctx, val);
+    return r;
+}
+
+#ifdef CONFIG_BIGNUM
+static inline BOOL JS_IsBigInt(JSContext *ctx, JSValueConst v);
+#endif
+
+static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc,
+                             JSValueConst holder, JSValue val, JSValueConst key)
+{
+    JSValue v;
+    JSValueConst args[2];
+
+    if (JS_IsObject(val)
+#ifdef CONFIG_BIGNUM
+    ||  JS_IsBigInt(ctx, val)   /* XXX: probably useless */
+#endif
+        ) {
+            JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON);
+            if (JS_IsException(f))
+                goto exception;
+            if (JS_IsFunction(ctx, f)) {
+                v = JS_CallFree(ctx, f, val, 1, &key);
+                JS_FreeValue(ctx, val);
+                val = v;
+                if (JS_IsException(val))
+                    goto exception;
+            } else {
+                JS_FreeValue(ctx, f);
+            }
+        }
+
+    if (!JS_IsUndefined(jsc->replacer_func)) {
+        args[0] = key;
+        args[1] = val;
+        v = JS_Call(ctx, jsc->replacer_func, holder, 2, args);
+        JS_FreeValue(ctx, val);
+        val = v;
+        if (JS_IsException(val))
+            goto exception;
+    }
+
+    switch (JS_VALUE_GET_NORM_TAG(val)) {
+    case JS_TAG_OBJECT:
+        if (JS_IsFunction(ctx, val))
+            break;
+    case JS_TAG_STRING:
+    case JS_TAG_INT:
+    case JS_TAG_FLOAT64:
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_FLOAT:
+#endif
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+#endif
+    case JS_TAG_EXCEPTION:
+        return val;
+    default:
+        break;
+    }
+    JS_FreeValue(ctx, val);
+    return JS_UNDEFINED;
+
+exception:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
+                          JSValueConst holder, JSValue val,
+                          JSValueConst indent)
+{
+    JSValue indent1, sep, sep1, tab, v, prop;
+    JSObject *p;
+    int64_t i, len;
+    int cl, ret;
+    BOOL has_content;
+    
+    indent1 = JS_UNDEFINED;
+    sep = JS_UNDEFINED;
+    sep1 = JS_UNDEFINED;
+    tab = JS_UNDEFINED;
+    prop = JS_UNDEFINED;
+
+    switch (JS_VALUE_GET_NORM_TAG(val)) {
+    case JS_TAG_OBJECT:
+        p = JS_VALUE_GET_OBJ(val);
+        cl = p->class_id;
+        if (cl == JS_CLASS_STRING) {
+            val = JS_ToStringFree(ctx, val);
+            if (JS_IsException(val))
+                goto exception;
+            val = JS_ToQuotedStringFree(ctx, val);
+            if (JS_IsException(val))
+                goto exception;
+            return string_buffer_concat_value_free(jsc->b, val);
+        } else if (cl == JS_CLASS_NUMBER) {
+            val = JS_ToNumberFree(ctx, val);
+            if (JS_IsException(val))
+                goto exception;
+            return string_buffer_concat_value_free(jsc->b, val);
+        } else if (cl == JS_CLASS_BOOLEAN) {
+            ret = string_buffer_concat_value(jsc->b, p->u.object_data);
+            JS_FreeValue(ctx, val);
+            return ret;
+        }
+#ifdef CONFIG_BIGNUM
+        else if (cl == JS_CLASS_BIG_FLOAT) {
+            return string_buffer_concat_value_free(jsc->b, val);
+        } else if (cl == JS_CLASS_BIG_INT) {
+            JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify");
+            goto exception;
+        }
+#endif
+        v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val);
+        if (JS_IsException(v))
+            goto exception;
+        if (JS_ToBoolFree(ctx, v)) {
+            JS_ThrowTypeError(ctx, "circular reference");
+            goto exception;
+        }
+        indent1 = JS_ConcatString(ctx, JS_DupValue(ctx, indent), JS_DupValue(ctx, jsc->gap));
+        if (JS_IsException(indent1))
+            goto exception;
+        if (!JS_IsEmptyString(jsc->gap)) {
+            sep = JS_ConcatString3(ctx, "\n", JS_DupValue(ctx, indent1), "");
+            if (JS_IsException(sep))
+                goto exception;
+            sep1 = JS_NewString(ctx, " ");
+            if (JS_IsException(sep1))
+                goto exception;
+        } else {
+            sep = JS_DupValue(ctx, jsc->empty);
+            sep1 = JS_DupValue(ctx, jsc->empty);
+        }
+        v = js_array_push(ctx, jsc->stack, 1, (JSValueConst *)&val, 0);
+        if (check_exception_free(ctx, v))
+            goto exception;
+        ret = JS_IsArray(ctx, val);
+        if (ret < 0)
+            goto exception;
+        if (ret) {
+            if (js_get_length64(ctx, &len, val))
+                goto exception;
+            string_buffer_putc8(jsc->b, '[');
+            for(i = 0; i < len; i++) {
+                if (i > 0)
+                    string_buffer_putc8(jsc->b, ',');
+                string_buffer_concat_value(jsc->b, sep);
+                v = JS_GetPropertyInt64(ctx, val, i);
+                if (JS_IsException(v))
+                    goto exception;
+                /* XXX: could do this string conversion only when needed */
+                prop = JS_ToStringFree(ctx, JS_NewInt64(ctx, i));
+                if (JS_IsException(prop))
+                    goto exception;
+                v = js_json_check(ctx, jsc, val, v, prop);
+                JS_FreeValue(ctx, prop);
+                prop = JS_UNDEFINED;
+                if (JS_IsException(v))
+                    goto exception;
+                if (JS_IsUndefined(v))
+                    v = JS_NULL;
+                if (js_json_to_str(ctx, jsc, val, v, indent1))
+                    goto exception;
+            }
+            if (len > 0 && !JS_IsEmptyString(jsc->gap)) {
+                string_buffer_putc8(jsc->b, '\n');
+                string_buffer_concat_value(jsc->b, indent);
+            }
+            string_buffer_putc8(jsc->b, ']');
+        } else {
+            if (!JS_IsUndefined(jsc->property_list))
+                tab = JS_DupValue(ctx, jsc->property_list);
+            else
+                tab = js_object_keys(ctx, JS_UNDEFINED, 1, (JSValueConst *)&val, JS_ITERATOR_KIND_KEY);
+            if (JS_IsException(tab))
+                goto exception;
+            if (js_get_length64(ctx, &len, tab))
+                goto exception;
+            string_buffer_putc8(jsc->b, '{');
+            has_content = FALSE;
+            for(i = 0; i < len; i++) {
+                JS_FreeValue(ctx, prop);
+                prop = JS_GetPropertyInt64(ctx, tab, i);
+                if (JS_IsException(prop))
+                    goto exception;
+                v = JS_GetPropertyValue(ctx, val, JS_DupValue(ctx, prop));
+                if (JS_IsException(v))
+                    goto exception;
+                v = js_json_check(ctx, jsc, val, v, prop);
+                if (JS_IsException(v))
+                    goto exception;
+                if (!JS_IsUndefined(v)) {
+                    if (has_content)
+                        string_buffer_putc8(jsc->b, ',');
+                    prop = JS_ToQuotedStringFree(ctx, prop);
+                    if (JS_IsException(prop)) {
+                        JS_FreeValue(ctx, v);
+                        goto exception;
+                    }
+                    string_buffer_concat_value(jsc->b, sep);
+                    string_buffer_concat_value(jsc->b, prop);
+                    string_buffer_putc8(jsc->b, ':');
+                    string_buffer_concat_value(jsc->b, sep1);
+                    if (js_json_to_str(ctx, jsc, val, v, indent1))
+                        goto exception;
+                    has_content = TRUE;
+                }
+            }
+            if (has_content && JS_VALUE_GET_STRING(jsc->gap)->len != 0) {
+                string_buffer_putc8(jsc->b, '\n');
+                string_buffer_concat_value(jsc->b, indent);
+            }
+            string_buffer_putc8(jsc->b, '}');
+        }
+        if (check_exception_free(ctx, js_array_pop(ctx, jsc->stack, 0, NULL, 0)))
+            goto exception;
+        JS_FreeValue(ctx, val);
+        JS_FreeValue(ctx, tab);
+        JS_FreeValue(ctx, sep);
+        JS_FreeValue(ctx, sep1);
+        JS_FreeValue(ctx, indent1);
+        JS_FreeValue(ctx, prop);
+        return 0;
+    case JS_TAG_STRING:
+        val = JS_ToQuotedStringFree(ctx, val);
+        if (JS_IsException(val))
+            goto exception;
+        goto concat_value;
+    case JS_TAG_FLOAT64:
+        if (!isfinite(JS_VALUE_GET_FLOAT64(val))) {
+            val = JS_NULL;
+        }
+        goto concat_value;
+    case JS_TAG_INT:
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_FLOAT:
+#endif
+    case JS_TAG_BOOL:
+    case JS_TAG_NULL:
+    concat_value:
+        return string_buffer_concat_value_free(jsc->b, val);
+#ifdef CONFIG_BIGNUM
+    case JS_TAG_BIG_INT:
+        JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify");
+        goto exception;
+#endif
+    default:
+        JS_FreeValue(ctx, val);
+        return 0;
+    }
+    
+exception:
+    JS_FreeValue(ctx, val);
+    JS_FreeValue(ctx, tab);
+    JS_FreeValue(ctx, sep);
+    JS_FreeValue(ctx, sep1);
+    JS_FreeValue(ctx, indent1);
+    JS_FreeValue(ctx, prop);
+    return -1;
+}
+
+static JSValue js_json_stringify(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    // stringify(val, replacer, space)
+    StringBuffer b_s;
+    JSONStringifyContext jsc_s, *jsc = &jsc_s;
+    JSValueConst replacer = argv[1];
+    JSValue val, v, space, ret, wrapper;
+    int res;
+    int64_t i, j, n;
+
+    jsc->replacer_func = JS_UNDEFINED;
+    jsc->stack = JS_UNDEFINED;
+    jsc->property_list = JS_UNDEFINED;
+    jsc->gap = JS_UNDEFINED;
+    jsc->b = &b_s;
+    jsc->empty = JS_AtomToString(ctx, JS_ATOM_empty_string);
+    ret = JS_UNDEFINED;
+    wrapper = JS_UNDEFINED;
+
+    string_buffer_init(ctx, jsc->b, 0);
+    jsc->stack = JS_NewArray(ctx);
+    if (JS_IsException(jsc->stack))
+        goto exception;
+    if (JS_IsFunction(ctx, replacer)) {
+        jsc->replacer_func = replacer;
+    } else {
+        res = JS_IsArray(ctx, replacer);
+        if (res < 0)
+            goto exception;
+        if (res) {
+            /* XXX: enumeration is not fully correct */
+            jsc->property_list = JS_NewArray(ctx);
+            if (JS_IsException(jsc->property_list))
+                goto exception;
+            if (js_get_length64(ctx, &n, replacer))
+                goto exception;
+            for (i = j = 0; i < n; i++) {
+                JSValue present;
+                v = JS_GetPropertyInt64(ctx, replacer, i);
+                if (JS_IsException(v))
+                    goto exception;
+                if (JS_IsObject(v)) {
+                    JSObject *p = JS_VALUE_GET_OBJ(v);
+                    if (p->class_id == JS_CLASS_STRING ||
+                        p->class_id == JS_CLASS_NUMBER) {
+                        v = JS_ToStringFree(ctx, v);
+                        if (JS_IsException(v))
+                            goto exception;
+                    } else {
+                        JS_FreeValue(ctx, v);
+                        continue;
+                    }
+                } else if (JS_IsNumber(v)) {
+                    v = JS_ToStringFree(ctx, v);
+                    if (JS_IsException(v))
+                        goto exception;
+                } else if (!JS_IsString(v)) {
+                    JS_FreeValue(ctx, v);
+                    continue;
+                }
+                present = js_array_includes(ctx, jsc->property_list,
+                                            1, (JSValueConst *)&v);
+                if (JS_IsException(present)) {
+                    JS_FreeValue(ctx, v);
+                    goto exception;
+                }
+                if (!JS_ToBoolFree(ctx, present)) {
+                    JS_SetPropertyInt64(ctx, jsc->property_list, j++, v);
+                } else {
+                    JS_FreeValue(ctx, v);
+                }
+            }
+        }
+    }
+    space = JS_DupValue(ctx, argv[2]);
+    if (JS_IsObject(space)) {
+        JSObject *p = JS_VALUE_GET_OBJ(space);
+        if (p->class_id == JS_CLASS_NUMBER) {
+            space = JS_ToNumberFree(ctx, space);
+        } else if (p->class_id == JS_CLASS_STRING) {
+            space = JS_ToStringFree(ctx, space);
+        }
+        if (JS_IsException(space)) {
+            JS_FreeValue(ctx, space);
+            goto exception;
+        }
+    }
+    if (JS_IsNumber(space)) {
+        int n;
+        if (JS_ToInt32Clamp(ctx, &n, space, 0, 10, 0))
+            goto exception;
+        jsc->gap = JS_NewStringLen(ctx, "          ", n);
+    } else if (JS_IsString(space)) {
+        JSString *p = JS_VALUE_GET_STRING(space);
+        jsc->gap = js_sub_string(ctx, p, 0, min_int(p->len, 10));
+    } else {
+        jsc->gap = JS_DupValue(ctx, jsc->empty);
+    }
+    JS_FreeValue(ctx, space);
+    if (JS_IsException(jsc->gap))
+        goto exception;
+    wrapper = JS_NewObject(ctx);
+    if (JS_IsException(wrapper))
+        goto exception;
+    if (JS_DefinePropertyValue(ctx, wrapper, JS_ATOM_empty_string,
+                               JS_DupValue(ctx, argv[0]), JS_PROP_C_W_E) < 0)
+        goto exception;
+    val = JS_DupValue(ctx, argv[0]);
+                           
+    val = js_json_check(ctx, jsc, wrapper, val, jsc->empty);
+    if (JS_IsException(val))
+        goto exception;
+    if (JS_IsUndefined(val)) {
+        ret = JS_UNDEFINED;
+        goto done1;
+    }
+    if (js_json_to_str(ctx, jsc, wrapper, val, jsc->empty))
+        goto exception;
+
+    ret = string_buffer_end(jsc->b);
+    goto done;
+
+exception:
+    ret = JS_EXCEPTION;
+done1:
+    string_buffer_free(jsc->b);
+done:
+    JS_FreeValue(ctx, wrapper);
+    JS_FreeValue(ctx, jsc->empty);
+    JS_FreeValue(ctx, jsc->gap);
+    JS_FreeValue(ctx, jsc->property_list);
+    JS_FreeValue(ctx, jsc->stack);
+    return ret;
+}
+
+static const JSCFunctionListEntry js_json_funcs[] = {
+    JS_CFUNC_DEF("parse", 2, js_json_parse ),
+    JS_CFUNC_DEF("stringify", 3, js_json_stringify ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_json_obj[] = {
+    JS_OBJECT_DEF("JSON", js_json_funcs, countof(js_json_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
+};
+
+void JS_AddIntrinsicJSON(JSContext *ctx)
+{
+    /* add JSON as autoinit object */
+    JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_json_obj, countof(js_json_obj));
+}
+
+/* Reflect */
+
+static JSValue js_reflect_apply(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    return js_function_apply(ctx, argv[0], max_int(0, argc - 1), argv + 1, 0);
+}
+
+static JSValue js_reflect_construct(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSValueConst func, array_arg, new_target;
+    JSValue *tab, ret;
+    uint32_t len;
+
+    func = argv[0];
+    array_arg = argv[1];
+    if (argc > 2) {
+        new_target = argv[2];
+        if (!JS_IsConstructor(ctx, new_target))
+            return JS_ThrowTypeError(ctx, "not a constructor");
+    } else {
+        new_target = func;
+    }
+    tab = build_arg_list(ctx, &len, array_arg);
+    if (!tab)
+        return JS_EXCEPTION;
+    ret = JS_CallConstructor2(ctx, func, new_target, len, (JSValueConst *)tab);
+    free_arg_list(ctx, tab, len);
+    return ret;
+}
+
+static JSValue js_reflect_deleteProperty(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    JSValueConst obj;
+    JSAtom atom;
+    int ret;
+
+    obj = argv[0];
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    atom = JS_ValueToAtom(ctx, argv[1]);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return JS_EXCEPTION;
+    ret = JS_DeleteProperty(ctx, obj, atom, 0);
+    JS_FreeAtom(ctx, atom);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_reflect_get(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    JSValueConst obj, prop, receiver;
+    JSAtom atom;
+    JSValue ret;
+
+    obj = argv[0];
+    prop = argv[1];
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    if (argc > 2)
+        receiver = argv[2];
+    else
+        receiver = obj;
+    atom = JS_ValueToAtom(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return JS_EXCEPTION;
+    ret = JS_GetPropertyInternal(ctx, obj, atom, receiver, FALSE);
+    JS_FreeAtom(ctx, atom);
+    return ret;
+}
+
+static JSValue js_reflect_has(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    JSValueConst obj, prop;
+    JSAtom atom;
+    int ret;
+
+    obj = argv[0];
+    prop = argv[1];
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    atom = JS_ValueToAtom(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return JS_EXCEPTION;
+    ret = JS_HasProperty(ctx, obj, atom);
+    JS_FreeAtom(ctx, atom);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_reflect_set(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    JSValueConst obj, prop, val, receiver;
+    int ret;
+    JSAtom atom;
+
+    obj = argv[0];
+    prop = argv[1];
+    val = argv[2];
+    if (argc > 3)
+        receiver = argv[3];
+    else
+        receiver = obj;
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    atom = JS_ValueToAtom(ctx, prop);
+    if (unlikely(atom == JS_ATOM_NULL))
+        return JS_EXCEPTION;
+    ret = JS_SetPropertyGeneric(ctx, JS_VALUE_GET_OBJ(obj), atom,
+                                JS_DupValue(ctx, val), receiver, 0);
+    JS_FreeAtom(ctx, atom);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_reflect_setPrototypeOf(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    int ret;
+    ret = JS_SetPrototypeInternal(ctx, argv[0], argv[1], FALSE);
+    if (ret < 0)
+        return JS_EXCEPTION;
+    else
+        return JS_NewBool(ctx, ret);
+}
+
+static JSValue js_reflect_ownKeys(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    return JS_GetOwnPropertyNames2(ctx, argv[0],
+                                   JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK,
+                                   JS_ITERATOR_KIND_KEY);
+}
+
+static const JSCFunctionListEntry js_reflect_funcs[] = {
+    JS_CFUNC_DEF("apply", 3, js_reflect_apply ),
+    JS_CFUNC_DEF("construct", 2, js_reflect_construct ),
+    JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 1 ),
+    JS_CFUNC_DEF("deleteProperty", 2, js_reflect_deleteProperty ),
+    JS_CFUNC_DEF("get", 2, js_reflect_get ),
+    JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 1 ),
+    JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 1 ),
+    JS_CFUNC_DEF("has", 2, js_reflect_has ),
+    JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 1 ),
+    JS_CFUNC_DEF("ownKeys", 1, js_reflect_ownKeys ),
+    JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 1 ),
+    JS_CFUNC_DEF("set", 3, js_reflect_set ),
+    JS_CFUNC_DEF("setPrototypeOf", 2, js_reflect_setPrototypeOf ),
+};
+
+static const JSCFunctionListEntry js_reflect_obj[] = {
+    JS_OBJECT_DEF("Reflect", js_reflect_funcs, countof(js_reflect_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
+};
+
+/* Proxy */
+
+static void js_proxy_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY);
+    if (s) {
+        JS_FreeValueRT(rt, s->target);
+        JS_FreeValueRT(rt, s->handler);
+        JS_FreeValueRT(rt, s->proto);
+        js_free_rt(rt, s);
+    }
+}
+
+static void js_proxy_mark(JSRuntime *rt, JSValueConst val,
+                          JS_MarkFunc *mark_func)
+{
+    JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY);
+    if (s) {
+        JS_MarkValue(rt, s->target, mark_func);
+        JS_MarkValue(rt, s->handler, mark_func);
+        JS_MarkValue(rt, s->proto, mark_func);
+    }
+}
+
+static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx)
+{
+    return JS_ThrowTypeError(ctx, "revoked proxy");
+}
+
+static JSProxyData *get_proxy_method(JSContext *ctx, JSValue *pmethod,
+                                     JSValueConst obj, JSAtom name)
+{
+    JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY);
+    JSValue method;
+
+    /* safer to test recursion in all proxy methods */
+    if (js_check_stack_overflow(ctx, 0)) {
+        JS_ThrowStackOverflow(ctx);
+        return NULL;
+    }
+    
+    /* 's' should never be NULL */
+    if (s->is_revoked) {
+        JS_ThrowTypeErrorRevokedProxy(ctx);
+        return NULL;
+    }
+    method = JS_GetProperty(ctx, s->handler, name);
+    if (JS_IsException(method))
+        return NULL;
+    if (JS_IsNull(method))
+        method = JS_UNDEFINED;
+    *pmethod = method;
+    return s;
+}
+
+static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj)
+{
+    JSProxyData *s;
+    JSValue method, ret;
+    JSValueConst proto1;
+    int res;
+
+    /* must check for timeout to avoid infinite loop in instanceof */
+    if (js_poll_interrupts(ctx))
+        return JS_EXCEPTION;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf);
+    if (!s)
+        return JS_EXCEPTION;
+    if (JS_IsUndefined(method))
+        return JS_GetPrototype(ctx, s->target);
+    ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target);
+    if (JS_IsException(ret))
+        return ret;
+    if (JS_VALUE_GET_TAG(ret) != JS_TAG_NULL &&
+        JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) {
+        goto fail;
+    }
+    res = JS_IsExtensible(ctx, s->target);
+    if (res < 0) {
+        JS_FreeValue(ctx, ret);
+        return JS_EXCEPTION;
+    }
+    if (!res) {
+        /* check invariant */
+        proto1 = JS_GetPrototype(ctx, s->target);
+        if (JS_IsException(proto1)) {
+            JS_FreeValue(ctx, ret);
+            return JS_EXCEPTION;
+        }
+        if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) {
+        fail:
+            JS_FreeValue(ctx, ret);
+            return JS_ThrowTypeError(ctx, "proxy: inconsistent prototype");
+        }
+    }
+    /* store the prototype in the proxy so that its refcount is at least 1 */
+    set_value(ctx, &s->proto, ret);
+    return (JSValueConst)ret;
+}
+
+static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj,
+                                   JSValueConst proto_val, BOOL throw_flag)
+{
+    JSProxyData *s;
+    JSValue method, ret;
+    JSValueConst args[2], proto1;
+    BOOL res;
+    int res2;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_setPrototypeOf);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method))
+        return JS_SetPrototypeInternal(ctx, s->target, proto_val, throw_flag);
+    args[0] = s->target;
+    args[1] = proto_val;
+    ret = JS_CallFree(ctx, method, s->handler, 2, args);
+    if (JS_IsException(ret))
+        return -1;
+    res = JS_ToBoolFree(ctx, ret);
+    if (!res) {
+        if (throw_flag) {
+            JS_ThrowTypeError(ctx, "proxy: bad prototype");
+            return -1;
+        } else {
+            return FALSE;
+        }
+    }
+    res2 = JS_IsExtensible(ctx, s->target);
+    if (res2 < 0)
+        return -1;
+    if (!res2) {
+        proto1 = JS_GetPrototype(ctx, s->target);
+        if (JS_IsException(proto1))
+            return -1;
+        if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) {
+            JS_ThrowTypeError(ctx, "proxy: inconsistent prototype");
+            return -1;
+        }
+    }
+    return TRUE;
+}
+
+static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj)
+{
+    JSProxyData *s;
+    JSValue method, ret;
+    BOOL res;
+    int res2;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_isExtensible);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method))
+        return JS_IsExtensible(ctx, s->target);
+    ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target);
+    if (JS_IsException(ret))
+        return -1;
+    res = JS_ToBoolFree(ctx, ret);
+    res2 = JS_IsExtensible(ctx, s->target);
+    if (res2 < 0)
+        return res2;
+    if (res != res2) {
+        JS_ThrowTypeError(ctx, "proxy: inconsistent isExtensible");
+        return -1;
+    }
+    return res;
+}
+
+static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj)
+{
+    JSProxyData *s;
+    JSValue method, ret;
+    BOOL res;
+    int res2;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_preventExtensions);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method))
+        return JS_PreventExtensions(ctx, s->target);
+    ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target);
+    if (JS_IsException(ret))
+        return -1;
+    res = JS_ToBoolFree(ctx, ret);
+    if (res) {
+        res2 = JS_IsExtensible(ctx, s->target);
+        if (res2 < 0)
+            return res2;
+        if (res2) {
+            JS_ThrowTypeError(ctx, "proxy: inconsistent preventExtensions");
+            return -1;
+        }
+    }
+    return res;
+}
+
+static int js_proxy_has(JSContext *ctx, JSValueConst obj, JSAtom atom)
+{
+    JSProxyData *s;
+    JSValue method, ret1, atom_val;
+    int ret, res;
+    JSObject *p;
+    JSValueConst args[2];
+    BOOL res2;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_has);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method))
+        return JS_HasProperty(ctx, s->target, atom);
+    atom_val = JS_AtomToValue(ctx, atom);
+    if (JS_IsException(atom_val)) {
+        JS_FreeValue(ctx, method);
+        return -1;
+    }
+    args[0] = s->target;
+    args[1] = atom_val;
+    ret1 = JS_CallFree(ctx, method, s->handler, 2, args);
+    JS_FreeValue(ctx, atom_val);
+    if (JS_IsException(ret1))
+        return -1;
+    ret = JS_ToBoolFree(ctx, ret1);
+    if (!ret) {
+        JSPropertyDescriptor desc;
+        p = JS_VALUE_GET_OBJ(s->target);
+        res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
+        if (res < 0)
+            return -1;
+        if (res) {
+            res2 = !(desc.flags & JS_PROP_CONFIGURABLE);
+            js_free_desc(ctx, &desc);
+            if (res2 || !p->extensible) {
+                JS_ThrowTypeError(ctx, "proxy: inconsistent has");
+                return -1;
+            }
+        }
+    }
+    return ret;
+}
+
+static JSValue js_proxy_get(JSContext *ctx, JSValueConst obj, JSAtom atom,
+                            JSValueConst receiver)
+{
+    JSProxyData *s;
+    JSValue method, ret, atom_val;
+    int res;
+    JSValueConst args[3];
+    JSPropertyDescriptor desc;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_get);
+    if (!s)
+        return JS_EXCEPTION;
+    /* Note: recursion is possible thru the prototype of s->target */
+    if (JS_IsUndefined(method))
+        return JS_GetPropertyInternal(ctx, s->target, atom, receiver, FALSE);
+    atom_val = JS_AtomToValue(ctx, atom);
+    if (JS_IsException(atom_val)) {
+        JS_FreeValue(ctx, method);
+        return JS_EXCEPTION;
+    }
+    args[0] = s->target;
+    args[1] = atom_val;
+    args[2] = receiver;
+    ret = JS_CallFree(ctx, method, s->handler, 3, args);
+    JS_FreeValue(ctx, atom_val);
+    if (JS_IsException(ret))
+        return JS_EXCEPTION;
+    res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom);
+    if (res < 0)
+        return JS_EXCEPTION;
+    if (res) {
+        if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) {
+            if (!js_same_value(ctx, desc.value, ret)) {
+                goto fail;
+            }
+        } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) {
+            if (JS_IsUndefined(desc.getter) && !JS_IsUndefined(ret)) {
+            fail:
+                js_free_desc(ctx, &desc);
+                JS_FreeValue(ctx, ret);
+                return JS_ThrowTypeError(ctx, "proxy: inconsistent get");
+            }
+        }
+        js_free_desc(ctx, &desc);
+    }
+    return ret;
+}
+
+static int js_proxy_set(JSContext *ctx, JSValueConst obj, JSAtom atom,
+                        JSValueConst value, JSValueConst receiver, int flags)
+{
+    JSProxyData *s;
+    JSValue method, ret1, atom_val;
+    int ret, res;
+    JSValueConst args[4];
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_set);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method)) {
+        return JS_SetPropertyGeneric(ctx, JS_VALUE_GET_OBJ(s->target), atom,
+                                     JS_DupValue(ctx, value), receiver,
+                                     flags);
+    }
+    atom_val = JS_AtomToValue(ctx, atom);
+    if (JS_IsException(atom_val)) {
+        JS_FreeValue(ctx, method);
+        return -1;
+    }
+    args[0] = s->target;
+    args[1] = atom_val;
+    args[2] = value;
+    args[3] = receiver;
+    ret1 = JS_CallFree(ctx, method, s->handler, 4, args);
+    JS_FreeValue(ctx, atom_val);
+    if (JS_IsException(ret1))
+        return -1;
+    ret = JS_ToBoolFree(ctx, ret1);
+    if (ret) {
+        JSPropertyDescriptor desc;
+        res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom);
+        if (res < 0)
+            return -1;
+        if (res) {
+            if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) {
+                if (!js_same_value(ctx, desc.value, value)) {
+                    goto fail;
+                }
+            } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET && JS_IsUndefined(desc.setter)) {
+                fail:
+                    js_free_desc(ctx, &desc);
+                    JS_ThrowTypeError(ctx, "proxy: inconsistent set");
+                    return -1;
+            }
+            js_free_desc(ctx, &desc);
+        }
+    } else {
+        if ((flags & JS_PROP_THROW) ||
+            ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
+            JS_ThrowTypeError(ctx, "proxy: cannot set property");
+            return -1;
+        }
+    }
+    return ret;
+}
+
+static JSValue js_create_desc(JSContext *ctx, JSValueConst val,
+                              JSValueConst getter, JSValueConst setter,
+                              int flags)
+{
+    JSValue ret;
+    ret = JS_NewObject(ctx);
+    if (JS_IsException(ret))
+        return ret;
+    if (flags & JS_PROP_HAS_GET) {
+        JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, JS_DupValue(ctx, getter),
+                               JS_PROP_C_W_E);
+    }
+    if (flags & JS_PROP_HAS_SET) {
+        JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, JS_DupValue(ctx, setter),
+                               JS_PROP_C_W_E);
+    }
+    if (flags & JS_PROP_HAS_VALUE) {
+        JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, JS_DupValue(ctx, val),
+                               JS_PROP_C_W_E);
+    }
+    if (flags & JS_PROP_HAS_WRITABLE) {
+        JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable,
+                               JS_NewBool(ctx, (flags & JS_PROP_WRITABLE) != 0),
+                               JS_PROP_C_W_E);
+    }
+    if (flags & JS_PROP_HAS_ENUMERABLE) {
+        JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable,
+                               JS_NewBool(ctx, (flags & JS_PROP_ENUMERABLE) != 0),
+                               JS_PROP_C_W_E);
+    }
+    if (flags & JS_PROP_HAS_CONFIGURABLE) {
+        JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable,
+                               JS_NewBool(ctx, (flags & JS_PROP_CONFIGURABLE) != 0),
+                               JS_PROP_C_W_E);
+    }
+    return ret;
+}
+
+static int js_proxy_get_own_property(JSContext *ctx, JSPropertyDescriptor *pdesc,
+                                     JSValueConst obj, JSAtom prop)
+{
+    JSProxyData *s;
+    JSValue method, trap_result_obj, prop_val;
+    int res, target_desc_ret, ret;
+    JSObject *p;
+    JSValueConst args[2];
+    JSPropertyDescriptor result_desc, target_desc;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_getOwnPropertyDescriptor);
+    if (!s)
+        return -1;
+    p = JS_VALUE_GET_OBJ(s->target);
+    if (JS_IsUndefined(method)) {
+        return JS_GetOwnPropertyInternal(ctx, pdesc, p, prop);
+    }
+    prop_val = JS_AtomToValue(ctx, prop);
+    if (JS_IsException(prop_val)) {
+        JS_FreeValue(ctx, method);
+        return -1;
+    }
+    args[0] = s->target;
+    args[1] = prop_val;
+    trap_result_obj = JS_CallFree(ctx, method, s->handler, 2, args);
+    JS_FreeValue(ctx, prop_val);
+    if (JS_IsException(trap_result_obj))
+        return -1;
+    if (!JS_IsObject(trap_result_obj) && !JS_IsUndefined(trap_result_obj)) {
+        JS_FreeValue(ctx, trap_result_obj);
+        goto fail;
+    }
+    target_desc_ret = JS_GetOwnPropertyInternal(ctx, &target_desc, p, prop);
+    if (target_desc_ret < 0) {
+        JS_FreeValue(ctx, trap_result_obj);
+        return -1;
+    }
+    if (target_desc_ret)
+        js_free_desc(ctx, &target_desc);
+    if (JS_IsUndefined(trap_result_obj)) {
+        if (target_desc_ret) {
+            if (!(target_desc.flags & JS_PROP_CONFIGURABLE) || !p->extensible)
+                goto fail;
+        }
+        ret = FALSE;
+    } else {
+        int flags1, extensible_target;
+        extensible_target = JS_IsExtensible(ctx, s->target);
+        if (extensible_target < 0) {
+            JS_FreeValue(ctx, trap_result_obj);
+            return -1;
+        }
+        res = js_obj_to_desc(ctx, &result_desc, trap_result_obj);
+        JS_FreeValue(ctx, trap_result_obj);
+        if (res < 0)
+            return -1;
+        
+        if (target_desc_ret) {
+            /* convert result_desc.flags to defineProperty flags */
+            flags1 = result_desc.flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE;
+            if (result_desc.flags & JS_PROP_GETSET)
+                flags1 |= JS_PROP_HAS_GET | JS_PROP_HAS_SET;
+            else
+                flags1 |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE;
+            /* XXX: not complete check: need to compare value &
+               getter/setter as in defineproperty */
+            if (!check_define_prop_flags(target_desc.flags, flags1))
+                goto fail1;
+        } else {
+            if (!extensible_target)
+                goto fail1;
+        }
+        if (!(result_desc.flags & JS_PROP_CONFIGURABLE)) {
+            if (!target_desc_ret || (target_desc.flags & JS_PROP_CONFIGURABLE))
+                goto fail1;
+            if ((result_desc.flags &
+                 (JS_PROP_GETSET | JS_PROP_WRITABLE)) == 0 &&
+                target_desc_ret &&
+                (target_desc.flags & JS_PROP_WRITABLE) != 0) {
+                /* proxy-missing-checks */
+            fail1:
+                js_free_desc(ctx, &result_desc);
+            fail:
+                JS_ThrowTypeError(ctx, "proxy: inconsistent getOwnPropertyDescriptor");
+                return -1;
+            }
+        }
+        ret = TRUE;
+        if (pdesc) {
+            *pdesc = result_desc;
+        } else {
+            js_free_desc(ctx, &result_desc);
+        }
+    }
+    return ret;
+}
+
+static int js_proxy_define_own_property(JSContext *ctx, JSValueConst obj,
+                                        JSAtom prop, JSValueConst val,
+                                        JSValueConst getter, JSValueConst setter,
+                                        int flags)
+{
+    JSProxyData *s;
+    JSValue method, ret1, prop_val, desc_val;
+    int res, ret;
+    JSObject *p;
+    JSValueConst args[3];
+    JSPropertyDescriptor desc;
+    BOOL setting_not_configurable;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_defineProperty);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method)) {
+        return JS_DefineProperty(ctx, s->target, prop, val, getter, setter, flags);
+    }
+    prop_val = JS_AtomToValue(ctx, prop);
+    if (JS_IsException(prop_val)) {
+        JS_FreeValue(ctx, method);
+        return -1;
+    }
+    desc_val = js_create_desc(ctx, val, getter, setter, flags);
+    if (JS_IsException(desc_val)) {
+        JS_FreeValue(ctx, prop_val);
+        JS_FreeValue(ctx, method);
+        return -1;
+    }
+    args[0] = s->target;
+    args[1] = prop_val;
+    args[2] = desc_val;
+    ret1 = JS_CallFree(ctx, method, s->handler, 3, args);
+    JS_FreeValue(ctx, prop_val);
+    JS_FreeValue(ctx, desc_val);
+    if (JS_IsException(ret1))
+        return -1;
+    ret = JS_ToBoolFree(ctx, ret1);
+    if (!ret) {
+        if (flags & JS_PROP_THROW) {
+            JS_ThrowTypeError(ctx, "proxy: defineProperty exception");
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+    p = JS_VALUE_GET_OBJ(s->target);
+    res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
+    if (res < 0)
+        return -1;
+    setting_not_configurable = ((flags & (JS_PROP_HAS_CONFIGURABLE |
+                                          JS_PROP_CONFIGURABLE)) ==
+                                JS_PROP_HAS_CONFIGURABLE);
+    if (!res) {
+        if (!p->extensible || setting_not_configurable)
+            goto fail;
+    } else {
+        if (!check_define_prop_flags(desc.flags, flags) ||
+            ((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable)) {
+            goto fail1;
+        }
+        if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
+            if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) ==
+                JS_PROP_GETSET) {
+                if ((flags & JS_PROP_HAS_GET) &&
+                    !js_same_value(ctx, getter, desc.getter)) {
+                    goto fail1;
+                }
+                if ((flags & JS_PROP_HAS_SET) &&
+                    !js_same_value(ctx, setter, desc.setter)) {
+                    goto fail1;
+                }
+            }
+        } else if (flags & JS_PROP_HAS_VALUE) {
+            if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) ==
+                JS_PROP_WRITABLE && !(flags & JS_PROP_WRITABLE)) {
+                /* missing-proxy-check feature */
+                goto fail1;
+            } else if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 &&
+                !js_same_value(ctx, val, desc.value)) {
+                goto fail1;
+            }
+        }
+        if (flags & JS_PROP_HAS_WRITABLE) {
+            if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE |
+                               JS_PROP_WRITABLE)) == JS_PROP_WRITABLE) {
+                /* proxy-missing-checks */
+            fail1:
+                js_free_desc(ctx, &desc);
+            fail:
+                JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty");
+                return -1;
+            }
+        }
+        js_free_desc(ctx, &desc);
+    }
+    return 1;
+}
+
+static int js_proxy_delete_property(JSContext *ctx, JSValueConst obj,
+                                    JSAtom atom)
+{
+    JSProxyData *s;
+    JSValue method, ret, atom_val;
+    int res, res2, is_extensible;
+    JSValueConst args[2];
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_deleteProperty);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method)) {
+        return JS_DeleteProperty(ctx, s->target, atom, 0);
+    }
+    atom_val = JS_AtomToValue(ctx, atom);;
+    if (JS_IsException(atom_val)) {
+        JS_FreeValue(ctx, method);
+        return -1;
+    }
+    args[0] = s->target;
+    args[1] = atom_val;
+    ret = JS_CallFree(ctx, method, s->handler, 2, args);
+    JS_FreeValue(ctx, atom_val);
+    if (JS_IsException(ret))
+        return -1;
+    res = JS_ToBoolFree(ctx, ret);
+    if (res) {
+        JSPropertyDescriptor desc;
+        res2 = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom);
+        if (res2 < 0)
+            return -1;
+        if (res2) {
+            if (!(desc.flags & JS_PROP_CONFIGURABLE))
+                goto fail;
+            is_extensible = JS_IsExtensible(ctx, s->target);
+            if (is_extensible < 0)
+                goto fail1;
+            if (!is_extensible) {
+                /* proxy-missing-checks */
+            fail:
+                JS_ThrowTypeError(ctx, "proxy: inconsistent deleteProperty");
+            fail1:
+                js_free_desc(ctx, &desc);
+                return -1;
+            }
+            js_free_desc(ctx, &desc);
+        }
+    }
+    return res;
+}
+
+/* return the index of the property or -1 if not found */
+static int find_prop_key(const JSPropertyEnum *tab, int n, JSAtom atom)
+{
+    int i;
+    for(i = 0; i < n; i++) {
+        if (tab[i].atom == atom)
+            return i;
+    }
+    return -1;
+}
+
+static int js_proxy_get_own_property_names(JSContext *ctx,
+                                           JSPropertyEnum **ptab,
+                                           uint32_t *plen,
+                                           JSValueConst obj)
+{
+    JSProxyData *s;
+    JSValue method, prop_array, val;
+    uint32_t len, i, len2;
+    JSPropertyEnum *tab, *tab2;
+    JSAtom atom;
+    JSPropertyDescriptor desc;
+    int res, is_extensible, idx;
+
+    s = get_proxy_method(ctx, &method, obj, JS_ATOM_ownKeys);
+    if (!s)
+        return -1;
+    if (JS_IsUndefined(method)) {
+        return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen,
+                                      JS_VALUE_GET_OBJ(s->target),
+                                      JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK);
+    }
+    prop_array = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target);
+    if (JS_IsException(prop_array))
+        return -1;
+    tab = NULL;
+    len = 0;
+    tab2 = NULL;
+    len2 = 0;
+    if (js_get_length32(ctx, &len, prop_array))
+        goto fail;
+    if (len == 0)
+        goto done;
+    tab = js_mallocz(ctx, sizeof(tab[0]) * len);
+    if (!tab)
+        goto fail;
+    for(i = 0; i < len; i++) {
+        val = JS_GetPropertyUint32(ctx, prop_array, i);
+        if (JS_IsException(val))
+            goto fail;
+        if (!JS_IsString(val) && !JS_IsSymbol(val)) {
+            JS_FreeValue(ctx, val);
+            JS_ThrowTypeError(ctx, "proxy: properties must be strings or symbols");
+            goto fail;
+        }
+        atom = JS_ValueToAtom(ctx, val);
+        JS_FreeValue(ctx, val);
+        if (atom == JS_ATOM_NULL)
+            goto fail;
+        tab[i].atom = atom;
+        tab[i].is_enumerable = FALSE; /* XXX: redundant? */
+    }
+
+    /* check duplicate properties (XXX: inefficient, could store the
+     * properties an a temporary object to use the hash) */
+    for(i = 1; i < len; i++) {
+        if (find_prop_key(tab, i, tab[i].atom) >= 0) {
+            JS_ThrowTypeError(ctx, "proxy: duplicate property");
+            goto fail;
+        }
+    }
+
+    is_extensible = JS_IsExtensible(ctx, s->target);
+    if (is_extensible < 0)
+        goto fail;
+
+    /* check if there are non configurable properties */
+    if (s->is_revoked) {
+        JS_ThrowTypeErrorRevokedProxy(ctx);
+        goto fail;
+    }
+    if (JS_GetOwnPropertyNamesInternal(ctx, &tab2, &len2, JS_VALUE_GET_OBJ(s->target),
+                               JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK))
+        goto fail;
+    for(i = 0; i < len2; i++) {
+        if (s->is_revoked) {
+            JS_ThrowTypeErrorRevokedProxy(ctx);
+            goto fail;
+        }
+        res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target),
+                                tab2[i].atom);
+        if (res < 0)
+            goto fail;
+        if (res) {  /* safety, property should be found */
+            js_free_desc(ctx, &desc);
+            if (!(desc.flags & JS_PROP_CONFIGURABLE) || !is_extensible) {
+                idx = find_prop_key(tab, len, tab2[i].atom);
+                if (idx < 0) {
+                    JS_ThrowTypeError(ctx, "proxy: target property must be present in proxy ownKeys");
+                    goto fail;
+                }
+                /* mark the property as found */
+                if (!is_extensible)
+                    tab[idx].is_enumerable = TRUE;
+            }
+        }
+    }
+    if (!is_extensible) {
+        /* check that all property in 'tab' were checked */
+        for(i = 0; i < len; i++) {
+            if (!tab[i].is_enumerable) {
+                JS_ThrowTypeError(ctx, "proxy: property not present in target were returned by non extensible proxy");
+                goto fail;
+            }
+        }
+    }
+
+ done:
+    js_free_prop_enum(ctx, tab2, len2);
+    JS_FreeValue(ctx, prop_array);
+    *ptab = tab;
+    *plen = len;
+    return 0;
+ fail:
+    js_free_prop_enum(ctx, tab2, len2);
+    js_free_prop_enum(ctx, tab, len);
+    JS_FreeValue(ctx, prop_array);
+    return -1;
+}
+
+static JSValue js_proxy_call(JSContext *ctx, JSValueConst func_obj,
+                             JSValueConst this_obj,
+                             int argc, JSValueConst *argv)
+{
+    JSProxyData *s;
+    JSValue method, arg_array, ret;
+    JSValueConst args[3];
+
+    s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_apply);
+    if (!s)
+        return JS_EXCEPTION;
+    if (!s->is_func) {
+        JS_FreeValue(ctx, method);
+        return JS_ThrowTypeError(ctx, "not a function");
+    }
+    if (JS_IsUndefined(method))
+        return JS_Call(ctx, s->target, this_obj, argc, argv);
+    arg_array = js_create_array(ctx, argc, argv);
+    if (JS_IsException(arg_array)) {
+        ret = JS_EXCEPTION;
+        goto fail;
+    }
+    args[0] = s->target;
+    args[1] = this_obj;
+    args[2] = arg_array;
+    ret = JS_Call(ctx, method, s->handler, 3, args);
+ fail:
+    JS_FreeValue(ctx, method);
+    JS_FreeValue(ctx, arg_array);
+    return ret;
+}
+
+static JSValue js_proxy_call_constructor(JSContext *ctx, JSValueConst func_obj,
+                                         JSValueConst new_target,
+                                         int argc, JSValueConst *argv)
+{
+    JSProxyData *s;
+    JSValue method, arg_array, ret;
+    JSValueConst args[3];
+
+    s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_construct);
+    if (!s)
+        return JS_EXCEPTION;
+    if (!JS_IsConstructor(ctx, s->target))
+        return JS_ThrowTypeError(ctx, "not a constructor");
+    if (JS_IsUndefined(method))
+        return JS_CallConstructor2(ctx, s->target, new_target, argc, argv);
+    arg_array = js_create_array(ctx, argc, argv);
+    if (JS_IsException(arg_array)) {
+        ret = JS_EXCEPTION;
+        goto fail;
+    }
+    args[0] = s->target;
+    args[1] = arg_array;
+    args[2] = new_target;
+    ret = JS_Call(ctx, method, s->handler, 3, args);
+    if (!JS_IsException(ret) && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) {
+        JS_FreeValue(ctx, ret);
+        ret = JS_ThrowTypeErrorNotAnObject(ctx);
+    }
+ fail:
+    JS_FreeValue(ctx, method);
+    JS_FreeValue(ctx, arg_array);
+    return ret;
+}
+
+static int js_proxy_isArray(JSContext *ctx, JSValueConst obj)
+{
+    JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY);
+    if (!s)
+        return FALSE;
+    if (s->is_revoked) {
+        JS_ThrowTypeErrorRevokedProxy(ctx);
+        return -1;
+    }
+    return JS_IsArray(ctx, s->target);
+}
+
+static const JSClassExoticMethods js_proxy_exotic_methods = {
+    .get_own_property = js_proxy_get_own_property,
+    .define_own_property = js_proxy_define_own_property,
+    .delete_property = js_proxy_delete_property,
+    .get_own_property_names = js_proxy_get_own_property_names,
+    .has_property = js_proxy_has,
+    .get_property = js_proxy_get,
+    .set_property = js_proxy_set,
+};
+
+static JSValue js_proxy_constructor(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSValueConst target, handler;
+    JSValue obj;
+    JSProxyData *s;
+
+    target = argv[0];
+    handler = argv[1];
+    if (JS_VALUE_GET_TAG(target) != JS_TAG_OBJECT ||
+        JS_VALUE_GET_TAG(handler) != JS_TAG_OBJECT)
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    s = JS_GetOpaque(target, JS_CLASS_PROXY);
+    if (s && s->is_revoked)
+        goto revoked_proxy;
+    s = JS_GetOpaque(handler, JS_CLASS_PROXY);
+    if (s && s->is_revoked) {
+    revoked_proxy:
+        return JS_ThrowTypeErrorRevokedProxy(ctx);
+    }
+
+    obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_PROXY);
+    if (JS_IsException(obj))
+        return obj;
+    s = js_malloc(ctx, sizeof(JSProxyData));
+    if (!s) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    s->target = JS_DupValue(ctx, target);
+    s->handler = JS_DupValue(ctx, handler);
+    s->proto = JS_NULL;
+    s->is_func = JS_IsFunction(ctx, target);
+    s->is_revoked = FALSE;
+    JS_SetOpaque(obj, s);
+    JS_SetConstructorBit(ctx, obj, JS_IsConstructor(ctx, target));
+    return obj;
+}
+
+static JSValue js_proxy_revoke(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv, int magic,
+                               JSValue *func_data)
+{
+    JSProxyData *s = JS_GetOpaque(func_data[0], JS_CLASS_PROXY);
+    if (s) {
+        /* We do not free the handler and target in case they are
+           referenced as constants in the C call stack */
+        s->is_revoked = TRUE;
+        JS_FreeValue(ctx, func_data[0]);
+        func_data[0] = JS_NULL;
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_proxy_revoke_constructor(JSContext *ctx,
+                                           JSValueConst proxy_obj)
+{
+    return JS_NewCFunctionData(ctx, js_proxy_revoke, 0, 0, 1, &proxy_obj);
+}
+
+static JSValue js_proxy_revocable(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSValue proxy_obj, revoke_obj = JS_UNDEFINED, obj;
+
+    proxy_obj = js_proxy_constructor(ctx, JS_UNDEFINED, argc, argv);
+    if (JS_IsException(proxy_obj))
+        goto fail;
+    revoke_obj = js_proxy_revoke_constructor(ctx, proxy_obj);
+    if (JS_IsException(revoke_obj))
+        goto fail;
+    obj = JS_NewObject(ctx);
+    if (JS_IsException(obj))
+        goto fail;
+    // XXX: exceptions?
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_proxy, proxy_obj, JS_PROP_C_W_E);
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_revoke, revoke_obj, JS_PROP_C_W_E);
+    return obj;
+ fail:
+    JS_FreeValue(ctx, proxy_obj);
+    JS_FreeValue(ctx, revoke_obj);
+    return JS_EXCEPTION;
+}
+
+static const JSCFunctionListEntry js_proxy_funcs[] = {
+    JS_CFUNC_DEF("revocable", 2, js_proxy_revocable ),
+};
+
+static const JSClassShortDef js_proxy_class_def[] = {
+    { JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark }, /* JS_CLASS_PROXY */
+};
+
+void JS_AddIntrinsicProxy(JSContext *ctx)
+{
+    JSRuntime *rt = ctx->rt;
+    JSValue obj1;
+
+    if (!JS_IsRegisteredClass(rt, JS_CLASS_PROXY)) {
+        init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY,
+                         countof(js_proxy_class_def));
+        rt->class_array[JS_CLASS_PROXY].exotic = &js_proxy_exotic_methods;
+        rt->class_array[JS_CLASS_PROXY].call = js_proxy_call;
+    }
+
+    obj1 = JS_NewCFunction2(ctx, js_proxy_constructor, "Proxy", 2,
+                            JS_CFUNC_constructor, 0);
+    JS_SetConstructorBit(ctx, obj1, TRUE);
+    JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs,
+                               countof(js_proxy_funcs));
+    JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy",
+                              obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+}
+
+/* Symbol */
+
+static JSValue js_symbol_constructor(JSContext *ctx, JSValueConst new_target,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue str;
+    JSString *p;
+
+    if (!JS_IsUndefined(new_target))
+        return JS_ThrowTypeError(ctx, "not a constructor");
+    if (argc == 0 || JS_IsUndefined(argv[0])) {
+        p = NULL;
+    } else {
+        str = JS_ToString(ctx, argv[0]);
+        if (JS_IsException(str))
+            return JS_EXCEPTION;
+        p = JS_VALUE_GET_STRING(str);
+    }
+    return JS_NewSymbol(ctx, p, JS_ATOM_TYPE_SYMBOL);
+}
+
+static JSValue js_thisSymbolValue(JSContext *ctx, JSValueConst this_val)
+{
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_SYMBOL)
+        return JS_DupValue(ctx, this_val);
+
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_SYMBOL) {
+            if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_SYMBOL)
+                return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a symbol");
+}
+
+static JSValue js_symbol_toString(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValue val, ret;
+    val = js_thisSymbolValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    /* XXX: use JS_ToStringInternal() with a flags */
+    ret = js_string_constructor(ctx, JS_UNDEFINED, 1, (JSValueConst *)&val);
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static JSValue js_symbol_valueOf(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    return js_thisSymbolValue(ctx, this_val);
+}
+
+static JSValue js_symbol_get_description(JSContext *ctx, JSValueConst this_val)
+{
+    JSValue val, ret;
+    JSAtomStruct *p;
+
+    val = js_thisSymbolValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    p = JS_VALUE_GET_PTR(val);
+    if (p->len == 0 && p->is_wide_char != 0) {
+        ret = JS_UNDEFINED;
+    } else {
+        ret = JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p));
+    }
+    JS_FreeValue(ctx, val);
+    return ret;
+}
+
+static const JSCFunctionListEntry js_symbol_proto_funcs[] = {
+    JS_CFUNC_DEF("toString", 0, js_symbol_toString ),
+    JS_CFUNC_DEF("valueOf", 0, js_symbol_valueOf ),
+    // XXX: should have writable: false
+    JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_symbol_valueOf ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Symbol", JS_PROP_CONFIGURABLE ),
+    JS_CGETSET_DEF("description", js_symbol_get_description, NULL ),
+};
+
+static JSValue js_symbol_for(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    JSValue str;
+
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        return JS_EXCEPTION;
+    return JS_NewSymbol(ctx, JS_VALUE_GET_STRING(str), JS_ATOM_TYPE_GLOBAL_SYMBOL);
+}
+
+static JSValue js_symbol_keyFor(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSAtomStruct *p;
+
+    if (!JS_IsSymbol(argv[0]))
+        return JS_ThrowTypeError(ctx, "not a symbol");
+    p = JS_VALUE_GET_PTR(argv[0]);
+    if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL)
+        return JS_UNDEFINED;
+    return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
+}
+
+static const JSCFunctionListEntry js_symbol_funcs[] = {
+    JS_CFUNC_DEF("for", 1, js_symbol_for ),
+    JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ),
+};
+
+/* Set/Map/WeakSet/WeakMap */
+
+typedef struct JSMapRecord {
+    int ref_count; /* used during enumeration to avoid freeing the record */
+    BOOL empty; /* TRUE if the record is deleted */
+    struct JSMapState *map;
+    struct JSMapRecord *next_weak_ref;
+    struct list_head link;
+    struct list_head hash_link;
+    JSValue key;
+    JSValue value;
+} JSMapRecord;
+
+typedef struct JSMapState {
+    BOOL is_weak; /* TRUE if WeakSet/WeakMap */
+    struct list_head records; /* list of JSMapRecord.link */
+    uint32_t record_count;
+    struct list_head *hash_table;
+    uint32_t hash_size; /* must be a power of two */
+    uint32_t record_count_threshold; /* count at which a hash table
+                                        resize is needed */
+} JSMapState;
+
+#define MAGIC_SET (1 << 0)
+#define MAGIC_WEAK (1 << 1)
+
+static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target,
+                                  int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s;
+    JSValue obj, adder = JS_UNDEFINED, iter = JS_UNDEFINED, next_method = JS_UNDEFINED;
+    JSValueConst arr;
+    BOOL is_set, is_weak;
+
+    is_set = magic & MAGIC_SET;
+    is_weak = ((magic & MAGIC_WEAK) != 0);
+    obj = js_create_from_ctor(ctx, new_target, JS_CLASS_MAP + magic);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s)
+        goto fail;
+    init_list_head(&s->records);
+    s->is_weak = is_weak;
+    JS_SetOpaque(obj, s);
+    s->hash_size = 1;
+    s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size);
+    if (!s->hash_table)
+        goto fail;
+    init_list_head(&s->hash_table[0]);
+    s->record_count_threshold = 4;
+
+    arr = JS_UNDEFINED;
+    if (argc > 0)
+        arr = argv[0];
+    if (!JS_IsUndefined(arr) && !JS_IsNull(arr)) {
+        JSValue item, ret;
+        BOOL done;
+
+        adder = JS_GetProperty(ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set);
+        if (JS_IsException(adder))
+            goto fail;
+        if (!JS_IsFunction(ctx, adder)) {
+            JS_ThrowTypeError(ctx, "set/add is not a function");
+            goto fail;
+        }
+
+        iter = JS_GetIterator(ctx, arr, FALSE);
+        if (JS_IsException(iter))
+            goto fail;
+        next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
+        if (JS_IsException(next_method))
+            goto fail;
+
+        for(;;) {
+            item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
+            if (JS_IsException(item))
+                goto fail;
+            if (done) {
+                JS_FreeValue(ctx, item);
+                break;
+            }
+            if (is_set) {
+                ret = JS_Call(ctx, adder, obj, 1, (JSValueConst *)&item);
+                if (JS_IsException(ret)) {
+                    JS_FreeValue(ctx, item);
+                    goto fail;
+                }
+            } else {
+                JSValue key, value;
+                JSValueConst args[2];
+                key = JS_UNDEFINED;
+                value = JS_UNDEFINED;
+                if (!JS_IsObject(item)) {
+                    JS_ThrowTypeErrorNotAnObject(ctx);
+                    goto fail1;
+                }
+                key = JS_GetPropertyUint32(ctx, item, 0);
+                if (JS_IsException(key))
+                    goto fail1;
+                value = JS_GetPropertyUint32(ctx, item, 1);
+                if (JS_IsException(value))
+                    goto fail1;
+                args[0] = key;
+                args[1] = value;
+                ret = JS_Call(ctx, adder, obj, 2, args);
+                if (JS_IsException(ret)) {
+                fail1:
+                    JS_FreeValue(ctx, item);
+                    JS_FreeValue(ctx, key);
+                    JS_FreeValue(ctx, value);
+                    goto fail;
+                }
+                JS_FreeValue(ctx, key);
+                JS_FreeValue(ctx, value);
+            }
+            JS_FreeValue(ctx, ret);
+            JS_FreeValue(ctx, item);
+        }
+        JS_FreeValue(ctx, next_method);
+        JS_FreeValue(ctx, iter);
+        JS_FreeValue(ctx, adder);
+    }
+    return obj;
+ fail:
+    if (JS_IsObject(iter)) {
+        /* close the iterator object, preserving pending exception */
+        JS_IteratorClose(ctx, iter, TRUE);
+    }
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    JS_FreeValue(ctx, adder);
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+/* XXX: could normalize strings to speed up comparison */
+static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key)
+{
+    uint32_t tag = JS_VALUE_GET_TAG(key);
+    /* convert -0.0 to +0.0 */
+    if (JS_TAG_IS_FLOAT64(tag) && JS_VALUE_GET_FLOAT64(key) == 0.0) {
+        key = JS_NewInt32(ctx, 0);
+    }
+    return key;
+}
+
+/* XXX: better hash ? */
+static uint32_t map_hash_key(JSContext *ctx, JSValueConst key)
+{
+    uint32_t tag = JS_VALUE_GET_NORM_TAG(key);
+    uint32_t h;
+    double d;
+    JSFloat64Union u;
+
+    switch(tag) {
+    case JS_TAG_BOOL:
+        h = JS_VALUE_GET_INT(key);
+        break;
+    case JS_TAG_STRING:
+        h = hash_string(JS_VALUE_GET_STRING(key), 0);
+        break;
+    case JS_TAG_OBJECT:
+    case JS_TAG_SYMBOL:
+        h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163;
+        break;
+    case JS_TAG_INT:
+        d = JS_VALUE_GET_INT(key) * 3163;
+        goto hash_float64;
+    case JS_TAG_FLOAT64:
+        d = JS_VALUE_GET_FLOAT64(key);
+        /* normalize the NaN */
+        if (isnan(d))
+            d = JS_FLOAT64_NAN;
+    hash_float64:
+        u.d = d;
+        h = (u.u32[0] ^ u.u32[1]) * 3163;
+        break;
+    default:
+        h = 0; /* XXX: bignum support */
+        break;
+    }
+    h ^= tag;
+    return h;
+}
+
+static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s,
+                                    JSValueConst key)
+{
+    struct list_head *el;
+    JSMapRecord *mr;
+    uint32_t h;
+    h = map_hash_key(ctx, key) & (s->hash_size - 1);
+    list_for_each(el, &s->hash_table[h]) {
+        mr = list_entry(el, JSMapRecord, hash_link);
+        if (js_same_value_zero(ctx, mr->key, key))
+            return mr;
+    }
+    return NULL;
+}
+
+static void map_hash_resize(JSContext *ctx, JSMapState *s)
+{
+    uint32_t new_hash_size, i, h;
+    size_t slack;
+    struct list_head *new_hash_table, *el;
+    JSMapRecord *mr;
+
+    /* XXX: no reporting of memory allocation failure */
+    if (s->hash_size == 1)
+        new_hash_size = 4;
+    else
+        new_hash_size = s->hash_size * 2;
+    new_hash_table = js_realloc2(ctx, s->hash_table,
+                                 sizeof(new_hash_table[0]) * new_hash_size, &slack);
+    if (!new_hash_table)
+        return;
+    new_hash_size += slack / sizeof(*new_hash_table);
+
+    for(i = 0; i < new_hash_size; i++)
+        init_list_head(&new_hash_table[i]);
+
+    list_for_each(el, &s->records) {
+        mr = list_entry(el, JSMapRecord, link);
+        if (!mr->empty) {
+            h = map_hash_key(ctx, mr->key) & (new_hash_size - 1);
+            list_add_tail(&mr->hash_link, &new_hash_table[h]);
+        }
+    }
+    s->hash_table = new_hash_table;
+    s->hash_size = new_hash_size;
+    s->record_count_threshold = new_hash_size * 2;
+}
+
+static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
+                                   JSValueConst key)
+{
+    uint32_t h;
+    JSMapRecord *mr;
+
+    mr = js_malloc(ctx, sizeof(*mr));
+    if (!mr)
+        return NULL;
+    mr->ref_count = 1;
+    mr->map = s;
+    mr->empty = FALSE;
+    if (s->is_weak) {
+        JSObject *p = JS_VALUE_GET_OBJ(key);
+        /* Add the weak reference */
+        mr->next_weak_ref = p->first_weak_ref;
+        p->first_weak_ref = mr;
+    } else {
+        JS_DupValue(ctx, key);
+    }
+    mr->key = (JSValue)key;
+    h = map_hash_key(ctx, key) & (s->hash_size - 1);
+    list_add_tail(&mr->hash_link, &s->hash_table[h]);
+    list_add_tail(&mr->link, &s->records);
+    s->record_count++;
+    if (s->record_count >= s->record_count_threshold) {
+        map_hash_resize(ctx, s);
+    }
+    return mr;
+}
+
+/* Remove the weak reference from the object weak
+   reference list. we don't use a doubly linked list to
+   save space, assuming a given object has few weak
+       references to it */
+static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr)
+{
+    JSMapRecord **pmr, *mr1;
+    JSObject *p;
+
+    p = JS_VALUE_GET_OBJ(mr->key);
+    pmr = &p->first_weak_ref;
+    for(;;) {
+        mr1 = *pmr;
+        assert(mr1 != NULL);
+        if (mr1 == mr)
+            break;
+        pmr = &mr1->next_weak_ref;
+    }
+    *pmr = mr1->next_weak_ref;
+}
+
+static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
+{
+    if (mr->empty)
+        return;
+    list_del(&mr->hash_link);
+    if (s->is_weak) {
+        delete_weak_ref(rt, mr);
+    } else {
+        JS_FreeValueRT(rt, mr->key);
+    }
+    JS_FreeValueRT(rt, mr->value);
+    if (--mr->ref_count == 0) {
+        list_del(&mr->link);
+        js_free_rt(rt, mr);
+    } else {
+        /* keep a zombie record for iterators */
+        mr->empty = TRUE;
+        mr->key = JS_UNDEFINED;
+        mr->value = JS_UNDEFINED;
+    }
+    s->record_count--;
+}
+
+static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
+{
+    if (--mr->ref_count == 0) {
+        /* the record can be safely removed */
+        assert(mr->empty);
+        list_del(&mr->link);
+        js_free_rt(rt, mr);
+    }
+}
+
+static void reset_weak_ref(JSRuntime *rt, JSObject *p)
+{
+    JSMapRecord *mr, *mr_next;
+    JSMapState *s;
+    
+    /* first pass to remove the records from the WeakMap/WeakSet
+       lists */
+    for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
+        s = mr->map;
+        assert(s->is_weak);
+        assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
+        list_del(&mr->hash_link);
+        list_del(&mr->link);
+    }
+    
+    /* second pass to free the values to avoid modifying the weak
+       reference list while traversing it. */
+    for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) {
+        mr_next = mr->next_weak_ref;
+        JS_FreeValueRT(rt, mr->value);
+        js_free_rt(rt, mr);
+    }
+
+    p->first_weak_ref = NULL; /* fail safe */
+}
+
+static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    JSMapRecord *mr;
+    JSValueConst key, value;
+
+    if (!s)
+        return JS_EXCEPTION;
+    key = map_normalize_key(ctx, argv[0]);
+    if (s->is_weak && !JS_IsObject(key))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    if (magic & MAGIC_SET)
+        value = JS_UNDEFINED;
+    else
+        value = argv[1];
+    mr = map_find_record(ctx, s, key);
+    if (mr) {
+        JS_FreeValue(ctx, mr->value);
+    } else {
+        mr = map_add_record(ctx, s, key);
+        if (!mr)
+            return JS_EXCEPTION;
+    }
+    mr->value = JS_DupValue(ctx, value);
+    return JS_DupValue(ctx, this_val);
+}
+
+static JSValue js_map_get(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    JSMapRecord *mr;
+    JSValueConst key;
+
+    if (!s)
+        return JS_EXCEPTION;
+    key = map_normalize_key(ctx, argv[0]);
+    mr = map_find_record(ctx, s, key);
+    if (!mr)
+        return JS_UNDEFINED;
+    else
+        return JS_DupValue(ctx, mr->value);
+}
+
+static JSValue js_map_has(JSContext *ctx, JSValueConst this_val,
+                          int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    JSMapRecord *mr;
+    JSValueConst key;
+
+    if (!s)
+        return JS_EXCEPTION;
+    key = map_normalize_key(ctx, argv[0]);
+    mr = map_find_record(ctx, s, key);
+    return JS_NewBool(ctx, (mr != NULL));
+}
+
+static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    JSMapRecord *mr;
+    JSValueConst key;
+
+    if (!s)
+        return JS_EXCEPTION;
+    key = map_normalize_key(ctx, argv[0]);
+    mr = map_find_record(ctx, s, key);
+    if (!mr)
+        return JS_FALSE;
+    map_delete_record(ctx->rt, s, mr);
+    return JS_TRUE;
+}
+
+static JSValue js_map_clear(JSContext *ctx, JSValueConst this_val,
+                            int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    struct list_head *el, *el1;
+    JSMapRecord *mr;
+
+    if (!s)
+        return JS_EXCEPTION;
+    list_for_each_safe(el, el1, &s->records) {
+        mr = list_entry(el, JSMapRecord, link);
+        map_delete_record(ctx->rt, s, mr);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_map_get_size(JSContext *ctx, JSValueConst this_val, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    if (!s)
+        return JS_EXCEPTION;
+    return JS_NewUint32(ctx, s->record_count);
+}
+
+static JSValue js_map_forEach(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int magic)
+{
+    JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    JSValueConst func, this_arg;
+    JSValue ret, args[3];
+    struct list_head *el;
+    JSMapRecord *mr;
+
+    if (!s)
+        return JS_EXCEPTION;
+    func = argv[0];
+    if (argc > 1)
+        this_arg = argv[1];
+    else
+        this_arg = JS_UNDEFINED;
+    if (check_function(ctx, func))
+        return JS_EXCEPTION;
+    /* Note: the list can be modified while traversing it, but the
+       current element is locked */
+    el = s->records.next;
+    while (el != &s->records) {
+        mr = list_entry(el, JSMapRecord, link);
+        if (!mr->empty) {
+            mr->ref_count++;
+            /* must duplicate in case the record is deleted */
+            args[1] = JS_DupValue(ctx, mr->key);
+            if (magic)
+                args[0] = args[1];
+            else
+                args[0] = JS_DupValue(ctx, mr->value);
+            args[2] = (JSValue)this_val;
+            ret = JS_Call(ctx, func, this_arg, 3, (JSValueConst *)args);
+            JS_FreeValue(ctx, args[0]);
+            if (!magic)
+                JS_FreeValue(ctx, args[1]);
+            el = el->next;
+            map_decref_record(ctx->rt, mr);
+            if (JS_IsException(ret))
+                return ret;
+            JS_FreeValue(ctx, ret);
+        } else {
+            el = el->next;
+        }
+    }
+    return JS_UNDEFINED;
+}
+
+static void js_map_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p;
+    JSMapState *s;
+    struct list_head *el, *el1;
+    JSMapRecord *mr;
+
+    p = JS_VALUE_GET_OBJ(val);
+    s = p->u.map_state;
+    if (s) {
+        /* if the object is deleted we are sure that no iterator is
+           using it */
+        list_for_each_safe(el, el1, &s->records) {
+            mr = list_entry(el, JSMapRecord, link);
+            if (!mr->empty) {
+                if (s->is_weak)
+                    delete_weak_ref(rt, mr);
+                else
+                    JS_FreeValueRT(rt, mr->key);
+                JS_FreeValueRT(rt, mr->value);
+            }
+            js_free_rt(rt, mr);
+        }
+        js_free_rt(rt, s->hash_table);
+        js_free_rt(rt, s);
+    }
+}
+
+static void js_map_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSMapState *s;
+    struct list_head *el;
+    JSMapRecord *mr;
+
+    s = p->u.map_state;
+    if (s) {
+        list_for_each(el, &s->records) {
+            mr = list_entry(el, JSMapRecord, link);
+            if (!s->is_weak)
+                JS_MarkValue(rt, mr->key, mark_func);
+            JS_MarkValue(rt, mr->value, mark_func);
+        }
+    }
+}
+
+/* Map Iterator */
+
+typedef struct JSMapIteratorData {
+    JSValue obj;
+    JSIteratorKindEnum kind;
+    JSMapRecord *cur_record;
+} JSMapIteratorData;
+
+static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p;
+    JSMapIteratorData *it;
+
+    p = JS_VALUE_GET_OBJ(val);
+    it = p->u.map_iterator_data;
+    if (it) {
+        /* During the GC sweep phase the Map finalizer may be
+           called before the Map iterator finalizer */
+        if (JS_IsLiveObject(rt, it->obj) && it->cur_record) {
+            map_decref_record(rt, it->cur_record);
+        }
+        JS_FreeValueRT(rt, it->obj);
+        js_free_rt(rt, it);
+    }
+}
+
+static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                 JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSMapIteratorData *it;
+    it = p->u.map_iterator_data;
+    if (it) {
+        /* the record is already marked by the object */
+        JS_MarkValue(rt, it->obj, mark_func);
+    }
+}
+
+static JSValue js_create_map_iterator(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv, int magic)
+{
+    JSIteratorKindEnum kind;
+    JSMapState *s;
+    JSMapIteratorData *it;
+    JSValue enum_obj;
+
+    kind = magic >> 2;
+    magic &= 3;
+    s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
+    if (!s)
+        return JS_EXCEPTION;
+    enum_obj = JS_NewObjectClass(ctx, JS_CLASS_MAP_ITERATOR + magic);
+    if (JS_IsException(enum_obj))
+        goto fail;
+    it = js_malloc(ctx, sizeof(*it));
+    if (!it) {
+        JS_FreeValue(ctx, enum_obj);
+        goto fail;
+    }
+    it->obj = JS_DupValue(ctx, this_val);
+    it->kind = kind;
+    it->cur_record = NULL;
+    JS_SetOpaque(enum_obj, it);
+    return enum_obj;
+ fail:
+    return JS_EXCEPTION;
+}
+
+static JSValue js_map_iterator_next(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv,
+                                    BOOL *pdone, int magic)
+{
+    JSMapIteratorData *it;
+    JSMapState *s;
+    JSMapRecord *mr;
+    struct list_head *el;
+
+    it = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic);
+    if (!it) {
+        *pdone = FALSE;
+        return JS_EXCEPTION;
+    }
+    if (JS_IsUndefined(it->obj))
+        goto done;
+    s = JS_GetOpaque(it->obj, JS_CLASS_MAP + magic);
+    assert(s != NULL);
+    if (!it->cur_record) {
+        el = s->records.next;
+    } else {
+        mr = it->cur_record;
+        el = mr->link.next;
+        map_decref_record(ctx->rt, mr); /* the record can be freed here */
+    }
+    for(;;) {
+        if (el == &s->records) {
+            /* no more record  */
+            it->cur_record = NULL;
+            JS_FreeValue(ctx, it->obj);
+            it->obj = JS_UNDEFINED;
+        done:
+            /* end of enumeration */
+            *pdone = TRUE;
+            return JS_UNDEFINED;
+        }
+        mr = list_entry(el, JSMapRecord, link);
+        if (!mr->empty)
+            break;
+        /* get the next record */
+        el = mr->link.next;
+    }
+
+    /* lock the record so that it won't be freed */
+    mr->ref_count++;
+    it->cur_record = mr;
+    *pdone = FALSE;
+
+    if (it->kind == JS_ITERATOR_KIND_KEY) {
+        return JS_DupValue(ctx, mr->key);
+    } else {
+        JSValueConst args[2];
+        args[0] = mr->key;
+        if (magic)
+            args[1] = mr->key;
+        else
+            args[1] = mr->value;
+        if (it->kind == JS_ITERATOR_KIND_VALUE) {
+            return JS_DupValue(ctx, args[1]);
+        } else {
+            return js_create_array(ctx, 2, args);
+        }
+    }
+}
+
+static const JSCFunctionListEntry js_map_funcs[] = {
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
+};
+
+static const JSCFunctionListEntry js_map_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ),
+    JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ),
+    JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ),
+    JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ),
+    JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ),
+    JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, 0),
+    JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0 ),
+    JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_VALUE << 2) | 0 ),
+    JS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | 0 ),
+    JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0 ),
+    JS_ALIAS_DEF("[Symbol.iterator]", "entries" ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_map_iterator_proto_funcs[] = {
+    JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0 ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_set_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET ),
+    JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET ),
+    JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET ),
+    JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ),
+    JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ),
+    JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ),
+    JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ),
+    JS_ALIAS_DEF("keys", "values" ),
+    JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
+    JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_set_iterator_proto_funcs[] = {
+    JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_weak_map_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ),
+    JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ),
+    JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ),
+    JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_weak_set_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK ),
+    JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK ),
+    JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry * const js_map_proto_funcs_ptr[6] = {
+    js_map_proto_funcs,
+    js_set_proto_funcs,
+    js_weak_map_proto_funcs,
+    js_weak_set_proto_funcs,
+    js_map_iterator_proto_funcs,
+    js_set_iterator_proto_funcs,
+};
+
+static const uint8_t js_map_proto_funcs_count[6] = {
+    countof(js_map_proto_funcs),
+    countof(js_set_proto_funcs),
+    countof(js_weak_map_proto_funcs),
+    countof(js_weak_set_proto_funcs),
+    countof(js_map_iterator_proto_funcs),
+    countof(js_set_iterator_proto_funcs),
+};
+
+void JS_AddIntrinsicMapSet(JSContext *ctx)
+{
+    int i;
+    JSValue obj1;
+    char buf[ATOM_GET_STR_BUF_SIZE];
+
+    for(i = 0; i < 4; i++) {
+        const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf),
+                                         JS_ATOM_Map + i);
+        ctx->class_proto[JS_CLASS_MAP + i] = JS_NewObject(ctx);
+        JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP + i],
+                                   js_map_proto_funcs_ptr[i],
+                                   js_map_proto_funcs_count[i]);
+        obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0,
+                                    JS_CFUNC_constructor_magic, i);
+        if (i < 2) {
+            JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs,
+                                       countof(js_map_funcs));
+        }
+        JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_proto[JS_CLASS_MAP + i]);
+    }
+
+    for(i = 0; i < 2; i++) {
+        ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] =
+            JS_NewObjectProto(ctx, ctx->iterator_proto);
+        JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP_ITERATOR + i],
+                                   js_map_proto_funcs_ptr[i + 4],
+                                   js_map_proto_funcs_count[i + 4]);
+    }
+}
+
+/* Generator */
+static const JSCFunctionListEntry js_generator_function_proto_funcs[] = {
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction", JS_PROP_CONFIGURABLE),
+};
+
+static const JSCFunctionListEntry js_generator_proto_funcs[] = {
+    JS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT ),
+    JS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN ),
+    JS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator", JS_PROP_CONFIGURABLE),
+};
+
+/* Promise */
+
+typedef enum JSPromiseStateEnum {
+    JS_PROMISE_PENDING,
+    JS_PROMISE_FULFILLED,
+    JS_PROMISE_REJECTED,
+} JSPromiseStateEnum;
+
+typedef struct JSPromiseData {
+    JSPromiseStateEnum promise_state;
+    /* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */
+    struct list_head promise_reactions[2];
+    BOOL is_handled; /* Note: only useful to debug */
+    JSValue promise_result;
+} JSPromiseData;
+
+typedef struct JSPromiseFunctionDataResolved {
+    int ref_count;
+    BOOL already_resolved;
+} JSPromiseFunctionDataResolved;
+
+typedef struct JSPromiseFunctionData {
+    JSValue promise;
+    JSPromiseFunctionDataResolved *presolved;
+} JSPromiseFunctionData;
+
+typedef struct JSPromiseReactionData {
+    struct list_head link; /* not used in promise_reaction_job */
+    JSValue resolving_funcs[2];
+    JSValue handler;
+} JSPromiseReactionData;
+
+static int js_create_resolving_functions(JSContext *ctx, JSValue *args,
+                                         JSValueConst promise);
+
+static void promise_reaction_data_free(JSRuntime *rt,
+                                       JSPromiseReactionData *rd)
+{
+    JS_FreeValueRT(rt, rd->resolving_funcs[0]);
+    JS_FreeValueRT(rt, rd->resolving_funcs[1]);
+    JS_FreeValueRT(rt, rd->handler);
+    js_free_rt(rt, rd);
+}
+
+static JSValue promise_reaction_job(JSContext *ctx, int argc,
+                                    JSValueConst *argv)
+{
+    JSValueConst handler, arg, func;
+    JSValue res, res2;
+    BOOL is_reject;
+
+    assert(argc == 5);
+    handler = argv[2];
+    is_reject = JS_ToBool(ctx, argv[3]);
+    arg = argv[4];
+#ifdef DUMP_PROMISE
+    printf("promise_reaction_job: is_reject=%d\n", is_reject);
+#endif
+
+    if (JS_IsUndefined(handler)) {
+        if (is_reject) {
+            res = JS_Throw(ctx, JS_DupValue(ctx, arg));
+        } else {
+            res = JS_DupValue(ctx, arg);
+        }
+    } else {
+        res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg);
+    }
+    is_reject = JS_IsException(res);
+    if (is_reject)
+        res = JS_GetException(ctx);
+    func = argv[is_reject];
+    /* as an extension, we support undefined as value to avoid
+       creating a dummy promise in the 'await' implementation of async
+       functions */
+    if (!JS_IsUndefined(func)) {
+        res2 = JS_Call(ctx, func, JS_UNDEFINED,
+                       1, (JSValueConst *)&res);
+    } else {
+        res2 = JS_UNDEFINED;
+    }
+    JS_FreeValue(ctx, res);
+
+    return res2;
+}
+
+static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise,
+                                      JSValueConst value, BOOL is_reject)
+{
+    JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
+    struct list_head *el, *el1;
+    JSPromiseReactionData *rd;
+    JSValueConst args[5];
+
+    if (!s || s->promise_state != JS_PROMISE_PENDING)
+        return; /* should never happen */
+    set_value(ctx, &s->promise_result, JS_DupValue(ctx, value));
+    s->promise_state = JS_PROMISE_FULFILLED + is_reject;
+#ifdef DUMP_PROMISE
+    printf("fulfill_or_reject_promise: is_reject=%d\n", is_reject);
+#endif
+    /* Note: could call HostPromiseRejectTracker */
+    list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) {
+        rd = list_entry(el, JSPromiseReactionData, link);
+        args[0] = rd->resolving_funcs[0];
+        args[1] = rd->resolving_funcs[1];
+        args[2] = rd->handler;
+        args[3] = JS_NewBool(ctx, is_reject);
+        args[4] = value;
+        JS_EnqueueJob(ctx, promise_reaction_job, 5, args);
+        list_del(&rd->link);
+        promise_reaction_data_free(ctx->rt, rd);
+    }
+
+    list_for_each_safe(el, el1, &s->promise_reactions[1 - is_reject]) {
+        rd = list_entry(el, JSPromiseReactionData, link);
+        list_del(&rd->link);
+        promise_reaction_data_free(ctx->rt, rd);
+    }
+}
+
+static void reject_promise(JSContext *ctx, JSValueConst promise,
+                           JSValueConst value)
+{
+    fulfill_or_reject_promise(ctx, promise, value, TRUE);
+}
+
+static JSValue js_promise_resolve_thenable_job(JSContext *ctx,
+                                               int argc, JSValueConst *argv)
+{
+    JSValueConst promise, thenable, then;
+    JSValue args[2], res;
+
+#ifdef DUMP_PROMISE
+    printf("js_promise_resolve_thenable_job\n");
+#endif
+    assert(argc == 3);
+    promise = argv[0];
+    thenable = argv[1];
+    then = argv[2];
+    if (js_create_resolving_functions(ctx, args, promise) < 0)
+        return JS_EXCEPTION;
+    res = JS_Call(ctx, then, thenable, 2, (JSValueConst *)args);
+    if (JS_IsException(res)) {
+        JSValue error = JS_GetException(ctx);
+        res = JS_Call(ctx, args[1], JS_UNDEFINED, 1, (JSValueConst *)&error);
+        JS_FreeValue(ctx, error);
+    }
+    JS_FreeValue(ctx, args[0]);
+    JS_FreeValue(ctx, args[1]);
+    return res;
+}
+
+static void js_promise_resolve_function_free_resolved(JSRuntime *rt,
+                                                      JSPromiseFunctionDataResolved *sr)
+{
+    if (--sr->ref_count == 0) {
+        js_free_rt(rt, sr);
+    }
+}
+
+static int js_create_resolving_functions(JSContext *ctx,
+                                         JSValue *resolving_funcs,
+                                         JSValueConst promise)
+
+{
+    JSValue obj;
+    JSPromiseFunctionData *s;
+    JSPromiseFunctionDataResolved *sr;
+    int i, ret;
+
+    sr = js_malloc(ctx, sizeof(*sr));
+    if (!sr)
+        return -1;
+    sr->ref_count = 1;
+    sr->already_resolved = FALSE; /* must be shared between the two functions */
+    ret = 0;
+    for(i = 0; i < 2; i++) {
+        obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
+                                     JS_CLASS_PROMISE_RESOLVE_FUNCTION + i);
+        if (JS_IsException(obj))
+            goto fail;
+        s = js_malloc(ctx, sizeof(*s));
+        if (!s) {
+            JS_FreeValue(ctx, obj);
+        fail:
+
+            if (i != 0)
+                JS_FreeValue(ctx, resolving_funcs[0]);
+            ret = -1;
+            break;
+        }
+        sr->ref_count++;
+        s->presolved = sr;
+        s->promise = JS_DupValue(ctx, promise);
+        JS_SetOpaque(obj, s);
+        js_function_set_properties(ctx, obj, JS_ATOM_empty_string, 1);
+        resolving_funcs[i] = obj;
+    }
+    js_promise_resolve_function_free_resolved(ctx->rt, sr);
+    return ret;
+}
+
+static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data;
+    if (s) {
+        js_promise_resolve_function_free_resolved(rt, s->presolved);
+        JS_FreeValueRT(rt, s->promise);
+        js_free_rt(rt, s);
+    }
+}
+
+static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val,
+                                             JS_MarkFunc *mark_func)
+{
+    JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data;
+    if (s) {
+        JS_MarkValue(rt, s->promise, mark_func);
+    }
+}
+
+static JSValue js_promise_resolve_function_call(JSContext *ctx,
+                                                JSValueConst func_obj,
+                                                JSValueConst this_val,
+                                                int argc, JSValueConst *argv)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(func_obj);
+    JSPromiseFunctionData *s;
+    JSValueConst resolution, args[3];
+    JSValue then;
+    BOOL is_reject;
+
+    s = p->u.promise_function_data;
+    if (!s || s->presolved->already_resolved)
+        return JS_UNDEFINED;
+    s->presolved->already_resolved = TRUE;
+    is_reject = p->class_id - JS_CLASS_PROMISE_RESOLVE_FUNCTION;
+    if (argc > 0)
+        resolution = argv[0];
+    else
+        resolution = JS_UNDEFINED;
+#ifdef DUMP_PROMISE
+    printf("js_promise_resolving_function_call: is_reject=%d resolution=", is_reject);
+    JS_DumpValue(ctx, resolution);
+    printf("\n");
+#endif
+    if (is_reject || !JS_IsObject(resolution)) {
+        goto done;
+    } else if (js_same_value(ctx, resolution, s->promise)) {
+        JS_ThrowTypeError(ctx, "promise self resolution");
+        goto fail_reject;
+    }
+    then = JS_GetProperty(ctx, resolution, JS_ATOM_then);
+    if (JS_IsException(then)) {
+        JSValue error;
+    fail_reject:
+        error = JS_GetException(ctx);
+        reject_promise(ctx, s->promise, error);
+        JS_FreeValue(ctx, error);
+    } else if (!JS_IsFunction(ctx, then)) {
+        JS_FreeValue(ctx, then);
+    done:
+        fulfill_or_reject_promise(ctx, s->promise, resolution, is_reject);
+    } else {
+        args[0] = s->promise;
+        args[1] = resolution;
+        args[2] = then;
+        JS_EnqueueJob(ctx, js_promise_resolve_thenable_job, 3, args);
+        JS_FreeValue(ctx, then);
+    }
+    return JS_UNDEFINED;
+}
+
+static void js_promise_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE);
+    struct list_head *el, *el1;
+    int i;
+
+    if (!s)
+        return;
+    for(i = 0; i < 2; i++) {
+        list_for_each_safe(el, el1, &s->promise_reactions[i]) {
+            JSPromiseReactionData *rd =
+                list_entry(el, JSPromiseReactionData, link);
+            promise_reaction_data_free(rt, rd);
+        }
+    }
+    JS_FreeValueRT(rt, s->promise_result);
+    js_free_rt(rt, s);
+}
+
+static void js_promise_mark(JSRuntime *rt, JSValueConst val,
+                            JS_MarkFunc *mark_func)
+{
+    JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE);
+    struct list_head *el;
+    int i;
+
+    if (!s)
+        return;
+    for(i = 0; i < 2; i++) {
+        list_for_each(el, &s->promise_reactions[i]) {
+            JSPromiseReactionData *rd =
+                list_entry(el, JSPromiseReactionData, link);
+            JS_MarkValue(rt, rd->resolving_funcs[0], mark_func);
+            JS_MarkValue(rt, rd->resolving_funcs[1], mark_func);
+            JS_MarkValue(rt, rd->handler, mark_func);
+        }
+    }
+    JS_MarkValue(rt, s->promise_result, mark_func);
+}
+
+static JSValue js_promise_constructor(JSContext *ctx, JSValueConst new_target,
+                                      int argc, JSValueConst *argv)
+{
+    JSValueConst executor;
+    JSValue obj;
+    JSPromiseData *s;
+    JSValue args[2], ret;
+    int i;
+
+    executor = argv[0];
+    if (check_function(ctx, executor))
+        return JS_EXCEPTION;
+    obj = js_create_from_ctor(ctx, new_target, JS_CLASS_PROMISE);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s)
+        goto fail;
+    s->promise_state = JS_PROMISE_PENDING;
+    s->is_handled = FALSE;
+    for(i = 0; i < 2; i++)
+        init_list_head(&s->promise_reactions[i]);
+    s->promise_result = JS_UNDEFINED;
+    JS_SetOpaque(obj, s);
+    if (js_create_resolving_functions(ctx, args, obj))
+        goto fail;
+    ret = JS_Call(ctx, executor, JS_UNDEFINED, 2, (JSValueConst *)args);
+    if (JS_IsException(ret)) {
+        JSValue ret2, error;
+        error = JS_GetException(ctx);
+        ret2 = JS_Call(ctx, args[1], JS_UNDEFINED, 1, (JSValueConst *)&error);
+        JS_FreeValue(ctx, error);
+        if (JS_IsException(ret2))
+            goto fail1;
+        JS_FreeValue(ctx, ret2);
+    }
+    JS_FreeValue(ctx, ret);
+    JS_FreeValue(ctx, args[0]);
+    JS_FreeValue(ctx, args[1]);
+    return obj;
+ fail1:
+    JS_FreeValue(ctx, args[0]);
+    JS_FreeValue(ctx, args[1]);
+ fail:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_promise_executor(JSContext *ctx,
+                                   JSValueConst this_val,
+                                   int argc, JSValueConst *argv,
+                                   int magic, JSValue *func_data)
+{
+    int i;
+
+    for(i = 0; i < 2; i++) {
+        if (!JS_IsUndefined(func_data[i]))
+            return JS_ThrowTypeError(ctx, "resolving function already set");
+        func_data[i] = JS_DupValue(ctx, argv[i]);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_promise_executor_new(JSContext *ctx)
+{
+    JSValueConst func_data[2];
+
+    func_data[0] = JS_UNDEFINED;
+    func_data[1] = JS_UNDEFINED;
+    return JS_NewCFunctionData(ctx, js_promise_executor, 2,
+                               0, 2, func_data);
+}
+
+static JSValue js_new_promise_capability(JSContext *ctx,
+                                         JSValue *resolving_funcs,
+                                         JSValueConst ctor)
+{
+    JSValue executor, result_promise;
+    JSCFunctionDataRecord *s;
+    int i;
+
+    executor = js_promise_executor_new(ctx);
+    if (JS_IsException(executor))
+        return executor;
+
+    if (JS_IsUndefined(ctor)) {
+        result_promise = js_promise_constructor(ctx, ctor, 1,
+                                                (JSValueConst *)&executor);
+    } else {
+        result_promise = JS_CallConstructor(ctx, ctor, 1,
+                                            (JSValueConst *)&executor);
+    }
+    if (JS_IsException(result_promise))
+        goto fail;
+    s = JS_GetOpaque(executor, JS_CLASS_C_FUNCTION_DATA);
+    for(i = 0; i < 2; i++) {
+        if (check_function(ctx, s->data[i]))
+            goto fail;
+    }
+    for(i = 0; i < 2; i++)
+        resolving_funcs[i] = JS_DupValue(ctx, s->data[i]);
+    JS_FreeValue(ctx, executor);
+    return result_promise;
+ fail:
+    JS_FreeValue(ctx, executor);
+    JS_FreeValue(ctx, result_promise);
+    return JS_EXCEPTION;
+}
+
+JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs)
+{
+    return js_new_promise_capability(ctx, resolving_funcs, JS_UNDEFINED);
+}
+
+static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv, int magic)
+{
+    JSValue result_promise, resolving_funcs[2], ret;
+    BOOL is_reject = magic;
+
+    if (!JS_IsObject(this_val))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    if (!is_reject && JS_GetOpaque(argv[0], JS_CLASS_PROMISE)) {
+        JSValue ctor;
+        BOOL is_same;
+        ctor = JS_GetProperty(ctx, argv[0], JS_ATOM_constructor);
+        if (JS_IsException(ctor))
+            return ctor;
+        is_same = js_same_value(ctx, ctor, this_val);
+        JS_FreeValue(ctx, ctor);
+        if (is_same)
+            return JS_DupValue(ctx, argv[0]);
+    }
+    result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
+    if (JS_IsException(result_promise))
+        return result_promise;
+    ret = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, argv);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    if (JS_IsException(ret)) {
+        JS_FreeValue(ctx, result_promise);
+        return ret;
+    }
+    JS_FreeValue(ctx, ret);
+    return result_promise;
+}
+
+#if 0
+static JSValue js_promise___newPromiseCapability(JSContext *ctx,
+                                                 JSValueConst this_val,
+                                                 int argc, JSValueConst *argv)
+{
+    JSValue result_promise, resolving_funcs[2], obj;
+    JSValueConst ctor;
+    ctor = argv[0];
+    if (!JS_IsObject(ctor))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
+    if (JS_IsException(result_promise))
+        return result_promise;
+    obj = JS_NewObject(ctx);
+    if (JS_IsException(obj)) {
+        JS_FreeValue(ctx, resolving_funcs[0]);
+        JS_FreeValue(ctx, resolving_funcs[1]);
+        JS_FreeValue(ctx, result_promise);
+        return JS_EXCEPTION;
+    }
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, JS_PROP_C_W_E);
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs[0], JS_PROP_C_W_E);
+    JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs[1], JS_PROP_C_W_E);
+    return obj;
+}
+#endif
+
+static __exception int remainingElementsCount_add(JSContext *ctx,
+                                                  JSValueConst resolve_element_env,
+                                                  int addend)
+{
+    JSValue val;
+    int remainingElementsCount;
+
+    val = JS_GetPropertyUint32(ctx, resolve_element_env, 0);
+    if (JS_IsException(val))
+        return -1;
+    if (JS_ToInt32Free(ctx, &remainingElementsCount, val))
+        return -1;
+    remainingElementsCount += addend;
+    if (JS_SetPropertyUint32(ctx, resolve_element_env, 0,
+                             JS_NewInt32(ctx, remainingElementsCount)) < 0)
+        return -1;
+    return (remainingElementsCount == 0);
+}
+
+static JSValue js_promise_all_resolve_element(JSContext *ctx,
+                                              JSValueConst this_val,
+                                              int argc, JSValueConst *argv,
+                                              int magic,
+                                              JSValue *func_data)
+{
+    int is_allSettled = magic & 1;
+    int is_reject = magic & 2;
+    BOOL alreadyCalled = JS_ToBool(ctx, func_data[0]);
+    JSValueConst values = func_data[2];
+    JSValueConst resolve = func_data[3];
+    JSValueConst resolve_element_env = func_data[4];
+    JSValue ret, obj;
+    int is_zero, index;
+    
+    if (JS_ToInt32(ctx, &index, func_data[1]))
+        return JS_EXCEPTION;
+    if (alreadyCalled)
+        return JS_UNDEFINED;
+    func_data[0] = JS_NewBool(ctx, TRUE);
+
+    if (is_allSettled) {
+        JSValue str;
+        
+        obj = JS_NewObject(ctx);
+        if (JS_IsException(obj))
+            return JS_EXCEPTION;
+        str = JS_NewString(ctx, is_reject ? "rejected" : "fulfilled");
+        if (JS_IsException(str))
+            goto fail1;
+        if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_status,
+                                   str,
+                                   JS_PROP_C_W_E) < 0)
+            goto fail1;
+        if (JS_DefinePropertyValue(ctx, obj,
+                                   is_reject ? JS_ATOM_reason : JS_ATOM_value,
+                                   JS_DupValue(ctx, argv[0]),
+                                   JS_PROP_C_W_E) < 0) {
+        fail1:
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+    } else {
+        obj = JS_DupValue(ctx, argv[0]);
+    }
+    if (JS_DefinePropertyValueUint32(ctx, values, index,
+                                     obj, JS_PROP_C_W_E) < 0)
+        return JS_EXCEPTION;
+    
+    is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
+    if (is_zero < 0)
+        return JS_EXCEPTION;
+    if (is_zero) {
+        ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, (JSValueConst *)&values);
+        if (JS_IsException(ret))
+            return ret;
+        JS_FreeValue(ctx, ret);
+    }
+    return JS_UNDEFINED;
+}
+
+/* magic = 0: Promise.all 1: Promise.allSettled */
+static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int magic)
+{
+    JSValue result_promise, resolving_funcs[2], iter, item, next_promise, ret;
+    JSValue next_method = JS_UNDEFINED, values = JS_UNDEFINED;
+    JSValue resolve_element_env = JS_UNDEFINED, resolve_element, reject_element;
+    JSValue promise_resolve = JS_UNDEFINED;
+    JSValueConst then_args[2], resolve_element_data[5];
+    BOOL done, is_allSettled = magic;
+    int index, is_zero;
+    
+    if (!JS_IsObject(this_val))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
+    if (JS_IsException(result_promise))
+        return result_promise;
+    iter = JS_GetIterator(ctx, argv[0], FALSE);
+    if (JS_IsException(iter)) {
+        JSValue error;
+    fail_reject:
+        error = JS_GetException(ctx);
+        ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1,
+                       (JSValueConst *)&error);
+        JS_FreeValue(ctx, error);
+        if (JS_IsException(ret))
+            goto fail;
+        JS_FreeValue(ctx, ret);
+    } else {
+        next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
+        if (JS_IsException(next_method))
+            goto fail_reject;
+        values = JS_NewArray(ctx);
+        if (JS_IsException(values))
+            goto fail_reject;
+        resolve_element_env = JS_NewArray(ctx);
+        if (JS_IsException(resolve_element_env))
+            goto fail_reject;
+        /* remainingElementsCount field */
+        if (JS_DefinePropertyValueUint32(ctx, resolve_element_env, 0,
+                                         JS_NewInt32(ctx, 1),
+                                         JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0)
+            goto fail_reject;
+        
+        promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve);
+        if (JS_IsException(promise_resolve) ||
+            check_function(ctx, promise_resolve))
+            goto fail_reject1;
+        
+        index = 0;
+        for(;;) {
+            /* XXX: conformance: should close the iterator if error on 'done'
+               access, but not on 'value' access */
+            item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
+            if (JS_IsException(item))
+                goto fail_reject;
+            if (done)
+                break;
+            next_promise = JS_Call(ctx, promise_resolve, 
+                                   this_val, 1, (JSValueConst *)&item);
+            JS_FreeValue(ctx, item);
+            if (JS_IsException(next_promise)) {
+            fail_reject1:
+                JS_IteratorClose(ctx, iter, TRUE);
+                goto fail_reject;
+            }
+            resolve_element_data[0] = JS_NewBool(ctx, FALSE);
+            resolve_element_data[1] = (JSValueConst)JS_NewInt32(ctx, index);
+            resolve_element_data[2] = values;
+            resolve_element_data[3] = resolving_funcs[0];
+            resolve_element_data[4] = resolve_element_env;
+            resolve_element =
+                JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1,
+                                    is_allSettled, 5, resolve_element_data);
+            if (JS_IsException(resolve_element)) {
+                JS_FreeValue(ctx, next_promise);
+                goto fail_reject1;
+            }
+            
+            if (is_allSettled) {
+                reject_element =
+                    JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1,
+                                        is_allSettled | 2, 5, resolve_element_data);
+                if (JS_IsException(reject_element)) {
+                    JS_FreeValue(ctx, next_promise);
+                    goto fail_reject1;
+                }
+            } else {
+                reject_element = JS_DupValue(ctx, resolving_funcs[1]);
+            }
+
+            if (remainingElementsCount_add(ctx, resolve_element_env, 1) < 0) {
+                JS_FreeValue(ctx, next_promise);
+                JS_FreeValue(ctx, resolve_element);
+                JS_FreeValue(ctx, reject_element);
+                goto fail_reject1;
+            }
+
+            then_args[0] = resolve_element;
+            then_args[1] = reject_element;
+            ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, then_args);
+            JS_FreeValue(ctx, resolve_element);
+            JS_FreeValue(ctx, reject_element);
+            if (check_exception_free(ctx, ret))
+                goto fail_reject1;
+            index++;
+        }
+
+        is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
+        if (is_zero < 0)
+            goto fail_reject;
+        if (is_zero) {
+            ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
+                          1, (JSValueConst *)&values);
+            if (check_exception_free(ctx, ret))
+                goto fail_reject;
+        }
+    }
+ done:
+    JS_FreeValue(ctx, promise_resolve);
+    JS_FreeValue(ctx, resolve_element_env);
+    JS_FreeValue(ctx, values);
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    return result_promise;
+ fail:
+    JS_FreeValue(ctx, result_promise);
+    result_promise = JS_EXCEPTION;
+    goto done;
+}
+
+static JSValue js_promise_race(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    JSValue result_promise, resolving_funcs[2], iter, item, next_promise, ret;
+    JSValue next_method = JS_UNDEFINED;
+    JSValue promise_resolve = JS_UNDEFINED;
+    BOOL done;
+
+    if (!JS_IsObject(this_val))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+    result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
+    if (JS_IsException(result_promise))
+        return result_promise;
+    iter = JS_GetIterator(ctx, argv[0], FALSE);
+    if (JS_IsException(iter)) {
+        JSValue error;
+    fail_reject:
+        error = JS_GetException(ctx);
+        ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1,
+                       (JSValueConst *)&error);
+        JS_FreeValue(ctx, error);
+        if (JS_IsException(ret))
+            goto fail;
+        JS_FreeValue(ctx, ret);
+    } else {
+        next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
+        if (JS_IsException(next_method))
+            goto fail_reject;
+
+        promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve);
+        if (JS_IsException(promise_resolve) ||
+            check_function(ctx, promise_resolve))
+            goto fail_reject1;
+
+        for(;;) {
+            /* XXX: conformance: should close the iterator if error on 'done'
+               access, but not on 'value' access */
+            item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
+            if (JS_IsException(item))
+                goto fail_reject;
+            if (done)
+                break;
+            next_promise = JS_Call(ctx, promise_resolve,
+                                   this_val, 1, (JSValueConst *)&item);
+            JS_FreeValue(ctx, item);
+            if (JS_IsException(next_promise)) {
+            fail_reject1:
+                JS_IteratorClose(ctx, iter, TRUE);
+                goto fail_reject;
+            }
+            ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2,
+                                (JSValueConst *)resolving_funcs);
+            if (check_exception_free(ctx, ret))
+                goto fail_reject1;
+        }
+    }
+ done:
+    JS_FreeValue(ctx, promise_resolve);
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    return result_promise;
+ fail:
+    //JS_FreeValue(ctx, next_method); // why not???
+    JS_FreeValue(ctx, result_promise);
+    result_promise = JS_EXCEPTION;
+    goto done;
+}
+
+static __exception int perform_promise_then(JSContext *ctx,
+                                            JSValueConst promise,
+                                            JSValueConst *resolve_reject,
+                                            JSValueConst *cap_resolving_funcs)
+{
+    JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
+    JSPromiseReactionData *rd_array[2], *rd;
+    int i, j;
+
+    rd_array[0] = NULL;
+    rd_array[1] = NULL;
+    for(i = 0; i < 2; i++) {
+        JSValueConst handler;
+        rd = js_mallocz(ctx, sizeof(*rd));
+        if (!rd) {
+            if (i == 1)
+                promise_reaction_data_free(ctx->rt, rd_array[0]);
+            return -1;
+        }
+        for(j = 0; j < 2; j++)
+            rd->resolving_funcs[j] = JS_DupValue(ctx, cap_resolving_funcs[j]);
+        handler = resolve_reject[i];
+        if (!JS_IsFunction(ctx, handler))
+            handler = JS_UNDEFINED;
+        rd->handler = JS_DupValue(ctx, handler);
+        rd_array[i] = rd;
+    }
+
+    if (s->promise_state == JS_PROMISE_PENDING) {
+        for(i = 0; i < 2; i++)
+            list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]);
+    } else {
+        JSValueConst args[5];
+        i = s->promise_state - JS_PROMISE_FULFILLED;
+        rd = rd_array[i];
+        args[0] = rd->resolving_funcs[0];
+        args[1] = rd->resolving_funcs[1];
+        args[2] = rd->handler;
+        args[3] = JS_NewBool(ctx, i);
+        args[4] = s->promise_result;
+        JS_EnqueueJob(ctx, promise_reaction_job, 5, args);
+        for(i = 0; i < 2; i++)
+            promise_reaction_data_free(ctx->rt, rd_array[i]);
+    }
+    s->is_handled = TRUE;
+    return 0;
+}
+
+static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    JSValue ctor, result_promise, resolving_funcs[2];
+    JSPromiseData *s;
+    int i, ret;
+
+    s = JS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE);
+    if (!s)
+        return JS_EXCEPTION;
+
+    ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
+    if (JS_IsException(ctor))
+        return ctor;
+    result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
+    JS_FreeValue(ctx, ctor);
+    if (JS_IsException(result_promise))
+        return result_promise;
+    ret = perform_promise_then(ctx, this_val, argv,
+                               (JSValueConst *)resolving_funcs);
+    for(i = 0; i < 2; i++)
+        JS_FreeValue(ctx, resolving_funcs[i]);
+    if (ret) {
+        JS_FreeValue(ctx, result_promise);
+        return JS_EXCEPTION;
+    }
+    return result_promise;
+}
+
+static JSValue js_promise_catch(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValueConst args[2];
+    args[0] = JS_UNDEFINED;
+    args[1] = argv[0];
+    return JS_Invoke(ctx, this_val, JS_ATOM_then, 2, args);
+}
+
+static JSValue js_promise_finally_value_thunk(JSContext *ctx, JSValueConst this_val,
+                                              int argc, JSValueConst *argv,
+                                              int magic, JSValue *func_data)
+{
+    return JS_DupValue(ctx, func_data[0]);
+}
+
+static JSValue js_promise_finally_thrower(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv,
+                                          int magic, JSValue *func_data)
+{
+    return JS_Throw(ctx, JS_DupValue(ctx, func_data[0]));
+}
+
+static JSValue js_promise_then_finally_func(JSContext *ctx, JSValueConst this_val,
+                                            int argc, JSValueConst *argv,
+                                            int magic, JSValue *func_data)
+{
+    JSValueConst ctor = func_data[0];
+    JSValueConst onFinally = func_data[1];
+    JSValue res, promise, resolving_funcs[2], ret, then_func;
+
+    res = JS_Call(ctx, onFinally, JS_UNDEFINED, 0, NULL);
+    if (JS_IsException(res))
+        return res;
+    promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
+    if (JS_IsException(promise)) {
+        JS_FreeValue(ctx, res);
+        return JS_EXCEPTION;
+    }
+    ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
+                  1, (JSValueConst*)&res);
+    JS_FreeValue(ctx, res);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    if (JS_IsException(ret)) {
+        JS_FreeValue(ctx, promise);
+        return ret;
+    }
+    JS_FreeValue(ctx, ret);
+    if (magic == 0) {
+        then_func = JS_NewCFunctionData(ctx, js_promise_finally_value_thunk, 1,
+                                        0, 1, argv);
+    } else {
+        then_func = JS_NewCFunctionData(ctx, js_promise_finally_thrower, 1,
+                                        0, 1, argv);
+    }
+    if (JS_IsException(then_func)) {
+        JS_FreeValue(ctx, promise);
+        return then_func;
+    }
+    ret = JS_InvokeFree(ctx, promise, JS_ATOM_then, 1, (JSValueConst *)&then_func);
+    JS_FreeValue(ctx, then_func);
+    return ret;
+}
+
+static JSValue js_promise_finally(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValueConst onFinally = argv[0];
+    JSValue ctor, ret;
+    JSValue then_funcs[2];
+    JSValueConst func_data[2];
+    int i;
+
+    ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
+    if (JS_IsException(ctor))
+        return ctor;
+    if (!JS_IsFunction(ctx, onFinally)) {
+        then_funcs[0] = JS_DupValue(ctx, onFinally);
+        then_funcs[1] = JS_DupValue(ctx, onFinally);
+    } else {
+        func_data[0] = ctor;
+        func_data[1] = onFinally;
+        for(i = 0; i < 2; i++) {
+            then_funcs[i] = JS_NewCFunctionData(ctx, js_promise_then_finally_func, 1, i, 2, func_data);
+            if (JS_IsException(then_funcs[i])) {
+                if (i == 1)
+                    JS_FreeValue(ctx, then_funcs[0]);
+                JS_FreeValue(ctx, ctor);
+                return JS_EXCEPTION;
+            }
+        }
+    }
+    JS_FreeValue(ctx, ctor);
+    ret = JS_Invoke(ctx, this_val, JS_ATOM_then, 2, (JSValueConst *)then_funcs);
+    JS_FreeValue(ctx, then_funcs[0]);
+    JS_FreeValue(ctx, then_funcs[1]);
+    return ret;
+}
+
+static const JSCFunctionListEntry js_promise_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("resolve", 1, js_promise_resolve, 0 ),
+    JS_CFUNC_MAGIC_DEF("reject", 1, js_promise_resolve, 1 ),
+    JS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, 0 ),
+    JS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all, 1 ),
+    JS_CFUNC_DEF("race", 1, js_promise_race ),
+    //JS_CFUNC_DEF("__newPromiseCapability", 1, js_promise___newPromiseCapability ),
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
+};
+
+static const JSCFunctionListEntry js_promise_proto_funcs[] = {
+    JS_CFUNC_DEF("then", 2, js_promise_then ),
+    JS_CFUNC_DEF("catch", 1, js_promise_catch ),
+    JS_CFUNC_DEF("finally", 1, js_promise_finally ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Promise", JS_PROP_CONFIGURABLE ),
+};
+
+/* AsyncFunction */
+static const JSCFunctionListEntry js_async_function_proto_funcs[] = {
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction", JS_PROP_CONFIGURABLE ),
+};
+
+static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx,
+                                                  JSValueConst this_val,
+                                                  int argc, JSValueConst *argv,
+                                                  int magic, JSValue *func_data)
+{
+    return js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]),
+                                     JS_ToBool(ctx, func_data[0]));
+}
+
+static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx,
+                                                              BOOL done)
+{
+    JSValueConst func_data[1];
+
+    func_data[0] = (JSValueConst)JS_NewBool(ctx, done);
+    return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap,
+                               1, 0, 1, func_data);
+}
+
+/* AsyncIteratorPrototype */
+
+static const JSCFunctionListEntry js_async_iterator_proto_funcs[] = {
+    JS_CFUNC_DEF("[Symbol.asyncIterator]", 0, js_iterator_proto_iterator ),
+};
+
+/* AsyncFromSyncIteratorPrototype */
+
+typedef struct JSAsyncFromSyncIteratorData {
+    JSValue sync_iter;
+    JSValue next_method;
+} JSAsyncFromSyncIteratorData;
+
+static void js_async_from_sync_iterator_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSAsyncFromSyncIteratorData *s =
+        JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
+    if (s) {
+        JS_FreeValueRT(rt, s->sync_iter);
+        JS_FreeValueRT(rt, s->next_method);
+        js_free_rt(rt, s);
+    }
+}
+
+static void js_async_from_sync_iterator_mark(JSRuntime *rt, JSValueConst val,
+                                             JS_MarkFunc *mark_func)
+{
+    JSAsyncFromSyncIteratorData *s =
+        JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
+    if (s) {
+        JS_MarkValue(rt, s->sync_iter, mark_func);
+        JS_MarkValue(rt, s->next_method, mark_func);
+    }
+}
+
+static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx,
+                                              JSValueConst sync_iter)
+{
+    JSValue async_iter, next_method;
+    JSAsyncFromSyncIteratorData *s;
+
+    next_method = JS_GetProperty(ctx, sync_iter, JS_ATOM_next);
+    if (JS_IsException(next_method))
+        return JS_EXCEPTION;
+    async_iter = JS_NewObjectClass(ctx, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
+    if (JS_IsException(async_iter)) {
+        JS_FreeValue(ctx, next_method);
+        return async_iter;
+    }
+    s = js_mallocz(ctx, sizeof(*s));
+    if (!s) {
+        JS_FreeValue(ctx, async_iter);
+        JS_FreeValue(ctx, next_method);
+        return JS_EXCEPTION;
+    }
+    s->sync_iter = JS_DupValue(ctx, sync_iter);
+    s->next_method = next_method;
+    JS_SetOpaque(async_iter, s);
+    return async_iter;
+}
+
+static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst this_val,
+                                                int argc, JSValueConst *argv,
+                                                int magic)
+{
+    JSValue promise, resolving_funcs[2], value, err, method;
+    JSAsyncFromSyncIteratorData *s;
+    int done;
+    int is_reject;
+
+    promise = JS_NewPromiseCapability(ctx, resolving_funcs);
+    if (JS_IsException(promise))
+        return JS_EXCEPTION;
+    s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
+    if (!s) {
+        JS_ThrowTypeError(ctx, "not an Async-from-Sync Iterator");
+        goto reject;
+    }
+
+    if (magic == GEN_MAGIC_NEXT) {
+        method = JS_DupValue(ctx, s->next_method);
+    } else {
+        method = JS_GetProperty(ctx, s->sync_iter,
+                                magic == GEN_MAGIC_RETURN ? JS_ATOM_return :
+                                JS_ATOM_throw);
+        if (JS_IsException(method))
+            goto reject;
+        if (JS_IsUndefined(method) || JS_IsNull(method)) {
+            if (magic == GEN_MAGIC_RETURN) {
+                err = js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), TRUE);
+                is_reject = 0;
+            } else {
+                err = JS_DupValue(ctx, argv[0]);
+                is_reject = 1;
+            }
+            goto done_resolve;
+        }
+    }
+    value = JS_IteratorNext2(ctx, s->sync_iter, method, 1, argv, &done);
+    JS_FreeValue(ctx, method);
+    if (JS_IsException(value))
+        goto reject;
+    if (done == 2) {
+        JSValue obj = value;
+        value = JS_IteratorGetCompleteValue(ctx, obj, &done);
+        JS_FreeValue(ctx, obj);
+        if (JS_IsException(value))
+            goto reject;
+    }
+
+    if (JS_IsException(value)) {
+        JSValue res2;
+    reject:
+        err = JS_GetException(ctx);
+        is_reject = 1;
+    done_resolve:
+        res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED,
+                       1, (JSValueConst *)&err);
+        JS_FreeValue(ctx, err);
+        JS_FreeValue(ctx, res2);
+        JS_FreeValue(ctx, resolving_funcs[0]);
+        JS_FreeValue(ctx, resolving_funcs[1]);
+        return promise;
+    }
+    {
+        JSValue value_wrapper_promise, resolve_reject[2];
+        int res;
+
+        value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor,
+                                                   1, (JSValueConst *)&value, 0);
+        if (JS_IsException(value_wrapper_promise)) {
+            JS_FreeValue(ctx, value);
+            goto reject;
+        }
+
+        resolve_reject[0] =
+            js_async_from_sync_iterator_unwrap_func_create(ctx, done);
+        if (JS_IsException(resolve_reject[0])) {
+            JS_FreeValue(ctx, value_wrapper_promise);
+            goto fail;
+        }
+        JS_FreeValue(ctx, value);
+        resolve_reject[1] = JS_UNDEFINED;
+
+        res = perform_promise_then(ctx, value_wrapper_promise,
+                                   (JSValueConst *)resolve_reject,
+                                   (JSValueConst *)resolving_funcs);
+        JS_FreeValue(ctx, resolve_reject[0]);
+        JS_FreeValue(ctx, value_wrapper_promise);
+        JS_FreeValue(ctx, resolving_funcs[0]);
+        JS_FreeValue(ctx, resolving_funcs[1]);
+        if (res) {
+            JS_FreeValue(ctx, promise);
+            return JS_EXCEPTION;
+        }
+    }
+    return promise;
+ fail:
+    JS_FreeValue(ctx, value);
+    JS_FreeValue(ctx, resolving_funcs[0]);
+    JS_FreeValue(ctx, resolving_funcs[1]);
+    JS_FreeValue(ctx, promise);
+    return JS_EXCEPTION;
+}
+
+static const JSCFunctionListEntry js_async_from_sync_iterator_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("next", 1, js_async_from_sync_iterator_next, GEN_MAGIC_NEXT ),
+    JS_CFUNC_MAGIC_DEF("return", 1, js_async_from_sync_iterator_next, GEN_MAGIC_RETURN ),
+    JS_CFUNC_MAGIC_DEF("throw", 1, js_async_from_sync_iterator_next, GEN_MAGIC_THROW ),
+};
+
+/* AsyncGeneratorFunction */
+
+static const JSCFunctionListEntry js_async_generator_function_proto_funcs[] = {
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGeneratorFunction", JS_PROP_CONFIGURABLE ),
+};
+
+/* AsyncGenerator prototype */
+
+static const JSCFunctionListEntry js_async_generator_proto_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("next", 1, js_async_generator_next, GEN_MAGIC_NEXT ),
+    JS_CFUNC_MAGIC_DEF("return", 1, js_async_generator_next, GEN_MAGIC_RETURN ),
+    JS_CFUNC_MAGIC_DEF("throw", 1, js_async_generator_next, GEN_MAGIC_THROW ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGenerator", JS_PROP_CONFIGURABLE ),
+};
+
+static JSClassShortDef const js_async_class_def[] = {
+    { JS_ATOM_Promise, js_promise_finalizer, js_promise_mark },                      /* JS_CLASS_PROMISE */
+    { JS_ATOM_PromiseResolveFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_RESOLVE_FUNCTION */
+    { JS_ATOM_PromiseRejectFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_REJECT_FUNCTION */
+    { JS_ATOM_AsyncFunction, js_bytecode_function_finalizer, js_bytecode_function_mark },  /* JS_CLASS_ASYNC_FUNCTION */
+    { JS_ATOM_AsyncFunctionResolve, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_RESOLVE */
+    { JS_ATOM_AsyncFunctionReject, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_REJECT */
+    { JS_ATOM_empty_string, js_async_from_sync_iterator_finalizer, js_async_from_sync_iterator_mark }, /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
+    { JS_ATOM_AsyncGeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark },  /* JS_CLASS_ASYNC_GENERATOR_FUNCTION */
+    { JS_ATOM_AsyncGenerator, js_async_generator_finalizer, js_async_generator_mark },  /* JS_CLASS_ASYNC_GENERATOR */
+};
+
+void JS_AddIntrinsicPromise(JSContext *ctx)
+{
+    JSRuntime *rt = ctx->rt;
+    JSValue obj1;
+
+    if (!JS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) {
+        init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE,
+                         countof(js_async_class_def));
+        rt->class_array[JS_CLASS_PROMISE_RESOLVE_FUNCTION].call = js_promise_resolve_function_call;
+        rt->class_array[JS_CLASS_PROMISE_REJECT_FUNCTION].call = js_promise_resolve_function_call;
+        rt->class_array[JS_CLASS_ASYNC_FUNCTION].call = js_async_function_call;
+        rt->class_array[JS_CLASS_ASYNC_FUNCTION_RESOLVE].call = js_async_function_resolve_call;
+        rt->class_array[JS_CLASS_ASYNC_FUNCTION_REJECT].call = js_async_function_resolve_call;
+        rt->class_array[JS_CLASS_ASYNC_GENERATOR_FUNCTION].call = js_async_generator_function_call;
+    }
+
+    /* Promise */
+    ctx->class_proto[JS_CLASS_PROMISE] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_PROMISE],
+                               js_promise_proto_funcs,
+                               countof(js_promise_proto_funcs));
+    obj1 = JS_NewCFunction2(ctx, js_promise_constructor, "Promise", 1,
+                            JS_CFUNC_constructor, 0);
+    ctx->promise_ctor = JS_DupValue(ctx, obj1);
+    JS_SetPropertyFunctionList(ctx, obj1,
+                               js_promise_funcs,
+                               countof(js_promise_funcs));
+    JS_NewGlobalCConstructor2(ctx, obj1, "Promise",
+                              ctx->class_proto[JS_CLASS_PROMISE]);
+
+    /* AsyncFunction */
+    ctx->class_proto[JS_CLASS_ASYNC_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
+    obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor,
+                            "AsyncFunction", 1,
+                            JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC,
+                            ctx->function_ctor);
+    JS_SetPropertyFunctionList(ctx,
+                               ctx->class_proto[JS_CLASS_ASYNC_FUNCTION],
+                               js_async_function_proto_funcs,
+                               countof(js_async_function_proto_funcs));
+    JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_FUNCTION],
+                       0, JS_PROP_CONFIGURABLE);
+    JS_FreeValue(ctx, obj1);
+
+    /* AsyncIteratorPrototype */
+    ctx->async_iterator_proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->async_iterator_proto,
+                               js_async_iterator_proto_funcs,
+                               countof(js_async_iterator_proto_funcs));
+
+    /* AsyncFromSyncIteratorPrototype */
+    ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR] =
+        JS_NewObjectProto(ctx, ctx->async_iterator_proto);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR],
+                               js_async_from_sync_iterator_proto_funcs,
+                               countof(js_async_from_sync_iterator_proto_funcs));
+
+    /* AsyncGeneratorPrototype */
+    ctx->class_proto[JS_CLASS_ASYNC_GENERATOR] =
+        JS_NewObjectProto(ctx, ctx->async_iterator_proto);
+    JS_SetPropertyFunctionList(ctx,
+                               ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
+                               js_async_generator_proto_funcs,
+                               countof(js_async_generator_proto_funcs));
+
+    /* AsyncGeneratorFunction */
+    ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION] =
+        JS_NewObjectProto(ctx, ctx->function_proto);
+    obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor,
+                            "AsyncGeneratorFunction", 1,
+                            JS_CFUNC_constructor_or_func_magic,
+                            JS_FUNC_ASYNC_GENERATOR,
+                            ctx->function_ctor);
+    JS_SetPropertyFunctionList(ctx,
+                               ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
+                               js_async_generator_function_proto_funcs,
+                               countof(js_async_generator_function_proto_funcs));
+    JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
+                       ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
+                       JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
+    JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
+                       0, JS_PROP_CONFIGURABLE);
+    JS_FreeValue(ctx, obj1);
+}
+
+/* URI handling */
+
+static int string_get_hex(JSString *p, int k, int n) {
+    int c = 0, h;
+    while (n-- > 0) {
+        if ((h = from_hex(string_get(p, k++))) < 0)
+            return -1;
+        c = (c << 4) | h;
+    }
+    return c;
+}
+
+static int isURIReserved(int c) {
+    return c < 0x100 && memchr(";/?:@&=+$,#", c, sizeof(";/?:@&=+$,#") - 1) != NULL;
+}
+
+static int __attribute__((format(printf, 2, 3))) js_throw_URIError(JSContext *ctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    JS_ThrowError(ctx, JS_URI_ERROR, fmt, ap);
+    va_end(ap);
+    return -1;
+}
+
+static int hex_decode(JSContext *ctx, JSString *p, int k) {
+    int c;
+
+    if (k >= p->len || string_get(p, k) != '%')
+        return js_throw_URIError(ctx, "expecting %%");
+    if (k + 2 >= p->len || (c = string_get_hex(p, k + 1, 2)) < 0)
+        return js_throw_URIError(ctx, "expecting hex digit");
+
+    return c;
+}
+
+static JSValue js_global_decodeURI(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv, int isComponent)
+{
+    JSValue str;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p;
+    int k, c, c1, n, c_min;
+
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        return str;
+
+    string_buffer_init(ctx, b, 0);
+
+    p = JS_VALUE_GET_STRING(str);
+    for (k = 0; k < p->len;) {
+        c = string_get(p, k);
+        if (c == '%') {
+            c = hex_decode(ctx, p, k);
+            if (c < 0)
+                goto fail;
+            k += 3;
+            if (c < 0x80) {
+                if (!isComponent && isURIReserved(c)) {
+                    c = '%';
+                    k -= 2;
+                }
+            } else {
+                /* Decode URI-encoded UTF-8 sequence */
+                if (c >= 0xc0 && c <= 0xdf) {
+                    n = 1;
+                    c_min = 0x80;
+                    c &= 0x1f;
+                } else if (c >= 0xe0 && c <= 0xef) {
+                    n = 2;
+                    c_min = 0x800;
+                    c &= 0xf;
+                } else if (c >= 0xf0 && c <= 0xf7) {
+                    n = 3;
+                    c_min = 0x10000;
+                    c &= 0x7;
+                } else {
+                    n = 0;
+                    c_min = 1;
+                    c = 0;
+                }
+                while (n-- > 0) {
+                    c1 = hex_decode(ctx, p, k);
+                    if (c1 < 0)
+                        goto fail;
+                    k += 3;
+                    if ((c1 & 0xc0) != 0x80) {
+                        c = 0;
+                        break;
+                    }
+                    c = (c << 6) | (c1 & 0x3f);
+                }
+                if (c < c_min || c > 0x10FFFF) {
+                    js_throw_URIError(ctx, "malformed UTF-8");
+                    goto fail;
+                }
+            }
+        } else {
+            k++;
+        }
+        string_buffer_putc(b, c);
+    }
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+
+fail:
+    JS_FreeValue(ctx, str);
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static int isUnescaped(int c) {
+    static char const unescaped_chars[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz"
+        "0123456789"
+        "@*_+-./";
+    return c < 0x100 &&
+        memchr(unescaped_chars, c, sizeof(unescaped_chars) - 1);
+}
+
+static int isURIUnescaped(int c, int isComponent) {
+    return c < 0x100 &&
+        ((c >= 0x61 && c <= 0x7a) ||
+         (c >= 0x41 && c <= 0x5a) ||
+         (c >= 0x30 && c <= 0x39) ||
+         memchr("-_.!~*'()", c, sizeof("-_.!~*'()") - 1) != NULL ||
+         (!isComponent && isURIReserved(c)));
+}
+
+static int encodeURI_hex(StringBuffer *b, int c) {
+    uint8_t buf[6];
+    int n = 0;
+    const char *hex = "0123456789ABCDEF";
+
+    buf[n++] = '%';
+    if (c >= 256) {
+        buf[n++] = 'u';
+        buf[n++] = hex[(c >> 12) & 15];
+        buf[n++] = hex[(c >>  8) & 15];
+    }
+    buf[n++] = hex[(c >> 4) & 15];
+    buf[n++] = hex[(c >> 0) & 15];
+    return string_buffer_write8(b, buf, n);
+}
+
+static JSValue js_global_encodeURI(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv,
+                                   int isComponent)
+{
+    JSValue str;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p;
+    int k, c, c1;
+
+    str = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(str))
+        return str;
+
+    p = JS_VALUE_GET_STRING(str);
+    string_buffer_init(ctx, b, p->len);
+    for (k = 0; k < p->len;) {
+        c = string_get(p, k);
+        k++;
+        if (isURIUnescaped(c, isComponent)) {
+            string_buffer_putc16(b, c);
+        } else {
+            if (c >= 0xdc00 && c <= 0xdfff) {
+                js_throw_URIError(ctx, "invalid character");
+                goto fail;
+            } else if (c >= 0xd800 && c <= 0xdbff) {
+                if (k >= p->len) {
+                    js_throw_URIError(ctx, "expecting surrogate pair");
+                    goto fail;
+                }
+                c1 = string_get(p, k);
+                k++;
+                if (c1 < 0xdc00 || c1 > 0xdfff) {
+                    js_throw_URIError(ctx, "expecting surrogate pair");
+                    goto fail;
+                }
+                c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
+            }
+            if (c < 0x80) {
+                encodeURI_hex(b, c);
+            } else {
+                /* XXX: use C UTF-8 conversion ? */
+                if (c < 0x800) {
+                    encodeURI_hex(b, (c >> 6) | 0xc0);
+                } else {
+                    if (c < 0x10000) {
+                        encodeURI_hex(b, (c >> 12) | 0xe0);
+                    } else {
+                        encodeURI_hex(b, (c >> 18) | 0xf0);
+                        encodeURI_hex(b, ((c >> 12) & 0x3f) | 0x80);
+                    }
+                    encodeURI_hex(b, ((c >> 6) & 0x3f) | 0x80);
+                }
+                encodeURI_hex(b, (c & 0x3f) | 0x80);
+            }
+        }
+    }
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+
+fail:
+    JS_FreeValue(ctx, str);
+    string_buffer_free(b);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_global_escape(JSContext *ctx, JSValueConst this_val,
+                                int argc, JSValueConst *argv)
+{
+    JSValue str;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p;
+    int i, len, c;
+
+    str = JS_ToStringCheckObject(ctx, argv[0]);
+    if (JS_IsException(str))
+        return str;
+
+    p = JS_VALUE_GET_STRING(str);
+    string_buffer_init(ctx, b, p->len);
+    for (i = 0, len = p->len; i < len; i++) {
+        c = string_get(p, i);
+        if (isUnescaped(c)) {
+            string_buffer_putc16(b, c);
+        } else {
+            encodeURI_hex(b, c);
+        }
+    }
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+}
+
+static JSValue js_global_unescape(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValue str;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p;
+    int i, len, c, n;
+
+    str = JS_ToStringCheckObject(ctx, argv[0]);
+    if (JS_IsException(str))
+        return str;
+
+    string_buffer_init(ctx, b, 0);
+    p = JS_VALUE_GET_STRING(str);
+    for (i = 0, len = p->len; i < len; i++) {
+        c = string_get(p, i);
+        if (c == '%') {
+            if (i + 6 <= len
+            &&  string_get(p, i + 1) == 'u'
+            &&  (n = string_get_hex(p, i + 2, 4)) >= 0) {
+                c = n;
+                i += 6 - 1;
+            } else
+            if (i + 3 <= len
+            &&  (n = string_get_hex(p, i + 1, 2)) >= 0) {
+                c = n;
+                i += 3 - 1;
+            }
+        }
+        string_buffer_putc16(b, c);
+    }
+    JS_FreeValue(ctx, str);
+    return string_buffer_end(b);
+}
+
+/* global object */
+
+static const JSCFunctionListEntry js_global_funcs[] = {
+    JS_CFUNC_DEF("parseInt", 2, js_parseInt ),
+    JS_CFUNC_DEF("parseFloat", 1, js_parseFloat ),
+    JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ),
+    JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ),
+
+    JS_CFUNC_MAGIC_DEF("decodeURI", 1, js_global_decodeURI, 0 ),
+    JS_CFUNC_MAGIC_DEF("decodeURIComponent", 1, js_global_decodeURI, 1 ),
+    JS_CFUNC_MAGIC_DEF("encodeURI", 1, js_global_encodeURI, 0 ),
+    JS_CFUNC_MAGIC_DEF("encodeURIComponent", 1, js_global_encodeURI, 1 ),
+    JS_CFUNC_DEF("escape", 1, js_global_escape ),
+    JS_CFUNC_DEF("unescape", 1, js_global_unescape ),
+    JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ),
+    JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ),
+    JS_PROP_UNDEFINED_DEF("undefined", 0 ),
+
+    /* for the 'Date' implementation */
+    JS_CFUNC_DEF("__date_clock", 0, js___date_clock ),
+    //JS_CFUNC_DEF("__date_now", 0, js___date_now ),
+    //JS_CFUNC_DEF("__date_getTimezoneOffset", 1, js___date_getTimezoneOffset ),
+    //JS_CFUNC_DEF("__date_create", 3, js___date_create ),
+};
+
+/* Date */
+
+static int64_t math_mod(int64_t a, int64_t b) {
+    /* return positive modulo */
+    int64_t m = a % b;
+    return m + (m < 0) * b;
+}
+
+static int64_t floor_div(int64_t a, int64_t b) {
+    /* integer division rounding toward -Infinity */
+    int64_t m = a % b;
+    return (a - (m + (m < 0) * b)) / b;
+}
+
+static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv);
+
+static __exception int JS_ThisTimeValue(JSContext *ctx, double *valp, JSValueConst this_val)
+{
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data))
+            return JS_ToFloat64(ctx, valp, p->u.object_data);
+    }
+    JS_ThrowTypeError(ctx, "not a Date object");
+    return -1;
+}
+
+static JSValue JS_SetThisTimeValue(JSContext *ctx, JSValueConst this_val, double v)
+{
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_DATE) {
+            JS_FreeValue(ctx, p->u.object_data);
+            p->u.object_data = __JS_NewFloat64(ctx, v);
+            return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a Date object");
+}
+
+static int64_t days_from_year(int64_t y) {
+    return 365 * (y - 1970) + floor_div(y - 1969, 4) -
+        floor_div(y - 1901, 100) + floor_div(y - 1601, 400);
+}
+
+static int64_t days_in_year(int64_t y) {
+    return 365 + !(y % 4) - !(y % 100) + !(y % 400);
+}
+
+/* return the year, update days */
+static int64_t year_from_days(int64_t *days) {
+    int64_t y, d1, nd, d = *days;
+    y = floor_div(d * 10000, 3652425) + 1970;
+    /* the initial approximation is very good, so only a few
+       iterations are necessary */
+    for(;;) {
+        d1 = d - days_from_year(y);
+        if (d1 < 0) {
+            y--;
+            d1 += days_in_year(y);
+        } else {
+            nd = days_in_year(y);
+            if (d1 < nd)
+                break;
+            d1 -= nd;
+            y++;
+        }
+    }
+    *days = d1;
+    return y;
+}
+
+static int const month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
+static char const day_names[] = "SunMonTueWedThuFriSat";
+
+static __exception int get_date_fields(JSContext *ctx, JSValueConst obj,
+                                       int64_t fields[9], int is_local, int force)
+{
+    double dval;
+    int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0;
+
+    if (JS_ThisTimeValue(ctx, &dval, obj))
+        return -1;
+
+    if (isnan(dval)) {
+        if (!force)
+            return FALSE; /* NaN */
+        d = 0;        /* initialize all fields to 0 */
+    } else {
+        d = dval;
+        if (is_local) {
+            tz = -getTimezoneOffset(d);
+            d += tz * 60000;
+        }
+    }
+
+    /* result is >= 0, we can use % */
+    h = math_mod(d, 86400000);
+    days = (d - h) / 86400000;
+    ms = h % 1000;
+    h = (h - ms) / 1000;
+    s = h % 60;
+    h = (h - s) / 60;
+    m = h % 60;
+    h = (h - m) / 60;
+    wd = math_mod(days + 4, 7); /* week day */
+    y = year_from_days(&days);
+
+    for(i = 0; i < 11; i++) {
+        md = month_days[i];
+        if (i == 1)
+            md += days_in_year(y) - 365;
+        if (days < md)
+            break;
+        days -= md;
+    }
+    fields[0] = y;
+    fields[1] = i;
+    fields[2] = days + 1;
+    fields[3] = h;
+    fields[4] = m;
+    fields[5] = s;
+    fields[6] = ms;
+    fields[7] = wd;
+    fields[8] = tz;
+    return TRUE;
+}
+
+static double time_clip(double t) {
+    if (t >= -8.64e15 && t <= 8.64e15)
+        return trunc(t) + 0.0;  /* convert -0 to +0 */
+    else
+        return NAN;
+}
+
+static double set_date_fields(int64_t fields[], int is_local) {
+    int64_t days, y, m, md, h, d, i;
+
+    i = fields[1];
+    m = math_mod(i, 12);
+    y = fields[0] + (i - m) / 12;
+    days = days_from_year(y);
+
+    for(i = 0; i < m; i++) {
+        md = month_days[i];
+        if (i == 1)
+            md += days_in_year(y) - 365;
+        days += md;
+    }
+    days += fields[2] - 1;
+    h = ((fields[3] * 60 + fields[4]) * 60 + fields[5]) * 1000 + fields[6];
+    d = days * 86400000 + h;
+    if (is_local)
+        d += getTimezoneOffset(d) * 60000;
+    return time_clip(d);
+}
+
+static JSValue get_date_field(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int magic)
+{
+    // get_date_field(obj, n, is_local)
+    int64_t fields[9];
+    int res, n, is_local;
+
+    is_local = magic & 0x0F;
+    n = (magic >> 4) & 0x0F;
+    res = get_date_fields(ctx, this_val, fields, is_local, 0);
+    if (res < 0)
+        return JS_EXCEPTION;
+    if (!res)
+        return JS_NAN;
+
+    if (magic & 0x100) {    // getYear
+        fields[0] -= 1900;
+    }
+    return JS_NewInt64(ctx, fields[n]);
+}
+
+static JSValue set_date_field(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv, int magic)
+{
+    // _field(obj, first_field, end_field, args, is_local)
+    int64_t fields[9];
+    int res, first_field, end_field, is_local, i, n;
+    double d, a;
+
+    d = NAN;
+    first_field = (magic >> 8) & 0x0F;
+    end_field = (magic >> 4) & 0x0F;
+    is_local = magic & 0x0F;
+
+    res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0);
+    if (res < 0)
+        return JS_EXCEPTION;
+    if (res && argc > 0) {
+        n = end_field - first_field;
+        if (argc < n)
+            n = argc;
+        for(i = 0; i < n; i++) {
+            if (JS_ToFloat64(ctx, &a, argv[i]))
+                return JS_EXCEPTION;
+            if (!isfinite(a))
+                goto done;
+            fields[first_field + i] = trunc(a);
+        }
+        d = set_date_fields(fields, is_local);
+    }
+done:
+    return JS_SetThisTimeValue(ctx, this_val, d);
+}
+
+/* fmt:
+   0: toUTCString: "Tue, 02 Jan 2018 23:04:46 GMT"
+   1: toString: "Wed Jan 03 2018 00:05:22 GMT+0100 (CET)"
+   2: toISOString: "2018-01-02T23:02:56.927Z"
+   3: toLocaleString: "1/2/2018, 11:40:40 PM"
+   part: 1=date, 2=time 3=all
+   XXX: should use a variant of strftime().
+ */
+static JSValue get_date_string(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv, int magic)
+{
+    // _string(obj, fmt, part)
+    char buf[64];
+    int64_t fields[9];
+    int res, fmt, part, pos;
+    int y, mon, d, h, m, s, ms, wd, tz;
+
+    fmt = (magic >> 4) & 0x0F;
+    part = magic & 0x0F;
+
+    res = get_date_fields(ctx, this_val, fields, fmt & 1, 0);
+    if (res < 0)
+        return JS_EXCEPTION;
+    if (!res) {
+        if (fmt == 2)
+            return JS_ThrowRangeError(ctx, "Date value is NaN");
+        else
+            return JS_NewString(ctx, "Invalid Date");
+    }
+
+    y = fields[0];
+    mon = fields[1];
+    d = fields[2];
+    h = fields[3];
+    m = fields[4];
+    s = fields[5];
+    ms = fields[6];
+    wd = fields[7];
+    tz = fields[8];
+
+    pos = 0;
+
+    if (part & 1) { /* date part */
+        switch(fmt) {
+        case 0:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%.3s, %02d %.3s %0*d ",
+                            day_names + wd * 3, d,
+                            month_names + mon * 3, 4 + (y < 0), y);
+            break;
+        case 1:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%.3s %.3s %02d %0*d",
+                            day_names + wd * 3,
+                            month_names + mon * 3, d, 4 + (y < 0), y);
+            if (part == 3) {
+                buf[pos++] = ' ';
+            }
+            break;
+        case 2:
+            if (y >= 0 && y <= 9999) {
+                pos += snprintf(buf + pos, sizeof(buf) - pos,
+                                "%04d", y);
+            } else {
+                pos += snprintf(buf + pos, sizeof(buf) - pos,
+                                "%+07d", y);
+            }
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "-%02d-%02dT", mon + 1, d);
+            break;
+        case 3:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%02d/%02d/%0*d", mon + 1, d, 4 + (y < 0), y);
+            if (part == 3) {
+                buf[pos++] = ',';
+                buf[pos++] = ' ';
+            }
+            break;
+        }
+    }
+    if (part & 2) { /* time part */
+        switch(fmt) {
+        case 0:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%02d:%02d:%02d GMT", h, m, s);
+            break;
+        case 1:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%02d:%02d:%02d GMT", h, m, s);
+            if (tz < 0) {
+                buf[pos++] = '-';
+                tz = -tz;
+            } else {
+                buf[pos++] = '+';
+            }
+            /* tz is >= 0, can use % */
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%02d%02d", tz / 60, tz % 60);
+            /* XXX: tack the time zone code? */
+            break;
+        case 2:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%02d:%02d:%02d.%03dZ", h, m, s, ms);
+            break;
+        case 3:
+            pos += snprintf(buf + pos, sizeof(buf) - pos,
+                            "%02d:%02d:%02d %cM", (h + 1) % 12 - 1, m, s,
+                            (h < 12) ? 'A' : 'P');
+            break;
+        }
+    }
+    return JS_NewStringLen(ctx, buf, pos);
+}
+
+/* OS dependent: return the UTC time in ms since 1970. */
+static int64_t date_now(void) {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
+}
+
+static JSValue js_date_constructor(JSContext *ctx, JSValueConst new_target,
+                                   int argc, JSValueConst *argv)
+{
+    // Date(y, mon, d, h, m, s, ms)
+    JSValue rv;
+    int i, n;
+    double a, val;
+
+    if (JS_IsUndefined(new_target)) {
+        /* invoked as function */
+        argc = 0;
+    }
+    n = argc;
+    if (n == 0) {
+        val = date_now();
+    } else if (n == 1) {
+        JSValue v, dv;
+        if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) {
+            JSObject *p = JS_VALUE_GET_OBJ(argv[0]);
+            if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data)) {
+                if (JS_ToFloat64(ctx, &val, p->u.object_data))
+                    return JS_EXCEPTION;
+                val = time_clip(val);
+                goto has_val;
+            }
+        }
+        v = JS_ToPrimitive(ctx, argv[0], HINT_NONE);
+        if (JS_IsString(v)) {
+            dv = js_Date_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&v);
+            JS_FreeValue(ctx, v);
+            if (JS_IsException(dv))
+                return JS_EXCEPTION;
+            if (JS_ToFloat64Free(ctx, &val, dv))
+                return JS_EXCEPTION;
+        } else {
+            if (JS_ToFloat64Free(ctx, &val, v))
+                return JS_EXCEPTION;
+        }
+        val = time_clip(val);
+    } else {
+        int64_t fields[] = { 0, 0, 1, 0, 0, 0, 0 };
+        if (n > 7)
+            n = 7;
+        for(i = 0; i < n; i++) {
+            if (JS_ToFloat64(ctx, &a, argv[i]))
+                return JS_EXCEPTION;
+            if (!isfinite(a))
+                break;
+            fields[i] = trunc(a);
+            if (i == 0 && fields[0] >= 0 && fields[0] < 100)
+                fields[0] += 1900;
+        }
+        val = (i == n) ? set_date_fields(fields, 1) : NAN;
+    }
+has_val:
+#if 0
+    JSValueConst args[3];
+    args[0] = new_target;
+    args[1] = ctx->class_proto[JS_CLASS_DATE];
+    args[2] = __JS_NewFloat64(ctx, val);
+    rv = js___date_create(ctx, JS_UNDEFINED, 3, args);
+#else
+    rv = js_create_from_ctor(ctx, new_target, JS_CLASS_DATE);
+    if (!JS_IsException(rv))
+        JS_SetObjectData(ctx, rv, __JS_NewFloat64(ctx, val));
+#endif
+    if (!JS_IsException(rv) && JS_IsUndefined(new_target)) {
+        /* invoked as a function, return (new Date()).toString(); */
+        JSValue s;
+        s = get_date_string(ctx, rv, 0, NULL, 0x13);
+        JS_FreeValue(ctx, rv);
+        rv = s;
+    }
+    return rv;
+}
+
+static JSValue js_Date_UTC(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    // UTC(y, mon, d, h, m, s, ms)
+    int64_t fields[] = { 0, 0, 1, 0, 0, 0, 0 };
+    int i, n;
+    double a;
+
+    n = argc;
+    if (n == 0)
+        return JS_NAN;
+    if (n > 7)
+        n = 7;
+    for(i = 0; i < n; i++) {
+        if (JS_ToFloat64(ctx, &a, argv[i]))
+            return JS_EXCEPTION;
+        if (!isfinite(a))
+            return JS_NAN;
+        fields[i] = trunc(a);
+        if (i == 0 && fields[0] >= 0 && fields[0] < 100)
+            fields[0] += 1900;
+    }
+    return __JS_NewFloat64(ctx, set_date_fields(fields, 0));
+}
+
+static void string_skip_spaces(JSString *sp, int *pp) {
+    while (*pp < sp->len && string_get(sp, *pp) == ' ')
+        *pp += 1;
+}
+
+static void string_skip_non_spaces(JSString *sp, int *pp) {
+    while (*pp < sp->len && string_get(sp, *pp) != ' ')
+        *pp += 1;
+}
+
+/* parse a numeric field */
+static int string_get_field(JSString *sp, int *pp, int64_t *pval) {
+    int64_t v = 0;
+    int c, p = *pp;
+
+    /* skip non digits, should only skip spaces? */
+    while (p < sp->len) {
+        c = string_get(sp, p);
+        if (c >= '0' && c <= '9')
+            break;
+        p++;
+    }
+    if (p >= sp->len)
+        return -1;
+    while (p < sp->len) {
+        c = string_get(sp, p);
+        if (!(c >= '0' && c <= '9'))
+            break;
+        v = v * 10 + c - '0';
+        p++;
+    }
+    *pval = v;
+    *pp = p;
+    return 0;
+}
+
+/* parse a fixed width numeric field */
+static int string_get_digits(JSString *sp, int *pp, int n, int64_t *pval) {
+    int64_t v = 0;
+    int i, c, p = *pp;
+
+    for(i = 0; i < n; i++) {
+        if (p >= sp->len)
+            return -1;
+        c = string_get(sp, p);
+        if (!(c >= '0' && c <= '9'))
+            return -1;
+        v = v * 10 + c - '0';
+        p++;
+    }
+    *pval = v;
+    *pp = p;
+    return 0;
+}
+
+/* parse a signed numeric field */
+static int string_get_signed_field(JSString *sp, int *pp, int64_t *pval) {
+    int sgn, res;
+
+    if (*pp >= sp->len)
+        return -1;
+
+    sgn = string_get(sp, *pp);
+    if (sgn == '-' || sgn == '+')
+        *pp += 1;
+
+    res = string_get_field(sp, pp, pval);
+    if (res == 0 && sgn == '-')
+        *pval = -*pval;
+    return res;
+}
+
+static int find_abbrev(JSString *sp, int p, const char *list, int count) {
+    int n, i;
+
+    if (p + 3 <= sp->len) {
+        for (n = 0; n < count; n++) {
+            for (i = 0; i < 3; i++) {
+                if (string_get(sp, p + i) != month_names[n * 3 + i])
+                    goto next;
+            }
+            return n;
+        next:;
+        }
+    }
+    return -1;
+}
+
+static int string_get_month(JSString *sp, int *pp, int64_t *pval) {
+    int n;
+
+    string_skip_spaces(sp, pp);
+    n = find_abbrev(sp, *pp, month_names, 12);
+    if (n < 0)
+        return -1;
+
+    *pval = n;
+    *pp += 3;
+    return 0;
+}
+
+static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv)
+{
+    // parse(s)
+    JSValue s, rv;
+    int64_t fields[] = { 0, 1, 1, 0, 0, 0, 0 };
+    int64_t tz, hh, mm;
+    double d;
+    int p, i, c, sgn;
+    JSString *sp;
+
+    rv = JS_NAN;
+
+    s = JS_ToString(ctx, argv[0]);
+    if (JS_IsException(s))
+        return JS_EXCEPTION;
+    
+    sp = JS_VALUE_GET_STRING(s);
+    p = 0;
+    if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) {
+        /* ISO format */
+        /* year field can be negative */
+        /* XXX: could be stricter */
+        if (string_get_signed_field(sp, &p, &fields[0]))
+            goto done;
+
+        for (i = 1; i < 6; i++) {
+            if (string_get_field(sp, &p, &fields[i]))
+                break;
+        }
+        if (i == 6 && p < sp->len && string_get(sp, p) == '.') {
+            /* parse milliseconds as a fractional part, round to nearest */
+            /* XXX: the spec does not indicate which rounding should be used */
+            int mul = 1000, ms = 0;
+            while (++p < sp->len) {
+                int c = string_get(sp, p);
+                if (!(c >= '0' && c <= '9'))
+                    break;
+                if (mul == 1 && c >= '5')
+                    ms += 1;
+                ms += (c - '0') * (mul /= 10);
+            }
+            fields[6] = ms;
+        }
+        fields[1] -= 1;
+
+        /* parse the time zone offset if present: [+-]HH:mm */
+        tz = 0;
+        if (p < sp->len && ((sgn = string_get(sp, p)) == '+' || sgn == '-')) {
+            if (string_get_field(sp, &p, &hh))
+                goto done;
+            if (string_get_field(sp, &p, &mm))
+                goto done;
+            tz = hh * 60 + mm;
+            if (sgn == '-')
+                tz = -tz;
+        }
+    } else {
+        /* toString or toUTCString format */
+        /* skip the day of the week */
+        string_skip_non_spaces(sp, &p);
+        string_skip_spaces(sp, &p);
+        if (p >= sp->len)
+            goto done;
+        c = string_get(sp, p);
+        if (c >= '0' && c <= '9') {
+            /* day of month first */
+            if (string_get_field(sp, &p, &fields[2]))
+                goto done;
+            if (string_get_month(sp, &p, &fields[1]))
+                goto done;
+        } else {
+            /* month first */
+            if (string_get_month(sp, &p, &fields[1]))
+                goto done;
+            if (string_get_field(sp, &p, &fields[2]))
+                goto done;
+        }
+        string_skip_spaces(sp, &p);
+        if (string_get_signed_field(sp, &p, &fields[0]))
+            goto done;
+
+        /* hour, min, seconds */
+        for(i = 0; i < 3; i++) {
+            if (string_get_field(sp, &p, &fields[3 + i]))
+                goto done;
+        }
+        // XXX: parse optional milliseconds?
+
+        /* parse the time zone offset if present: [+-]HHmm */
+        tz = 0;
+        for (tz = 0; p < sp->len; p++) {
+            sgn = string_get(sp, p);
+            if (sgn == '+' || sgn == '-') {
+                p++;
+                if (string_get_digits(sp, &p, 2, &hh))
+                    goto done;
+                if (string_get_digits(sp, &p, 2, &mm))
+                    goto done;
+                tz = hh * 60 + mm;
+                if (sgn == '-')
+                    tz = -tz;
+                break;
+            }
+        }
+    }
+    d = set_date_fields(fields, 0) - tz * 60000;
+    rv = __JS_NewFloat64(ctx, d);
+
+done:
+    JS_FreeValue(ctx, s);
+    return rv;
+}
+
+static JSValue js_Date_now(JSContext *ctx, JSValueConst this_val,
+                           int argc, JSValueConst *argv)
+{
+    // now()
+    return JS_NewInt64(ctx, date_now());
+}
+
+static JSValue js_date_Symbol_toPrimitive(JSContext *ctx, JSValueConst this_val,
+                                          int argc, JSValueConst *argv)
+{
+    // Symbol_toPrimitive(hint)
+    JSValueConst obj = this_val;
+    JSAtom hint = JS_ATOM_NULL;
+    int hint_num;
+
+    if (!JS_IsObject(obj))
+        return JS_ThrowTypeErrorNotAnObject(ctx);
+
+    if (JS_IsString(argv[0])) {
+        hint = JS_ValueToAtom(ctx, argv[0]);
+        if (hint == JS_ATOM_NULL)
+            return JS_EXCEPTION;
+        JS_FreeAtom(ctx, hint);
+    }
+    switch (hint) {
+    case JS_ATOM_number:
+#ifdef CONFIG_BIGNUM
+    case JS_ATOM_integer:
+#endif
+        hint_num = HINT_NUMBER;
+        break;
+    case JS_ATOM_string:
+    case JS_ATOM_default:
+        hint_num = HINT_STRING;
+        break;
+    default:
+        return JS_ThrowTypeError(ctx, "invalid hint");
+    }
+    return JS_ToPrimitive(ctx, obj, hint_num | HINT_FORCE_ORDINARY);
+}
+
+static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    // getTimezoneOffset()
+    double v;
+
+    if (JS_ThisTimeValue(ctx, &v, this_val))
+        return JS_EXCEPTION;
+    if (isnan(v))
+        return JS_NAN;
+    else
+        return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v)));
+}
+
+static JSValue js_date_getTime(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    // getTime()
+    double v;
+
+    if (JS_ThisTimeValue(ctx, &v, this_val))
+        return JS_EXCEPTION;
+    return __JS_NewFloat64(ctx, v);
+}
+
+static JSValue js_date_setTime(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    // setTime(v)
+    double v;
+
+    if (JS_ThisTimeValue(ctx, &v, this_val) || JS_ToFloat64(ctx, &v, argv[0]))
+        return JS_EXCEPTION;
+    return JS_SetThisTimeValue(ctx, this_val, time_clip(v));
+}
+
+static JSValue js_date_setYear(JSContext *ctx, JSValueConst this_val,
+                               int argc, JSValueConst *argv)
+{
+    // setYear(y)
+    double y;
+    JSValueConst args[1];
+
+    if (JS_ThisTimeValue(ctx, &y, this_val) || JS_ToFloat64(ctx, &y, argv[0]))
+        return JS_EXCEPTION;
+    y = +y;
+    if (isfinite(y)) {
+        y = trunc(y);
+        if (y >= 0 && y < 100)
+            y += 1900;
+    }
+    args[0] = __JS_NewFloat64(ctx, y);
+    return set_date_field(ctx, this_val, 1, args, 0x011);
+}
+
+static JSValue js_date_toJSON(JSContext *ctx, JSValueConst this_val,
+                              int argc, JSValueConst *argv)
+{
+    // toJSON(key)
+    JSValue obj, tv, method, rv;
+    double d;
+
+    rv = JS_EXCEPTION;
+    tv = JS_UNDEFINED;
+
+    obj = JS_ToObject(ctx, this_val);
+    tv = JS_ToPrimitive(ctx, obj, HINT_NUMBER);
+    if (JS_IsException(tv))
+        goto exception;
+    if (JS_IsNumber(tv)) {
+        if (JS_ToFloat64(ctx, &d, tv) < 0)
+            goto exception;
+        if (!isfinite(d)) {
+            rv = JS_NULL;
+            goto done;
+        }
+    }
+    method = JS_GetPropertyStr(ctx, obj, "toISOString");
+    if (JS_IsException(method))
+        goto exception;
+    if (!JS_IsFunction(ctx, method)) {
+        JS_ThrowTypeError(ctx, "object needs toISOString method");
+        JS_FreeValue(ctx, method);
+        goto exception;
+    }
+    rv = JS_CallFree(ctx, method, obj, 0, NULL);
+exception:
+done:
+    JS_FreeValue(ctx, obj);
+    JS_FreeValue(ctx, tv);
+    return rv;
+}
+
+static const JSCFunctionListEntry js_date_funcs[] = {
+    JS_CFUNC_DEF("now", 0, js_Date_now ),
+    JS_CFUNC_DEF("parse", 1, js_Date_parse ),
+    JS_CFUNC_DEF("UTC", 7, js_Date_UTC ),
+};
+
+static const JSCFunctionListEntry js_date_proto_funcs[] = {
+    JS_CFUNC_DEF("valueOf", 0, js_date_getTime ),
+    JS_CFUNC_MAGIC_DEF("toString", 0, get_date_string, 0x13 ),
+    JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_date_Symbol_toPrimitive ),
+    JS_CFUNC_MAGIC_DEF("toUTCString", 0, get_date_string, 0x03 ),
+    JS_ALIAS_DEF("toGMTString", "toUTCString" ),
+    JS_CFUNC_MAGIC_DEF("toISOString", 0, get_date_string, 0x23 ),
+    JS_CFUNC_MAGIC_DEF("toDateString", 0, get_date_string, 0x11 ),
+    JS_CFUNC_MAGIC_DEF("toTimeString", 0, get_date_string, 0x12 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleString", 0, get_date_string, 0x33 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleDateString", 0, get_date_string, 0x31 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleTimeString", 0, get_date_string, 0x32 ),
+    JS_CFUNC_DEF("getTimezoneOffset", 0, js_date_getTimezoneOffset ),
+    JS_CFUNC_DEF("getTime", 0, js_date_getTime ),
+    JS_CFUNC_MAGIC_DEF("getYear", 0, get_date_field, 0x101 ),
+    JS_CFUNC_MAGIC_DEF("getFullYear", 0, get_date_field, 0x01 ),
+    JS_CFUNC_MAGIC_DEF("getUTCFullYear", 0, get_date_field, 0x00 ),
+    JS_CFUNC_MAGIC_DEF("getMonth", 0, get_date_field, 0x11 ),
+    JS_CFUNC_MAGIC_DEF("getUTCMonth", 0, get_date_field, 0x10 ),
+    JS_CFUNC_MAGIC_DEF("getDate", 0, get_date_field, 0x21 ),
+    JS_CFUNC_MAGIC_DEF("getUTCDate", 0, get_date_field, 0x20 ),
+    JS_CFUNC_MAGIC_DEF("getHours", 0, get_date_field, 0x31 ),
+    JS_CFUNC_MAGIC_DEF("getUTCHours", 0, get_date_field, 0x30 ),
+    JS_CFUNC_MAGIC_DEF("getMinutes", 0, get_date_field, 0x41 ),
+    JS_CFUNC_MAGIC_DEF("getUTCMinutes", 0, get_date_field, 0x40 ),
+    JS_CFUNC_MAGIC_DEF("getSeconds", 0, get_date_field, 0x51 ),
+    JS_CFUNC_MAGIC_DEF("getUTCSeconds", 0, get_date_field, 0x50 ),
+    JS_CFUNC_MAGIC_DEF("getMilliseconds", 0, get_date_field, 0x61 ),
+    JS_CFUNC_MAGIC_DEF("getUTCMilliseconds", 0, get_date_field, 0x60 ),
+    JS_CFUNC_MAGIC_DEF("getDay", 0, get_date_field, 0x71 ),
+    JS_CFUNC_MAGIC_DEF("getUTCDay", 0, get_date_field, 0x70 ),
+    JS_CFUNC_DEF("setTime", 1, js_date_setTime ),
+    JS_CFUNC_MAGIC_DEF("setMilliseconds", 1, set_date_field, 0x671 ),
+    JS_CFUNC_MAGIC_DEF("setUTCMilliseconds", 1, set_date_field, 0x670 ),
+    JS_CFUNC_MAGIC_DEF("setSeconds", 2, set_date_field, 0x571 ),
+    JS_CFUNC_MAGIC_DEF("setUTCSeconds", 2, set_date_field, 0x570 ),
+    JS_CFUNC_MAGIC_DEF("setMinutes", 3, set_date_field, 0x471 ),
+    JS_CFUNC_MAGIC_DEF("setUTCMinutes", 3, set_date_field, 0x470 ),
+    JS_CFUNC_MAGIC_DEF("setHours", 4, set_date_field, 0x371 ),
+    JS_CFUNC_MAGIC_DEF("setUTCHours", 4, set_date_field, 0x370 ),
+    JS_CFUNC_MAGIC_DEF("setDate", 1, set_date_field, 0x231 ),
+    JS_CFUNC_MAGIC_DEF("setUTCDate", 1, set_date_field, 0x230 ),
+    JS_CFUNC_MAGIC_DEF("setMonth", 2, set_date_field, 0x131 ),
+    JS_CFUNC_MAGIC_DEF("setUTCMonth", 2, set_date_field, 0x130 ),
+    JS_CFUNC_DEF("setYear", 1, js_date_setYear ),
+    JS_CFUNC_MAGIC_DEF("setFullYear", 3, set_date_field, 0x031 ),
+    JS_CFUNC_MAGIC_DEF("setUTCFullYear", 3, set_date_field, 0x030 ),
+    JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ),
+};
+
+void JS_AddIntrinsicDate(JSContext *ctx)
+{
+    JSValueConst obj;
+
+    /* Date */
+    ctx->class_proto[JS_CLASS_DATE] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATE], js_date_proto_funcs,
+                               countof(js_date_proto_funcs));
+    obj = JS_NewGlobalCConstructor(ctx, "Date", js_date_constructor, 7,
+                                   ctx->class_proto[JS_CLASS_DATE]);
+    JS_SetPropertyFunctionList(ctx, obj, js_date_funcs, countof(js_date_funcs));
+}
+
+/* eval */
+
+void JS_AddIntrinsicEval(JSContext *ctx)
+{
+    ctx->eval_internal = __JS_EvalInternal;
+}
+
+#ifdef CONFIG_BIGNUM
+
+static JSValue JS_ToBigIntCtorFree(JSContext *ctx, JSValue val)
+{
+    uint32_t tag;
+    BOOL is_legacy;
+    int ret;
+
+    is_legacy = is_bignum_mode(ctx) ^ 1;
+ redo:
+    tag = JS_VALUE_GET_NORM_TAG(val);
+    switch(tag) {
+    case JS_TAG_INT:
+    case JS_TAG_BOOL:
+        if (is_legacy) {
+            bf_t r_s, *r = &r_s;
+            bf_init(ctx->bf_ctx, r);
+            bf_set_si(r, JS_VALUE_GET_INT(val));
+            val = JS_NewBigInt2(ctx, r, TRUE);
+        } else {
+            val = JS_NewInt32(ctx, JS_VALUE_GET_INT(val));
+        }
+        break;
+    case JS_TAG_BIG_INT:
+        break;
+    case JS_TAG_FLOAT64:
+    case JS_TAG_BIG_FLOAT:
+        {
+            bf_t *a, a_s, r_s, *r = &r_s;
+            BOOL is_float;
+            a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
+            bf_init(ctx->bf_ctx, r);
+            if (!bf_is_finite(a)) {
+                JS_FreeValue(ctx, val);
+                val = JS_ThrowRangeError(ctx, "cannot convert NaN or Infinity to bigint");
+            } else {
+                bf_set(r, a);
+                ret = bf_rint(r, BF_PREC_INF, BF_RNDZ);
+                JS_FreeValue(ctx, val);
+                if (is_legacy && (ret & BF_ST_INEXACT)) {
+                    bf_delete(r);
+                    val = JS_ThrowRangeError(ctx, "cannot convert to bigint: not an integer");
+                } else {
+                    val = JS_NewBigInt2(ctx, r, is_legacy);
+                }
+            }
+            if (a == &a_s)
+                bf_delete(a);
+        }
+        break;
+    case JS_TAG_STRING:
+        val = JS_StringToBigIntErr(ctx, val);
+        if (JS_IsException(val))
+            break;
+        goto redo;
+    case JS_TAG_OBJECT:
+        val = JS_ToPrimitiveFree(ctx, val,
+                                 is_legacy ? HINT_NUMBER : HINT_INTEGER);
+        if (JS_IsException(val))
+            break;
+        goto redo;
+    case JS_TAG_NULL:
+    case JS_TAG_UNDEFINED:
+    default:
+        JS_FreeValue(ctx, val);
+        return JS_ThrowTypeError(ctx, "cannot convert to bigint");
+    }
+    return val;
+}
+
+static JSValue js_bigint_constructor(JSContext *ctx,
+                                     JSValueConst this_val,
+                                     int argc, JSValueConst *argv)
+{
+    return JS_ToBigIntCtorFree(ctx, JS_DupValue(ctx, argv[0]));
+}
+
+static inline BOOL JS_IsBigInt(JSContext *ctx, JSValueConst v)
+{
+    int tag = JS_VALUE_GET_TAG(v);
+    if (tag == JS_TAG_BIG_INT)
+        return TRUE;
+    if (is_bignum_mode(ctx))
+        return tag == JS_TAG_INT;
+    else
+        return FALSE;
+}
+
+static JSValue js_thisBigIntValue(JSContext *ctx, JSValueConst this_val)
+{
+    if (JS_IsBigInt(ctx, this_val))
+        return JS_DupValue(ctx, this_val);
+
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_BIG_INT) {
+            /* XXX: may relax the check in case the object comes from
+               bignum mode */
+            if (JS_IsBigInt(ctx, p->u.object_data))
+                return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a bigint");
+}
+
+static JSValue js_bigint_toString(JSContext *ctx, JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValue val;
+    int base;
+    JSValue ret;
+
+    val = js_thisBigIntValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (argc == 0 || JS_IsUndefined(argv[0])) {
+        base = 10;
+    } else {
+        if (JS_ToInt32Sat(ctx, &base, argv[0]))
+            goto fail;
+        if (base < 2 || base > 36) {
+            JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
+            goto fail;
+        }
+    }
+    ret = js_ftoa(ctx, val, base, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN);
+    JS_FreeValue(ctx, val);
+    return ret;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_bigint_valueOf(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    return js_thisBigIntValue(ctx, this_val);
+}
+
+static JSValue js_integer_div(JSContext *ctx,
+                              JSValueConst this_val,
+                              int argc, JSValueConst *argv, int magic)
+{
+    bf_t a_s, b_s, *a, *b, r_s, *r = &r_s, q_s, *q = &q_s;
+    int status;
+
+    b = NULL;
+    a = JS_ToBigInt(ctx, &a_s, argv[0]);
+    if (!a)
+        return JS_EXCEPTION;
+    b = JS_ToBigInt(ctx, &b_s, argv[1]);
+    if (!b) {
+        JS_FreeBigInt(ctx, a, &a_s);
+        return JS_EXCEPTION;
+    }
+    bf_init(ctx->bf_ctx, q);
+    bf_init(ctx->bf_ctx, r);
+    status = bf_divrem(q, r, a, b, BF_PREC_INF, BF_RNDZ, magic & 0xf);
+    JS_FreeBigInt(ctx, a, &a_s);
+    JS_FreeBigInt(ctx, b, &b_s);
+    if (unlikely(status)) {
+        bf_delete(q);
+        bf_delete(r);
+        throw_bf_exception(ctx, status);
+        return JS_EXCEPTION;
+    }
+    if (magic & 0x10) {
+        JSValue ret;
+        /* XXX: handle exceptions */
+        ret = JS_NewArray(ctx);
+        JS_SetPropertyUint32(ctx, ret, 0, JS_NewBigInt(ctx, q));
+        JS_SetPropertyUint32(ctx, ret, 1, JS_NewBigInt(ctx, r));
+        return ret;
+    } else {
+        bf_delete(r);
+        return JS_NewBigInt(ctx, q);
+    }
+}
+
+static JSValue js_integer_sqrt(JSContext *ctx,
+                               JSValueConst this_val,
+                               int argc, JSValueConst *argv, int magic)
+{
+    bf_t a_s, *a, r_s, *r = &r_s, rem_s, *rem = &rem_s;
+    int status;
+
+    a = JS_ToBigInt(ctx, &a_s, argv[0]);
+    if (!a)
+        return JS_EXCEPTION;
+    bf_init(ctx->bf_ctx, r);
+    bf_init(ctx->bf_ctx, rem);
+    status = bf_sqrtrem(r, rem, a);
+    JS_FreeBigInt(ctx, a, &a_s);
+    if (unlikely(status & ~BF_ST_INEXACT)) {
+        bf_delete(r);
+        bf_delete(rem);
+        return throw_bf_exception(ctx, status);
+    }
+    if (magic) {
+        JSValue ret;
+        /* XXX: handle exceptions */
+        ret = JS_NewArray(ctx);
+        JS_SetPropertyUint32(ctx, ret, 0, JS_NewBigInt(ctx, r));
+        JS_SetPropertyUint32(ctx, ret, 1, JS_NewBigInt(ctx, rem));
+        return ret;
+    } else {
+        bf_delete(rem);
+        return JS_NewBigInt(ctx, r);
+    }
+}
+
+static JSValue js_integer_op1(JSContext *ctx,
+                              JSValueConst this_val,
+                              int argc, JSValueConst *argv,
+                              int magic)
+{
+    bf_t a_s, *a;
+    int64_t res;
+
+    a = JS_ToBigInt(ctx, &a_s, argv[0]);
+    if (!a)
+        return JS_EXCEPTION;
+    switch(magic) {
+    case 0: /* floorLog2 */
+        if (a->sign || a->expn <= 0) {
+            res = -1;
+        } else {
+            res = a->expn - 1;
+        }
+        break;
+    case 1: /* ctz */
+        if (bf_is_zero(a)) {
+            res = -1;
+        } else {
+            res = bf_get_exp_min(a);
+        }
+        break;
+    default:
+        abort();
+    }
+    JS_FreeBigInt(ctx, a, &a_s);
+    return JS_NewInt64(ctx, res);
+}
+
+static JSValue js_integer_asUintN(JSContext *ctx,
+                                  JSValueConst this_val,
+                                  int argc, JSValueConst *argv, int asIntN)
+{
+    uint64_t bits;
+    bf_t a_s, *a = &a_s, r_s, *r = &r_s, mask_s, *mask = &mask_s;
+    BOOL is_legacy;
+
+    if (JS_ToIndex(ctx, &bits, argv[0]))
+        return JS_EXCEPTION;
+    a = JS_ToBigInt(ctx, &a_s, argv[1]);
+    if (!a)
+        return JS_EXCEPTION;
+    /* XXX: optimize */
+    bf_init(ctx->bf_ctx, r);
+    bf_init(ctx->bf_ctx, mask);
+    bf_set_ui(mask, 1);
+    bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ);
+    bf_add_si(mask, mask, -1, BF_PREC_INF, BF_RNDZ);
+    bf_logic_and(r, a, mask);
+    if (asIntN && bits != 0) {
+        bf_set_ui(mask, 1);
+        bf_mul_2exp(mask, bits - 1, BF_PREC_INF, BF_RNDZ);
+        if (bf_cmpu(r, mask) >= 0) {
+            bf_set_ui(mask, 1);
+            bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ);
+            bf_sub(r, r, mask, BF_PREC_INF, BF_RNDZ);
+        }
+    }
+    bf_delete(mask);
+    JS_FreeBigInt(ctx, a, &a_s);
+    is_legacy = is_bignum_mode(ctx) ^ 1;
+    return JS_NewBigInt2(ctx, r, is_legacy);
+}
+
+static const JSCFunctionListEntry js_integer_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("tdiv", 2, js_integer_div, BF_RNDZ ),
+    JS_CFUNC_MAGIC_DEF("fdiv", 2, js_integer_div, BF_RNDD ),
+    JS_CFUNC_MAGIC_DEF("cdiv", 2, js_integer_div, BF_RNDU ),
+    JS_CFUNC_MAGIC_DEF("ediv", 2, js_integer_div, BF_DIVREM_EUCLIDIAN ),
+    JS_CFUNC_MAGIC_DEF("tdivrem", 2, js_integer_div, BF_RNDZ | 0x10 ),
+    JS_CFUNC_MAGIC_DEF("fdivrem", 2, js_integer_div, BF_RNDD | 0x10 ),
+    JS_CFUNC_MAGIC_DEF("cdivrem", 2, js_integer_div, BF_RNDU | 0x10 ),
+    JS_CFUNC_MAGIC_DEF("edivrem", 2, js_integer_div, BF_DIVREM_EUCLIDIAN | 0x10 ),
+    JS_CFUNC_MAGIC_DEF("sqrt", 1, js_integer_sqrt, 0 ),
+    JS_CFUNC_MAGIC_DEF("sqrtrem", 1, js_integer_sqrt, 1 ),
+    JS_CFUNC_MAGIC_DEF("floorLog2", 1, js_integer_op1, 0 ),
+    JS_CFUNC_MAGIC_DEF("ctz", 1, js_integer_op1, 1 ),
+    JS_CFUNC_MAGIC_DEF("asUintN", 2, js_integer_asUintN, 0 ),
+    JS_CFUNC_MAGIC_DEF("asIntN", 2, js_integer_asUintN, 1 ),
+};
+
+static const JSCFunctionListEntry js_bigint_proto_funcs[] = {
+    JS_CFUNC_DEF("toString", 0, js_bigint_toString ),
+    JS_CFUNC_DEF("valueOf", 0, js_bigint_valueOf ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "BigInt", JS_PROP_CONFIGURABLE ),
+};
+
+static JSValue js_thisBigFloatValue(JSContext *ctx, JSValueConst this_val)
+{
+    if (JS_IsBigFloat(this_val))
+        return JS_DupValue(ctx, this_val);
+
+    if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
+        JSObject *p = JS_VALUE_GET_OBJ(this_val);
+        if (p->class_id == JS_CLASS_BIG_FLOAT) {
+            if (JS_IsBigFloat(p->u.object_data))
+                return JS_DupValue(ctx, p->u.object_data);
+        }
+    }
+    return JS_ThrowTypeError(ctx, "not a bigfloat");
+}
+
+static JSValue js_bigfloat_toString(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSValue val;
+    int base;
+    JSValue ret;
+
+    val = js_thisBigFloatValue(ctx, this_val);
+    if (JS_IsException(val))
+        return val;
+    if (argc == 0 || JS_IsUndefined(argv[0])) {
+        base = 10;
+    } else {
+        if (JS_ToInt32Sat(ctx, &base, argv[0]))
+            goto fail;
+        if (base < 2 || base > 36) {
+            JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
+            goto fail;
+        }
+    }
+    ret = js_ftoa(ctx, val, base, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN);
+    JS_FreeValue(ctx, val);
+    return ret;
+ fail:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_bigfloat_valueOf(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    return js_thisBigFloatValue(ctx, this_val);
+}
+
+static const JSCFunctionListEntry js_bigfloat_proto_funcs[] = {
+    JS_CFUNC_DEF("toString", 0, js_bigfloat_toString ),
+    JS_CFUNC_DEF("valueOf", 0, js_bigfloat_valueOf ),
+    JS_CFUNC_MAGIC_DEF("toPrecision", 1, js_number_toPrecision, 1 ),
+    JS_CFUNC_MAGIC_DEF("toFixed", 1, js_number_toFixed, 1 ),
+    JS_CFUNC_MAGIC_DEF("toExponential", 1, js_number_toExponential, 1 ),
+};
+
+static JSValue js_bigfloat_constructor(JSContext *ctx,
+                                       JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSValue val;
+    if (argc == 0) {
+        bf_t r_s, *r = &r_s;
+        bf_init(ctx->bf_ctx, r);
+        bf_set_zero(r, 0);
+        val = JS_NewBigFloat(ctx, r);
+    } else {
+        val = JS_DupValue(ctx, argv[0]);
+    redo:
+        switch(JS_VALUE_GET_NORM_TAG(val)) {
+        case JS_TAG_BIG_FLOAT:
+            break;
+        case JS_TAG_FLOAT64:
+            {
+                bf_t r_s, *r = &r_s;
+                bf_init(ctx->bf_ctx, r);
+                bf_set_float64(r, JS_VALUE_GET_FLOAT64(val));
+                val = JS_NewBigFloat(ctx, r);
+            }
+            break;
+        case JS_TAG_INT:
+            {
+                bf_t r_s, *r = &r_s;
+                bf_init(ctx->bf_ctx, r);
+                bf_set_si(r, JS_VALUE_GET_INT(val));
+                val = JS_NewBigFloat(ctx, r);
+            }
+            break;
+        case JS_TAG_BIG_INT:
+            /* We keep the full precision of the integer */
+            {
+                JSBigFloat *p = JS_VALUE_GET_PTR(val);
+                val = JS_MKPTR(JS_TAG_BIG_FLOAT, p);
+            }
+            break;
+        case JS_TAG_STRING:
+            {
+                const char *str, *p;
+                int err;
+
+                str = JS_ToCString(ctx, val);
+                JS_FreeValue(ctx, val);
+                if (!str)
+                    return JS_EXCEPTION;
+                val = js_atof(ctx, str, &p, 0, BF_ATOF_BIN_OCT |
+                              BF_ATOF_NO_PREFIX_AFTER_SIGN |
+                              BF_ATOF_JS_QUIRKS);
+                p += skip_spaces(p);
+                err = (*p != '\0');
+                JS_FreeCString(ctx, str);
+                if (err) {
+                    JS_FreeValue(ctx, val);
+                    return JS_ThrowSyntaxError(ctx, "invalid bigfloat literal");
+                }
+            }
+            break;
+        case JS_TAG_OBJECT:
+            val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER);
+            if (JS_IsException(val))
+                break;
+            goto redo;
+        case JS_TAG_NULL:
+        case JS_TAG_UNDEFINED:
+        default:
+            JS_FreeValue(ctx, val);
+            return JS_ThrowTypeError(ctx, "cannot convert to bigfloat");
+        }
+    }
+    return val;
+}
+
+static JSValue js_float_get_const(JSContext *ctx,
+                                  JSValueConst this_val, int magic)
+{
+    bf_t r_s, *r = &r_s;
+    bf_init(ctx->bf_ctx, r);
+    switch(magic) {
+    case 0: /* PI */
+        bf_const_pi(r, ctx->fp_env.prec, ctx->fp_env.flags);
+        break;
+    case 1: /* LN2 */
+        bf_const_log2(r, ctx->fp_env.prec, ctx->fp_env.flags);
+        break;
+    case 2: /* MIN_VALUE */
+    case 3: /* MAX_VALUE */
+        {
+            slimb_t e_range, e;
+            e_range = (limb_t)1 << (bf_get_exp_bits(ctx->fp_env.flags) - 1);
+            bf_set_ui(r, 1);
+            if (magic == 2) {
+                e = -e_range + 2;
+                if (ctx->fp_env.flags & BF_FLAG_SUBNORMAL)
+                    e -= ctx->fp_env.prec - 1;
+                bf_mul_2exp(r, e, ctx->fp_env.prec, ctx->fp_env.flags);
+            } else {
+                bf_mul_2exp(r, ctx->fp_env.prec, ctx->fp_env.prec,
+                            ctx->fp_env.flags);
+                bf_add_si(r, r, -1, ctx->fp_env.prec, ctx->fp_env.flags);
+                bf_mul_2exp(r, e_range - ctx->fp_env.prec, ctx->fp_env.prec,
+                            ctx->fp_env.flags);
+            }
+        }
+        break;
+    case 4: /* EPSILON */
+        bf_set_ui(r, 1);
+        bf_mul_2exp(r, 1 - ctx->fp_env.prec,
+                    ctx->fp_env.prec, ctx->fp_env.flags);
+        break;
+    default:
+        abort();
+    }
+    return JS_NewBigFloat(ctx, r);
+}
+
+static JSValue js_float_parseFloat(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    bf_t a_s, *a = &a_s;
+    const char *str;
+    JSValue ret;
+    int radix;
+    JSFloatEnv *fe;
+
+    str = JS_ToCString(ctx, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &radix, argv[1])) {
+    fail:
+        JS_FreeCString(ctx, str);
+        return JS_EXCEPTION;
+    }
+    if (radix != 0 && (radix < 2 || radix > 36)) {
+        JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
+        goto fail;
+    }
+    fe = &ctx->fp_env;
+    if (argc > 2) {
+        fe = JS_GetOpaque2(ctx, argv[2], JS_CLASS_FLOAT_ENV);
+        if (!fe)
+            goto fail;
+    }
+    bf_init(ctx->bf_ctx, a);
+    bf_atof(a, str, NULL, radix, fe->prec, BF_ATOF_JS_QUIRKS | fe->flags);
+    ret = JS_NewBigFloat(ctx, a);
+    JS_FreeCString(ctx, str);
+    return ret;
+}
+
+static const JSCFunctionListEntry js_bigfloat_funcs[] = {
+    JS_CGETSET_MAGIC_DEF("PI", js_float_get_const, NULL, 0 ),
+    JS_CGETSET_MAGIC_DEF("LN2", js_float_get_const, NULL, 1 ),
+    JS_CGETSET_MAGIC_DEF("MIN_VALUE", js_float_get_const, NULL, 2 ),
+    JS_CGETSET_MAGIC_DEF("MAX_VALUE", js_float_get_const, NULL, 3 ),
+    JS_CGETSET_MAGIC_DEF("EPSILON", js_float_get_const, NULL, 4 ),
+    JS_CFUNC_DEF("parseFloat", 1, js_float_parseFloat ),
+    JS_CFUNC_MAGIC_DEF("abs", 1, js_math_fop, MATH_OP_ABS ),
+    JS_CFUNC_MAGIC_DEF("fpRound", 1, js_math_fop, MATH_OP_FPROUND ),
+    JS_CFUNC_MAGIC_DEF("floor", 1, js_math_fop, MATH_OP_FLOOR ),
+    JS_CFUNC_MAGIC_DEF("ceil", 1, js_math_fop, MATH_OP_CEIL ),
+    JS_CFUNC_MAGIC_DEF("round", 1, js_math_fop, MATH_OP_ROUND ),
+    JS_CFUNC_MAGIC_DEF("trunc", 1, js_math_fop, MATH_OP_TRUNC ),
+    JS_CFUNC_MAGIC_DEF("sqrt", 1, js_math_fop, MATH_OP_SQRT ),
+    JS_CFUNC_MAGIC_DEF("acos", 1, js_math_fop, MATH_OP_ACOS ),
+    JS_CFUNC_MAGIC_DEF("asin", 1, js_math_fop, MATH_OP_ASIN ),
+    JS_CFUNC_MAGIC_DEF("atan", 1, js_math_fop, MATH_OP_ATAN ),
+    JS_CFUNC_MAGIC_DEF("atan2", 2, js_math_fop2, MATH_OP_ATAN2 ),
+    JS_CFUNC_MAGIC_DEF("cos", 1, js_math_fop, MATH_OP_COS ),
+    JS_CFUNC_MAGIC_DEF("exp", 1, js_math_fop, MATH_OP_EXP ),
+    JS_CFUNC_MAGIC_DEF("log", 1, js_math_fop, MATH_OP_LOG ),
+    JS_CFUNC_MAGIC_DEF("pow", 2, js_math_fop2, MATH_OP_POW ),
+    JS_CFUNC_MAGIC_DEF("sin", 1, js_math_fop, MATH_OP_SIN ),
+    JS_CFUNC_MAGIC_DEF("tan", 1, js_math_fop, MATH_OP_TAN ),
+    JS_CFUNC_MAGIC_DEF("sign", 1, js_math_fop, MATH_OP_SIGN ),
+    JS_CFUNC_MAGIC_DEF("add", 2, js_math_fop2, MATH_OP_ADD ),
+    JS_CFUNC_MAGIC_DEF("sub", 2, js_math_fop2, MATH_OP_SUB ),
+    JS_CFUNC_MAGIC_DEF("mul", 2, js_math_fop2, MATH_OP_MUL ),
+    JS_CFUNC_MAGIC_DEF("div", 2, js_math_fop2, MATH_OP_DIV ),
+    JS_CFUNC_MAGIC_DEF("fmod", 2, js_math_fop2, MATH_OP_FMOD ),
+    JS_CFUNC_MAGIC_DEF("remainder", 2, js_math_fop2, MATH_OP_REM ),
+};
+
+/* FloatEnv */
+
+static JSValue js_float_env_constructor(JSContext *ctx,
+                                        JSValueConst new_target,
+                                        int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    JSFloatEnv *fe;
+    int64_t prec;
+    int flags, rndmode;
+
+    prec = ctx->fp_env.prec;
+    flags = ctx->fp_env.flags;
+    if (!JS_IsUndefined(argv[0])) {
+        if (JS_ToInt64Sat(ctx, &prec, argv[0]))
+            return JS_EXCEPTION;
+        if (prec < BF_PREC_MIN || prec > BF_PREC_MAX)
+            return JS_ThrowRangeError(ctx, "invalid precision");
+        flags = BF_RNDN; /* RNDN, max exponent size, no subnormal */
+        if (argc > 1 && !JS_IsUndefined(argv[1])) {
+            if (JS_ToInt32Sat(ctx, &rndmode, argv[1]))
+                return JS_EXCEPTION;
+            if (rndmode < BF_RNDN || rndmode > BF_RNDF)
+                return JS_ThrowRangeError(ctx, "invalid rounding mode");
+            flags = rndmode;
+        }
+    }
+
+    obj = JS_NewObjectClass(ctx, JS_CLASS_FLOAT_ENV);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    fe = js_malloc(ctx, sizeof(*fe));
+    if (!fe)
+        return JS_EXCEPTION;
+    fe->prec = prec;
+    fe->flags = flags;
+    fe->status = 0;
+    JS_SetOpaque(obj, fe);
+    return obj;
+}
+
+static void js_float_env_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSFloatEnv *fe = JS_GetOpaque(val, JS_CLASS_FLOAT_ENV);
+    js_free_rt(rt, fe);
+}
+
+static JSValue js_float_env_get_prec(JSContext *ctx, JSValueConst this_val)
+{
+    return JS_NewInt64(ctx, ctx->fp_env.prec);
+}
+
+static JSValue js_float_env_get_expBits(JSContext *ctx, JSValueConst this_val)
+{
+    return JS_NewInt32(ctx, bf_get_exp_bits(ctx->fp_env.flags));
+}
+
+/* temporary fix for string conversion overflows */
+#define BF_EXP_BITS_MAX1 (BF_EXP_BITS_MAX - 1)
+
+static JSValue js_float_env_setPrec(JSContext *ctx,
+                                    JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSValueConst func;
+    int exp_bits, flags, saved_flags;
+    JSValue ret;
+    limb_t saved_prec;
+    int64_t prec;
+
+    func = argv[0];
+    if (JS_ToInt64Sat(ctx, &prec, argv[1]))
+        return JS_EXCEPTION;
+    if (prec < BF_PREC_MIN || prec > BF_PREC_MAX)
+        return JS_ThrowRangeError(ctx, "invalid precision");
+    exp_bits = BF_EXP_BITS_MAX1;
+
+    if (argc > 2 && !JS_IsUndefined(argv[2])) {
+        if (JS_ToInt32Sat(ctx, &exp_bits, argv[2]))
+            return JS_EXCEPTION;
+        if (exp_bits < BF_EXP_BITS_MIN || exp_bits > BF_EXP_BITS_MAX1)
+            return JS_ThrowRangeError(ctx, "invalid number of exponent bits");
+    }
+
+    flags = BF_RNDN | bf_set_exp_bits(exp_bits);
+    if (exp_bits != BF_EXP_BITS_MAX1)
+        flags |= BF_FLAG_SUBNORMAL;
+
+    saved_prec = ctx->fp_env.prec;
+    saved_flags = ctx->fp_env.flags;
+
+    ctx->fp_env.prec = prec;
+    ctx->fp_env.flags = flags;
+
+    ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL);
+    /* always restore the floating point precision */
+    ctx->fp_env.prec = saved_prec;
+    ctx->fp_env.flags = saved_flags;
+    return ret;
+}
+
+#define FE_PREC      (-1)
+#define FE_EXP       (-2)
+#define FE_RNDMODE   (-3)
+#define FE_SUBNORMAL (-4)
+
+static JSValue js_float_env_proto_get_status(JSContext *ctx, JSValueConst this_val, int magic)
+{
+    JSFloatEnv *fe;
+    fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV);
+    if (!fe)
+        return JS_EXCEPTION;
+    switch(magic) {
+    case FE_PREC:
+        return JS_NewInt64(ctx, fe->prec);
+    case FE_EXP:
+        return JS_NewInt32(ctx, bf_get_exp_bits(fe->flags));
+    case FE_RNDMODE:
+        return JS_NewInt32(ctx, fe->flags & BF_RND_MASK);
+    case FE_SUBNORMAL:
+        return JS_NewBool(ctx, (fe->flags & BF_FLAG_SUBNORMAL) != 0);
+    default:
+        return JS_NewBool(ctx, (fe->status & magic) != 0);
+    }
+}
+
+static JSValue js_float_env_proto_set_status(JSContext *ctx, JSValueConst this_val, JSValue val, int magic)
+{
+    JSFloatEnv *fe;
+    int b;
+    int64_t prec;
+
+    fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV);
+    if (!fe)
+        return JS_EXCEPTION;
+    switch(magic) {
+    case FE_PREC:
+        if (JS_ToInt64Sat(ctx, &prec, val))
+            return JS_EXCEPTION;
+        if (prec < BF_PREC_MIN || prec > BF_PREC_MAX)
+            return JS_ThrowRangeError(ctx, "invalid precision");
+        fe->prec = prec;
+        break;
+    case FE_EXP:
+        if (JS_ToInt32Sat(ctx, &b, val))
+            return JS_EXCEPTION;
+        if (b < BF_EXP_BITS_MIN || b > BF_EXP_BITS_MAX1)
+            return JS_ThrowRangeError(ctx, "invalid number of exponent bits");
+        if (b == BF_EXP_BITS_MAX1)
+            fe->flags &= ~BF_FLAG_SUBNORMAL;
+        fe->flags = (fe->flags & ~(BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT)) |
+            bf_set_exp_bits(b);
+        break;
+    case FE_RNDMODE:
+        b = get_rnd_mode(ctx, val);
+        if (b < 0)
+            return JS_EXCEPTION;
+        fe->flags = (fe->flags & ~BF_RND_MASK) | b;
+        break;
+    case FE_SUBNORMAL:
+        b = JS_ToBool(ctx, val);
+        if (bf_get_exp_bits(fe->flags) != BF_EXP_BITS_MAX1) {
+            fe->flags = (fe->flags & ~BF_FLAG_SUBNORMAL) | (b ? BF_FLAG_SUBNORMAL: 0);
+        }
+        break;
+    default:
+        b = JS_ToBool(ctx, val);
+        fe->status = (fe->status & ~magic) & ((-b) & magic);
+        break;
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_float_env_clearStatus(JSContext *ctx,
+                                        JSValueConst this_val,
+                                        int argc, JSValueConst *argv)
+{
+    JSFloatEnv *fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV);
+    if (!fe)
+        return JS_EXCEPTION;
+    fe->status = 0;
+    return JS_UNDEFINED;
+}
+
+static const JSCFunctionListEntry js_float_env_funcs[] = {
+    JS_CGETSET_DEF("prec", js_float_env_get_prec, NULL ),
+    JS_CGETSET_DEF("expBits", js_float_env_get_expBits, NULL ),
+    JS_CFUNC_DEF("setPrec", 2, js_float_env_setPrec ),
+    JS_PROP_INT32_DEF("RNDN", BF_RNDN, 0 ),
+    JS_PROP_INT32_DEF("RNDZ", BF_RNDZ, 0 ),
+    JS_PROP_INT32_DEF("RNDU", BF_RNDU, 0 ),
+    JS_PROP_INT32_DEF("RNDD", BF_RNDD, 0 ),
+    JS_PROP_INT32_DEF("RNDNA", BF_RNDNA, 0 ),
+    JS_PROP_INT32_DEF("RNDNU", BF_RNDNU, 0 ),
+    JS_PROP_INT32_DEF("RNDF", BF_RNDF, 0 ),
+    JS_PROP_INT32_DEF("precMin", BF_PREC_MIN, 0 ),
+    JS_PROP_INT64_DEF("precMax", BF_PREC_MAX, 0 ),
+    JS_PROP_INT32_DEF("expBitsMin", BF_EXP_BITS_MIN, 0 ),
+    JS_PROP_INT32_DEF("expBitsMax", BF_EXP_BITS_MAX1, 0 ),
+};
+
+static const JSCFunctionListEntry js_float_env_proto_funcs[] = {
+    JS_CGETSET_MAGIC_DEF("prec", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, FE_PREC ),
+    JS_CGETSET_MAGIC_DEF("expBits", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, FE_EXP ),
+    JS_CGETSET_MAGIC_DEF("rndMode", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, FE_RNDMODE ),
+    JS_CGETSET_MAGIC_DEF("subnormal", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, FE_SUBNORMAL ),
+    JS_CGETSET_MAGIC_DEF("invalidOperation", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, BF_ST_INVALID_OP ),
+    JS_CGETSET_MAGIC_DEF("divideByZero", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, BF_ST_DIVIDE_ZERO ),
+    JS_CGETSET_MAGIC_DEF("overflow", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, BF_ST_OVERFLOW ),
+    JS_CGETSET_MAGIC_DEF("underflow", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, BF_ST_UNDERFLOW ),
+    JS_CGETSET_MAGIC_DEF("inexact", js_float_env_proto_get_status,
+                         js_float_env_proto_set_status, BF_ST_INEXACT ),
+    JS_CFUNC_DEF("clearStatus", 0, js_float_env_clearStatus ),
+};
+
+#endif /* CONFIG_BIGNUM */
+
+static const char * const native_error_name[JS_NATIVE_ERROR_COUNT] = {
+    "EvalError", "RangeError", "ReferenceError",
+    "SyntaxError", "TypeError", "URIError",
+    "InternalError",
+};
+
+/* Minimum amount of objects to be able to compile code and display
+   error messages. No JSAtom should be allocated by this function. */
+static void JS_AddIntrinsicBasicObjects(JSContext *ctx)
+{
+    JSValue proto;
+    int i;
+
+    ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto(ctx, JS_NULL);
+    ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0,
+                                           JS_CFUNC_generic, 0,
+                                           ctx->class_proto[JS_CLASS_OBJECT]);
+    ctx->class_proto[JS_CLASS_BYTECODE_FUNCTION] = JS_DupValue(ctx, ctx->function_proto);
+    ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject(ctx);
+#if 0
+    /* these are auto-initialized from js_error_proto_funcs,
+       but delaying might be a problem */
+    JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ERROR], JS_ATOM_name,
+                           JS_AtomToString(ctx, JS_ATOM_Error),
+                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+    JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ERROR], JS_ATOM_message,
+                           JS_AtomToString(ctx, JS_ATOM_empty_string),
+                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+#endif
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ERROR],
+                               js_error_proto_funcs,
+                               countof(js_error_proto_funcs));
+
+    for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
+        proto = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ERROR]);
+        JS_DefinePropertyValue(ctx, proto, JS_ATOM_name,
+                               JS_NewAtomString(ctx, native_error_name[i]),
+                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+        JS_DefinePropertyValue(ctx, proto, JS_ATOM_message,
+                               JS_AtomToString(ctx, JS_ATOM_empty_string),
+                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+        ctx->native_error_proto[i] = proto;
+    }
+
+    /* the array prototype is an array */
+    ctx->class_proto[JS_CLASS_ARRAY] =
+        JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                               JS_CLASS_ARRAY);
+
+    ctx->array_shape = js_new_shape2(ctx, get_proto_obj(ctx->class_proto[JS_CLASS_ARRAY]),
+                                     JS_PROP_INITIAL_HASH_SIZE, 1);
+    add_shape_property(ctx, &ctx->array_shape, NULL,
+                       JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH);
+
+    /* XXX: could test it on first context creation to ensure that no
+       new atoms are created in JS_AddIntrinsicBasicObjects(). It is
+       necessary to avoid useless renumbering of atoms after
+       JS_EvalBinary() if it is done just after
+       JS_AddIntrinsicBasicObjects(). */
+    //    assert(ctx->rt->atom_count == JS_ATOM_END);
+}
+
+void JS_AddIntrinsicBaseObjects(JSContext *ctx)
+{
+    int i;
+    JSValueConst obj, number_obj;
+    JSValue obj1;
+
+    ctx->throw_type_error = JS_NewCFunction(ctx, js_throw_type_error, NULL, 0);
+
+    /* add caller and arguments properties to throw a TypeError */
+    obj1 = JS_NewCFunction(ctx, js_function_proto_caller, "get caller", 0);
+    JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_UNDEFINED,
+                      obj1, ctx->throw_type_error,
+                      JS_PROP_HAS_GET | JS_PROP_HAS_SET |
+                      JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE);
+    JS_FreeValue(ctx, obj1);
+    JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_arguments, JS_UNDEFINED,
+                      ctx->throw_type_error, ctx->throw_type_error,
+                      JS_PROP_HAS_GET | JS_PROP_HAS_SET |
+                      JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE);
+    JS_FreeValue(ctx, js_object_seal(ctx, JS_UNDEFINED, 1, (JSValueConst *)&ctx->throw_type_error, 1));
+
+    ctx->global_obj = JS_NewObject(ctx);
+    ctx->global_var_obj = JS_NewObjectProto(ctx, JS_NULL);
+
+    /* Object */
+    obj = JS_NewGlobalCConstructor(ctx, "Object", js_object_constructor, 1,
+                                   ctx->class_proto[JS_CLASS_OBJECT]);
+    JS_SetPropertyFunctionList(ctx, obj, js_object_funcs, countof(js_object_funcs));
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                               js_object_proto_funcs, countof(js_object_proto_funcs));
+
+    /* Function */
+    JS_SetPropertyFunctionList(ctx, ctx->function_proto, js_function_proto_funcs, countof(js_function_proto_funcs));
+    ctx->function_ctor = JS_NewCFunctionMagic(ctx, js_function_constructor,
+                                              "Function", 1, JS_CFUNC_constructor_or_func_magic,
+                                              JS_FUNC_NORMAL);
+    JS_NewGlobalCConstructor2(ctx, JS_DupValue(ctx, ctx->function_ctor), "Function",
+                              ctx->function_proto);
+
+    /* Error */
+    obj1 = JS_NewCFunctionMagic(ctx, js_error_constructor,
+                                "Error", 1, JS_CFUNC_constructor_or_func_magic, -1);
+    JS_NewGlobalCConstructor2(ctx, obj1,
+                              "Error", ctx->class_proto[JS_CLASS_ERROR]);
+
+    for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
+        JSValue func_obj = JS_NewCFunction3(ctx, (JSCFunction *)js_error_constructor,
+                                            native_error_name[i], 1,
+                                            JS_CFUNC_constructor_or_func_magic, i, obj1);
+        JS_NewGlobalCConstructor2(ctx, func_obj, native_error_name[i],
+                                  ctx->native_error_proto[i]);
+    }
+
+    /* Iterator prototype */
+    ctx->iterator_proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->iterator_proto,
+                               js_iterator_proto_funcs,
+                               countof(js_iterator_proto_funcs));
+
+    /* Array */
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY],
+                               js_array_proto_funcs,
+                               countof(js_array_proto_funcs));
+
+    obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1,
+                                   ctx->class_proto[JS_CLASS_ARRAY]);
+    JS_SetPropertyFunctionList(ctx, obj, js_array_funcs,
+                               countof(js_array_funcs));
+
+    /* XXX: create auto_initializer */
+    {
+        /* initialize Array.prototype[Symbol.unscopables] */
+        char const unscopables[] = "copyWithin" "\0" "entries" "\0" "fill" "\0" "find" "\0"
+            "findIndex" "\0" "flat" "\0" "flatMap" "\0" "includes" "\0" "keys" "\0" "values" "\0";
+        const char *p = unscopables;
+        obj1 = JS_NewObjectProto(ctx, JS_NULL);
+        for(p = unscopables; *p; p += strlen(p) + 1) {
+            JS_DefinePropertyValueStr(ctx, obj1, p, JS_TRUE, JS_PROP_C_W_E);
+        }
+        JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ARRAY],
+                               JS_ATOM_Symbol_unscopables, obj1,
+                               JS_PROP_CONFIGURABLE);
+    }
+
+    /* needed to initialize arguments[Symbol.iterator] */
+    ctx->array_proto_values =
+        JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values);
+
+    ctx->class_proto[JS_CLASS_ARRAY_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_ITERATOR],
+                               js_array_iterator_proto_funcs,
+                               countof(js_array_iterator_proto_funcs));
+
+    /* Number */
+    ctx->class_proto[JS_CLASS_NUMBER] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                                                               JS_CLASS_NUMBER);
+    JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_NUMBER], JS_NewInt32(ctx, 0));
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_NUMBER],
+                               js_number_proto_funcs,
+                               countof(js_number_proto_funcs));
+    number_obj = JS_NewGlobalCConstructor(ctx, "Number", js_number_constructor, 1,
+                                          ctx->class_proto[JS_CLASS_NUMBER]);
+    JS_SetPropertyFunctionList(ctx, number_obj, js_number_funcs, countof(js_number_funcs));
+
+    /* Boolean */
+    ctx->class_proto[JS_CLASS_BOOLEAN] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                                                                JS_CLASS_BOOLEAN);
+    JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], JS_NewBool(ctx, FALSE));
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], js_boolean_proto_funcs,
+                               countof(js_boolean_proto_funcs));
+    JS_NewGlobalCConstructor(ctx, "Boolean", js_boolean_constructor, 1,
+                             ctx->class_proto[JS_CLASS_BOOLEAN]);
+
+    /* String */
+    ctx->class_proto[JS_CLASS_STRING] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
+                                                               JS_CLASS_STRING);
+    JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_STRING], JS_AtomToString(ctx, JS_ATOM_empty_string));
+    obj = JS_NewGlobalCConstructor(ctx, "String", js_string_constructor, 1,
+                                   ctx->class_proto[JS_CLASS_STRING]);
+    JS_SetPropertyFunctionList(ctx, obj, js_string_funcs,
+                               countof(js_string_funcs));
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_funcs,
+                               countof(js_string_proto_funcs));
+
+    ctx->class_proto[JS_CLASS_STRING_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING_ITERATOR],
+                               js_string_iterator_proto_funcs,
+                               countof(js_string_iterator_proto_funcs));
+
+    /* Math: create as autoinit object */
+    js_random_init(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj));
+
+    /* ES6 Reflect: create as autoinit object */
+    JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj, countof(js_reflect_obj));
+
+    /* ES6 Symbol */
+    ctx->class_proto[JS_CLASS_SYMBOL] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SYMBOL], js_symbol_proto_funcs,
+                               countof(js_symbol_proto_funcs));
+    obj = JS_NewGlobalCConstructor(ctx, "Symbol", js_symbol_constructor, 0,
+                                   ctx->class_proto[JS_CLASS_SYMBOL]);
+    JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs,
+                               countof(js_symbol_funcs));
+    for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) {
+        char buf[ATOM_GET_STR_BUF_SIZE];
+        const char *str, *p;
+        str = JS_AtomGetStr(ctx, buf, sizeof(buf), i);
+        /* skip "Symbol." */
+        p = strchr(str, '.');
+        if (p)
+            str = p + 1;
+        JS_DefinePropertyValueStr(ctx, obj, str, JS_AtomToValue(ctx, i), 0);
+    }
+
+    /* ES6 Generator */
+    ctx->class_proto[JS_CLASS_GENERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR],
+                               js_generator_proto_funcs,
+                               countof(js_generator_proto_funcs));
+
+    ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
+    obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor,
+                                "GeneratorFunction", 1,
+                                JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR);
+    JS_SetPropertyFunctionList(ctx,
+                               ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
+                               js_generator_function_proto_funcs,
+                               countof(js_generator_function_proto_funcs));
+    JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
+                       ctx->class_proto[JS_CLASS_GENERATOR],
+                       JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
+    JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
+                       0, JS_PROP_CONFIGURABLE);
+    JS_FreeValue(ctx, obj1);
+
+#ifdef CONFIG_BIGNUM
+    ctx->class_proto[JS_CLASS_BIG_INT] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_INT],
+                               js_bigint_proto_funcs,
+                               countof(js_bigint_proto_funcs));
+    obj1 = JS_NewCFunction(ctx, js_bigint_constructor, "BigInt", 1);
+    JS_NewGlobalCConstructor2(ctx, obj1, "BigInt",
+                              ctx->class_proto[JS_CLASS_BIG_INT]);
+    JS_SetPropertyFunctionList(ctx, obj1, js_integer_funcs,
+                               countof(js_integer_funcs));
+
+    ctx->class_proto[JS_CLASS_BIG_FLOAT] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_FLOAT],
+                               js_bigfloat_proto_funcs,
+                               countof(js_bigfloat_proto_funcs));
+    obj1 = JS_NewCFunction(ctx, js_bigfloat_constructor, "BigFloat", 1);
+    JS_NewGlobalCConstructor2(ctx, obj1, "BigFloat",
+                              ctx->class_proto[JS_CLASS_BIG_FLOAT]);
+    JS_SetPropertyFunctionList(ctx, obj1, js_bigfloat_funcs,
+                               countof(js_bigfloat_funcs));
+
+    ctx->class_proto[JS_CLASS_FLOAT_ENV] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_FLOAT_ENV],
+                               js_float_env_proto_funcs,
+                               countof(js_float_env_proto_funcs));
+    obj1 = JS_NewGlobalCConstructorOnly(ctx, "BigFloatEnv",
+                                        js_float_env_constructor, 1,
+                                        ctx->class_proto[JS_CLASS_FLOAT_ENV]);
+    JS_SetPropertyFunctionList(ctx, obj1, js_float_env_funcs,
+                               countof(js_float_env_funcs));
+#endif
+    /* global properties */
+    ctx->eval_obj = JS_NewCFunction(ctx, js_global_eval, "eval", 1);
+    JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_eval,
+                           JS_DupValue(ctx, ctx->eval_obj),
+                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+
+    JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs,
+                               countof(js_global_funcs));
+
+    JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis,
+                           JS_DupValue(ctx, ctx->global_obj),
+                           JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
+}
+
+/* Typed Arrays */
+
+static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = {
+    0, 0, 0, 1, 1, 2, 2,
+#ifdef CONFIG_BIGNUM
+    3, 3, /* BigInt64Array, BigUint64Array */
+#endif
+    2, 3
+};
+
+static JSValue js_array_buffer_constructor3(JSContext *ctx,
+                                            JSValueConst new_target,
+                                            uint64_t len, JSClassID class_id,
+                                            uint8_t *buf,
+                                            JSFreeArrayBufferDataFunc *free_func,
+                                            void *opaque, BOOL alloc_flag)
+{
+    JSValue obj;
+    JSArrayBuffer *abuf = NULL;
+
+    obj = js_create_from_ctor(ctx, new_target, class_id);
+    if (JS_IsException(obj))
+        return obj;
+    /* XXX: we are currently limited to 2 GB */
+    if (len > INT32_MAX) {
+        JS_ThrowRangeError(ctx, "invalid array buffer length");
+        goto fail;
+    }
+    abuf = js_malloc(ctx, sizeof(*abuf));
+    if (!abuf)
+        goto fail;
+    abuf->byte_length = len;
+    if (alloc_flag) {
+        /* the allocation must be done after the object creation */
+        abuf->data = js_mallocz(ctx, max_int(len, 1));
+        if (!abuf->data)
+            goto fail;
+    } else {
+        abuf->data = buf;
+    }
+    init_list_head(&abuf->array_list);
+    abuf->detached = FALSE;
+    abuf->shared = (class_id == JS_CLASS_SHARED_ARRAY_BUFFER);
+    abuf->opaque = opaque;
+    abuf->free_func = free_func;
+    if (alloc_flag && buf)
+        memcpy(abuf->data, buf, len);
+    JS_SetOpaque(obj, abuf);
+    return obj;
+ fail:
+    JS_FreeValue(ctx, obj);
+    js_free(ctx, abuf);
+    return JS_EXCEPTION;
+}
+
+static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr)
+{
+    js_free_rt(rt, ptr);
+}
+
+static JSValue js_array_buffer_constructor2(JSContext *ctx,
+                                            JSValueConst new_target,
+                                            uint64_t len, JSClassID class_id)
+{
+    return js_array_buffer_constructor3(ctx, new_target, len, class_id,
+                                        NULL, js_array_buffer_free, NULL,
+                                        TRUE);
+}
+
+static JSValue js_array_buffer_constructor1(JSContext *ctx,
+                                            JSValueConst new_target,
+                                            uint64_t len)
+{
+    return js_array_buffer_constructor2(ctx, new_target, len,
+                                        JS_CLASS_ARRAY_BUFFER);
+}
+
+JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len,
+                          JSFreeArrayBufferDataFunc *free_func, void *opaque,
+                          BOOL is_shared)
+{
+    return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len,
+                                        is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER,
+                                        buf, free_func, opaque, FALSE);
+}
+
+/* create a new ArrayBuffer of length 'len' and copy 'buf' to it */
+JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len)
+{
+    return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len,
+                                        JS_CLASS_ARRAY_BUFFER,
+                                        (uint8_t *)buf,
+                                        js_array_buffer_free, NULL,
+                                        TRUE);
+}
+
+static JSValue js_array_buffer_constructor(JSContext *ctx,
+                                           JSValueConst new_target,
+                                           int argc, JSValueConst *argv)
+{
+    uint64_t len;
+    if (JS_ToIndex(ctx, &len, argv[0]))
+        return JS_EXCEPTION;
+    return js_array_buffer_constructor1(ctx, new_target, len);
+}
+
+static JSValue js_shared_array_buffer_constructor(JSContext *ctx,
+                                                  JSValueConst new_target,
+                                                  int argc, JSValueConst *argv)
+{
+    uint64_t len;
+    if (JS_ToIndex(ctx, &len, argv[0]))
+        return JS_EXCEPTION;
+    return js_array_buffer_constructor2(ctx, new_target, len,
+                                        JS_CLASS_SHARED_ARRAY_BUFFER);
+}
+
+/* also used for SharedArrayBuffer */
+static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSArrayBuffer *abuf = p->u.array_buffer;
+    if (abuf) {
+        /* During the GC sweep phase the ArrayBuffer finalizer may be
+           called before the typed array finalizers using it, so
+           abuf->array_list is not necessarily empty. */
+        if (!JS_IsInGCSweep(rt)) {
+            assert(list_empty(&abuf->array_list));
+        }
+        if (abuf->free_func)
+            abuf->free_func(rt, abuf->opaque, abuf->data);
+        js_free_rt(rt, abuf);
+    }
+}
+
+static JSValue js_array_buffer_isView(JSContext *ctx,
+                                      JSValueConst this_val,
+                                      int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    BOOL res;
+    res = FALSE;
+    if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) {
+        p = JS_VALUE_GET_OBJ(argv[0]);
+        if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+            p->class_id <= JS_CLASS_DATAVIEW) {
+            res = TRUE;
+        }
+    }
+    return JS_NewBool(ctx, res);
+}
+
+static const JSCFunctionListEntry js_array_buffer_funcs[] = {
+    JS_CFUNC_DEF("isView", 1, js_array_buffer_isView ),
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
+};
+
+static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx)
+{
+    return JS_ThrowTypeError(ctx, "ArrayBuffer is detached");
+}
+
+static JSValue js_array_buffer_get_byteLength(JSContext *ctx,
+                                              JSValueConst this_val,
+                                              int class_id)
+{
+    JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id);
+    if (!abuf)
+        return JS_EXCEPTION;
+    if (abuf->detached)
+        return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+    return JS_NewUint32(ctx, abuf->byte_length);
+}
+
+void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj)
+{
+    JSArrayBuffer *abuf = JS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER);
+    struct list_head *el;
+
+    if (!abuf || abuf->detached)
+        return;
+    if (abuf->free_func)
+        abuf->free_func(ctx->rt, abuf->opaque, abuf->data);
+    abuf->data = NULL;
+    abuf->byte_length = 0;
+    abuf->detached = TRUE;
+
+    list_for_each(el, &abuf->array_list) {
+        JSTypedArray *ta;
+        JSObject *p;
+
+        ta = list_entry(el, JSTypedArray, link);
+        p = ta->obj;
+        /* Note: the typed array length and offset fields are not modified */
+        if (p->class_id != JS_CLASS_DATAVIEW) {
+            p->u.array.count = 0;
+            p->u.array.u.ptr = NULL;
+        }
+    }
+}
+
+/* get an ArrayBuffer or SharedArrayBuffer */
+static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        goto fail;
+    p = JS_VALUE_GET_OBJ(obj);
+    if (p->class_id != JS_CLASS_ARRAY_BUFFER &&
+        p->class_id != JS_CLASS_SHARED_ARRAY_BUFFER) {
+    fail:
+        JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_ARRAY_BUFFER);
+        return NULL;
+    }
+    return p->u.array_buffer;
+}
+
+/* return NULL if exception. WARNING: any JS call can detach the
+   buffer and render the returned pointer invalid */
+uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj)
+{
+    JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj);
+    if (!abuf)
+        goto fail;
+    if (abuf->detached) {
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    *psize = abuf->byte_length;
+    return abuf->data;
+ fail:
+    *psize = 0;
+    return NULL;
+}
+
+static JSValue js_array_buffer_slice(JSContext *ctx,
+                                     JSValueConst this_val,
+                                     int argc, JSValueConst *argv, int class_id)
+{
+    JSArrayBuffer *abuf, *new_abuf;
+    int64_t len, start, end, new_len;
+    JSValue ctor, new_obj;
+
+    abuf = JS_GetOpaque2(ctx, this_val, class_id);
+    if (!abuf)
+        return JS_EXCEPTION;
+    if (abuf->detached)
+        return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+    len = abuf->byte_length;
+
+    if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
+        return JS_EXCEPTION;
+
+    end = len;
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_ToInt64Clamp(ctx, &end, argv[1], 0, len, len))
+            return JS_EXCEPTION;
+    }
+    new_len = max_int64(end - start, 0);
+    ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
+    if (JS_IsException(ctor))
+        return ctor;
+    if (JS_IsUndefined(ctor)) {
+        new_obj = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len,
+                                               class_id);
+    } else {
+        JSValue args[1];
+        args[0] = JS_NewInt64(ctx, new_len);
+        new_obj = JS_CallConstructor(ctx, ctor, 1, (JSValueConst *)args);
+        JS_FreeValue(ctx, ctor);
+        JS_FreeValue(ctx, args[0]);
+    }
+    if (JS_IsException(new_obj))
+        return new_obj;
+    new_abuf = JS_GetOpaque2(ctx, new_obj, class_id);
+    if (!new_abuf)
+        goto fail;
+    if (js_same_value(ctx, new_obj, this_val)) {
+        JS_ThrowTypeError(ctx, "cannot use identical ArrayBuffer");
+        goto fail;
+    }
+    if (new_abuf->detached) {
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    if (new_abuf->byte_length < new_len) {
+        JS_ThrowTypeError(ctx, "new ArrayBuffer is too small");
+        goto fail;
+    }
+    /* must test again because of side effects */
+    if (abuf->detached) {
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    memcpy(new_abuf->data, abuf->data + start, new_len);
+    return new_obj;
+ fail:
+    JS_FreeValue(ctx, new_obj);
+    return JS_EXCEPTION;
+}
+
+static const JSCFunctionListEntry js_array_buffer_proto_funcs[] = {
+    JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_ARRAY_BUFFER ),
+    JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_ARRAY_BUFFER ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "ArrayBuffer", JS_PROP_CONFIGURABLE ),
+};
+
+/* SharedArrayBuffer */
+
+static const JSCFunctionListEntry js_shared_array_buffer_funcs[] = {
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
+};
+
+static const JSCFunctionListEntry js_shared_array_buffer_proto_funcs[] = {
+    JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ),
+    JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_SHARED_ARRAY_BUFFER ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedArrayBuffer", JS_PROP_CONFIGURABLE ),
+};
+
+static JSObject *get_typed_array(JSContext *ctx,
+                                 JSValueConst this_val,
+                                 int is_dataview)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
+        goto fail;
+    p = JS_VALUE_GET_OBJ(this_val);
+    if (is_dataview) {
+        if (p->class_id != JS_CLASS_DATAVIEW)
+            goto fail;
+    } else {
+        if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+              p->class_id <= JS_CLASS_FLOAT64_ARRAY)) {
+        fail:
+            JS_ThrowTypeError(ctx, "not a %s", is_dataview ? "DataView" : "TypedArray");
+            return NULL;
+        }
+    }
+    return p;
+}
+
+/* WARNING: 'p' must be a typed array */
+static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p)
+{
+    JSTypedArray *ta = p->u.typed_array;
+    JSArrayBuffer *abuf = ta->buffer->u.array_buffer;
+    /* XXX: could simplify test by ensuring that
+       p->u.array.u.ptr is NULL iff it is detached */
+    return abuf->detached;
+}
+
+/* WARNING: 'p' must be a typed array. Works even if the array buffer
+   is detached */
+static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p)
+{
+    JSTypedArray *ta = p->u.typed_array;
+    int size_log2 = typed_array_size_log2(p->class_id);
+    return ta->length >> size_log2;
+}
+
+static int validate_typed_array(JSContext *ctx, JSValueConst this_val)
+{
+    JSObject *p;
+    p = get_typed_array(ctx, this_val, 0);
+    if (!p)
+        return -1;
+    if (typed_array_is_detached(ctx, p)) {
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        return -1;
+    }
+    return 0;
+}
+
+static JSValue js_typed_array_get_length(JSContext *ctx,
+                                         JSValueConst this_val)
+{
+    JSObject *p;
+    p = get_typed_array(ctx, this_val, 0);
+    if (!p)
+        return JS_EXCEPTION;
+    return JS_NewInt32(ctx, p->u.array.count);
+}
+
+static JSValue js_typed_array_get_buffer(JSContext *ctx,
+                                         JSValueConst this_val, int is_dataview)
+{
+    JSObject *p;
+    JSTypedArray *ta;
+    p = get_typed_array(ctx, this_val, is_dataview);
+    if (!p)
+        return JS_EXCEPTION;
+    ta = p->u.typed_array;
+    return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, ta->buffer));
+}
+
+static JSValue js_typed_array_get_byteLength(JSContext *ctx,
+                                             JSValueConst this_val,
+                                             int is_dataview)
+{
+    JSObject *p;
+    JSTypedArray *ta;
+    p = get_typed_array(ctx, this_val, is_dataview);
+    if (!p)
+        return JS_EXCEPTION;
+    if (typed_array_is_detached(ctx, p)) {
+        if (is_dataview) {
+            return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        } else {
+            return JS_NewInt32(ctx, 0);
+        }
+    }
+    ta = p->u.typed_array;
+    return JS_NewInt32(ctx, ta->length);
+}
+
+static JSValue js_typed_array_get_byteOffset(JSContext *ctx,
+                                             JSValueConst this_val,
+                                             int is_dataview)
+{
+    JSObject *p;
+    JSTypedArray *ta;
+    p = get_typed_array(ctx, this_val, is_dataview);
+    if (!p)
+        return JS_EXCEPTION;
+    if (typed_array_is_detached(ctx, p)) {
+        if (is_dataview) {
+            return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        } else {
+            return JS_NewInt32(ctx, 0);
+        }
+    }
+    ta = p->u.typed_array;
+    return JS_NewInt32(ctx, ta->offset);
+}
+
+static JSValue js_typed_array_get_toStringTag(JSContext *ctx,
+                                              JSValueConst this_val)
+{
+    JSObject *p;
+    if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
+        return JS_UNDEFINED;
+    p = JS_VALUE_GET_OBJ(this_val);
+    if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+          p->class_id <= JS_CLASS_FLOAT64_ARRAY))
+        return JS_UNDEFINED;
+    return JS_AtomToString(ctx, ctx->rt->class_array[p->class_id].class_name);
+}
+
+static JSValue js_typed_array_set_internal(JSContext *ctx,
+                                           JSValueConst dst,
+                                           JSValueConst src,
+                                           JSValueConst off)
+{
+    JSObject *p;
+    JSObject *src_p;
+    uint32_t i;
+    int64_t src_len, offset;
+    JSValue val, src_obj = JS_UNDEFINED;
+
+    p = get_typed_array(ctx, dst, 0);
+    if (!p)
+        goto fail;
+    if (JS_ToInt64Sat(ctx, &offset, off))
+        goto fail;
+    if (offset < 0)
+        goto range_error;
+    if (typed_array_is_detached(ctx, p)) {
+    detached:
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    src_obj = JS_ToObject(ctx, src);
+    if (JS_IsException(src_obj))
+        goto fail;
+    src_p = JS_VALUE_GET_OBJ(src_obj);
+    if (src_p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+        src_p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+        JSTypedArray *dest_ta = p->u.typed_array;
+        JSArrayBuffer *dest_abuf = dest_ta->buffer->u.array_buffer;
+        JSTypedArray *src_ta = src_p->u.typed_array;
+        JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer;
+        int shift = typed_array_size_log2(p->class_id);
+
+        if (src_abuf->detached)
+            goto detached;
+
+        src_len = src_p->u.array.count;
+        if (offset > (int64_t)(p->u.array.count - src_len))
+            goto range_error;
+
+        /* copying between typed objects */
+        if (src_p->class_id == p->class_id) {
+            /* same type, use memmove */
+            memmove(dest_abuf->data + dest_ta->offset + (offset << shift),
+                    src_abuf->data + src_ta->offset, src_len << shift);
+            goto done;
+        }
+        if (dest_abuf->data == src_abuf->data) {
+            /* copying between the same buffer using different types of mappings
+               would require a temporary buffer */
+        }
+        /* otherwise, default behavior is slow but correct */
+    } else {
+        if (js_get_length64(ctx, &src_len, src_obj))
+            goto fail;
+        if (offset > (int64_t)(p->u.array.count - src_len)) {
+        range_error:
+            JS_ThrowRangeError(ctx, "invalid array length");
+            goto fail;
+        }
+    }
+    for(i = 0; i < src_len; i++) {
+        val = JS_GetPropertyUint32(ctx, src_obj, i);
+        if (JS_IsException(val))
+            goto fail;
+        if (JS_SetPropertyUint32(ctx, dst, offset + i, val) < 0)
+            goto fail;
+    }
+done:
+    JS_FreeValue(ctx, src_obj);
+    return JS_UNDEFINED;
+fail:
+    JS_FreeValue(ctx, src_obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_set(JSContext *ctx,
+                                  JSValueConst this_val,
+                                  int argc, JSValueConst *argv)
+{
+    JSValueConst offset = JS_UNDEFINED;
+    if (argc > 1) {
+        offset = argv[1];
+    }
+    return js_typed_array_set_internal(ctx, this_val, argv[0], offset);
+}
+
+static JSValue js_create_typed_array_iterator(JSContext *ctx, JSValueConst this_val,
+                                              int argc, JSValueConst *argv, int magic)
+{
+    if (validate_typed_array(ctx, this_val))
+        return JS_EXCEPTION;
+    return js_create_array_iterator(ctx, this_val, argc, argv, magic);
+}
+
+/* return < 0 if exception */
+static int js_typed_array_get_length_internal(JSContext *ctx,
+                                              JSValueConst obj)
+{
+    JSObject *p;
+    p = get_typed_array(ctx, obj, 0);
+    if (!p)
+        return -1;
+    if (typed_array_is_detached(ctx, p)) {
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        return -1;
+    }
+    return p->u.array.count;
+}
+
+#if 0
+/* validate a typed array and return its length */
+static JSValue js_typed_array___getLength(JSContext *ctx,
+                                          JSValueConst this_val,
+                                          int argc, JSValueConst *argv)
+{
+    BOOL ignore_detached = JS_ToBool(ctx, argv[1]);
+
+    if (ignore_detached) {
+        return js_typed_array_get_length(ctx, argv[0]);
+    } else {
+        int len;
+        len = js_typed_array_get_length_internal(ctx, argv[0]);
+        if (len < 0)
+            return JS_EXCEPTION;
+        return JS_NewInt32(ctx, len);
+    }
+}
+#endif
+
+static JSValue js_typed_array_constructor(JSContext *ctx,
+                                          JSValueConst this_val,
+                                          int argc, JSValueConst *argv,
+                                          int classid);
+
+static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor,
+                                     int argc, JSValueConst *argv)
+{
+    JSValue ret;
+    int new_len;
+    int64_t len;
+
+    ret = JS_CallConstructor(ctx, ctor, argc, argv);
+    if (JS_IsException(ret))
+        return ret;
+    /* validate the typed array */
+    new_len = js_typed_array_get_length_internal(ctx, ret);
+    if (new_len < 0)
+        goto fail;
+    if (argc == 1) {
+        /* ensure that it is large enough */
+        if (JS_ToLengthFree(ctx, &len, JS_DupValue(ctx, argv[0])))
+            goto fail;
+        if (new_len < len) {
+            JS_ThrowTypeError(ctx, "TypedArray length is too small");
+        fail:
+            JS_FreeValue(ctx, ret);
+            return JS_EXCEPTION;
+        }
+    }
+    return ret;
+}
+
+#if 0
+static JSValue js_typed_array___create(JSContext *ctx,
+                                       JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    return js_typed_array_create(ctx, argv[0], max_int(argc - 1, 0), argv + 1);
+}
+#endif
+
+static JSValue js_typed_array___speciesCreate(JSContext *ctx,
+                                              JSValueConst this_val,
+                                              int argc, JSValueConst *argv)
+{
+    JSValueConst obj;
+    JSObject *p;
+    JSValue ctor, ret;
+    int argc1;
+
+    obj = argv[0];
+    p = get_typed_array(ctx, obj, 0);
+    if (!p)
+        return JS_EXCEPTION;
+    ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED);
+    if (JS_IsException(ctor))
+        return ctor;
+    argc1 = max_int(argc - 1, 0);
+    if (JS_IsUndefined(ctor)) {
+        ret = js_typed_array_constructor(ctx, JS_UNDEFINED, argc1, argv + 1,
+                                         p->class_id);
+    } else {
+        ret = js_typed_array_create(ctx, ctor, argc1, argv + 1);
+        JS_FreeValue(ctx, ctor);
+    }
+    return ret;
+}
+
+static JSValue js_typed_array_from(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    // from(items, mapfn = void 0, this_arg = void 0)
+    JSValueConst items = argv[0], mapfn, this_arg;
+    JSValueConst args[2];
+    JSValue stack[2];
+    JSValue iter, arr, r, v, v2;
+    int64_t k, len;
+    int done, mapping;
+
+    mapping = FALSE;
+    mapfn = JS_UNDEFINED;
+    this_arg = JS_UNDEFINED;
+    r = JS_UNDEFINED;
+    arr = JS_UNDEFINED;
+    stack[0] = JS_UNDEFINED;
+    stack[1] = JS_UNDEFINED;
+
+    if (argc > 1) {
+        mapfn = argv[1];
+        if (!JS_IsUndefined(mapfn)) {
+            if (check_function(ctx, mapfn))
+                goto exception;
+            mapping = 1;
+            if (argc > 2)
+                this_arg = argv[2];
+        }
+    }
+    iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator);
+    if (JS_IsException(iter))
+        goto exception;
+    if (!JS_IsUndefined(iter)) {
+        JS_FreeValue(ctx, iter);
+        arr = JS_NewArray(ctx);
+        if (JS_IsException(arr))
+            goto exception;
+        stack[0] = JS_DupValue(ctx, items);
+        if (js_for_of_start(ctx, &stack[1], FALSE))
+            goto exception;
+        for (k = 0;; k++) {
+            v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
+            if (JS_IsException(v))
+                goto exception_close;
+            if (done)
+                break;
+            if (JS_DefinePropertyValueInt64(ctx, arr, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0)
+                goto exception_close;
+        }
+    } else {
+        arr = JS_ToObject(ctx, items);
+        if (JS_IsException(arr))
+            goto exception;
+    }
+    if (js_get_length64(ctx, &len, arr) < 0)
+        goto exception;
+    v = JS_NewInt64(ctx, len);
+    args[0] = v;
+    r = js_typed_array_create(ctx, this_val, 1, args);
+    JS_FreeValue(ctx, v);
+    if (JS_IsException(r))
+        goto exception;
+    for(k = 0; k < len; k++) {
+        v = JS_GetPropertyInt64(ctx, arr, k);
+        if (JS_IsException(v))
+            goto exception;
+        if (mapping) {
+            args[0] = v;
+            args[1] = JS_NewInt32(ctx, k);
+            v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
+            JS_FreeValue(ctx, v);
+            v = v2;
+            if (JS_IsException(v))
+                goto exception;
+        }
+        if (JS_SetPropertyInt64(ctx, r, k, v) < 0)
+            goto exception;
+    }
+    goto done;
+
+ exception_close:
+    if (!JS_IsUndefined(stack[0]))
+        JS_IteratorClose(ctx, stack[0], TRUE);
+ exception:
+    JS_FreeValue(ctx, r);
+    r = JS_EXCEPTION;
+ done:
+    JS_FreeValue(ctx, arr);
+    JS_FreeValue(ctx, stack[0]);
+    JS_FreeValue(ctx, stack[1]);
+    return r;
+}
+
+static JSValue js_typed_array_of(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv)
+{
+    JSValue obj;
+    JSValueConst args[1];
+    int i;
+
+    args[0] = JS_NewInt32(ctx, argc);
+    obj = js_typed_array_create(ctx, this_val, 1, args);
+    if (JS_IsException(obj))
+        return obj;
+
+    for(i = 0; i < argc; i++) {
+        if (JS_SetPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i])) < 0) {
+            JS_FreeValue(ctx, obj);
+            return JS_EXCEPTION;
+        }
+    }
+    return obj;
+}
+
+static JSValue js_typed_array_copyWithin(JSContext *ctx, JSValueConst this_val,
+                                         int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    int len, to, from, final, count, shift;
+
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        return JS_EXCEPTION;
+
+    if (JS_ToInt32Clamp(ctx, &to, argv[0], 0, len, len))
+        return JS_EXCEPTION;
+
+    if (JS_ToInt32Clamp(ctx, &from, argv[1], 0, len, len))
+        return JS_EXCEPTION;
+
+    final = len;
+    if (argc > 2 && !JS_IsUndefined(argv[2])) {
+        if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len))
+            return JS_EXCEPTION;
+    }
+
+    count = min_int(final - from, len - to);
+    if (count > 0) {
+        p = JS_VALUE_GET_OBJ(this_val);
+        shift = typed_array_size_log2(p->class_id);
+        memmove(p->u.array.u.uint8_ptr + (to << shift),
+                p->u.array.u.uint8_ptr + (from << shift),
+                count << shift);
+    }
+    return JS_DupValue(ctx, this_val);
+}
+
+static JSValue js_typed_array_fill(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    int len, k, final, shift;
+    uint64_t v64;
+
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        return JS_EXCEPTION;
+    p = JS_VALUE_GET_OBJ(this_val);
+
+    if (p->class_id == JS_CLASS_UINT8C_ARRAY) {
+        int32_t v;
+        if (JS_ToUint8ClampFree(ctx, &v, JS_DupValue(ctx, argv[0])))
+            return JS_EXCEPTION;
+        v64 = v;
+    } else if (p->class_id <= JS_CLASS_UINT32_ARRAY) {
+        uint32_t v;
+        if (JS_ToUint32(ctx, &v, argv[0]))
+            return JS_EXCEPTION;
+        v64 = v;
+    } else
+#ifdef CONFIG_BIGNUM
+    if (p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
+        if (JS_ToBigInt64(ctx, (int64_t *)&v64, argv[0]))
+            return JS_EXCEPTION;
+    } else
+#endif
+    {
+        double d;
+        if (JS_ToFloat64(ctx, &d, argv[0]))
+            return JS_EXCEPTION;
+        if (p->class_id == JS_CLASS_FLOAT32_ARRAY) {
+            union {
+                float f;
+                uint32_t u32;
+            } u;
+            u.f = d;
+            v64 = u.u32;
+        } else {
+            JSFloat64Union u;
+            u.d = d;
+            v64 = u.u64;
+        }
+    }
+
+    k = 0;
+    if (argc > 1) {
+        if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len))
+            return JS_EXCEPTION;
+    }
+
+    final = len;
+    if (argc > 2 && !JS_IsUndefined(argv[2])) {
+        if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len))
+            return JS_EXCEPTION;
+    }
+
+    shift = typed_array_size_log2(p->class_id);
+    switch(shift) {
+    case 0:
+        if (k < final) {
+            memset(p->u.array.u.uint8_ptr + k, v64, final - k);
+        }
+        break;
+    case 1:
+        for(; k < final; k++) {
+            p->u.array.u.uint16_ptr[k] = v64;
+        }
+        break;
+    case 2:
+        for(; k < final; k++) {
+            p->u.array.u.uint32_ptr[k] = v64;
+        }
+        break;
+    case 3:
+        for(; k < final; k++) {
+            p->u.array.u.uint64_ptr[k] = v64;
+        }
+        break;
+    default:
+        abort();
+    }
+    return JS_DupValue(ctx, this_val);
+}
+
+static JSValue js_typed_array_find(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv, int findIndex)
+{
+    JSValueConst func, this_arg;
+    JSValueConst args[3];
+    JSValue val, index_val, res;
+    int len, k;
+
+    val = JS_UNDEFINED;
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        goto exception;
+
+    func = argv[0];
+    if (check_function(ctx, func))
+        goto exception;
+
+    this_arg = JS_UNDEFINED;
+    if (argc > 1)
+        this_arg = argv[1];
+
+    for(k = 0; k < len; k++) {
+        index_val = JS_NewInt32(ctx, k);
+        val = JS_GetPropertyValue(ctx, this_val, index_val);
+        if (JS_IsException(val))
+            goto exception;
+        args[0] = val;
+        args[1] = index_val;
+        args[2] = this_val;
+        res = JS_Call(ctx, func, this_arg, 3, args);
+        if (JS_IsException(res))
+            goto exception;
+        if (JS_ToBoolFree(ctx, res)) {
+            if (findIndex) {
+                JS_FreeValue(ctx, val);
+                return index_val;
+            } else {
+                return val;
+            }
+        }
+        JS_FreeValue(ctx, val);
+    }
+    if (findIndex)
+        return JS_NewInt32(ctx, -1);
+    else
+        return JS_UNDEFINED;
+
+exception:
+    JS_FreeValue(ctx, val);
+    return JS_EXCEPTION;
+}
+
+#define special_indexOf 0
+#define special_lastIndexOf 1
+#define special_includes -1
+
+static JSValue js_typed_array_indexOf(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv, int special)
+{
+    JSObject *p;
+    int len, tag, is_int, is_big, k, stop, inc, res = -1;
+    int64_t v64;
+    double d;
+    float f;
+
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        goto exception;
+    if (len == 0)
+        goto done;
+
+    if (special == special_lastIndexOf) {
+        k = len - 1;
+        if (argc > 1) {
+            if (JS_ToFloat64(ctx, &d, argv[1]))
+                goto exception;
+            if (isnan(d)) {
+                k = 0;
+            } else {
+                if (d >= 0) {
+                    if (d < k) {
+                        k = d;
+                    }
+                } else {
+                    d += len;
+                    if (d < 0)
+                        goto done;
+                    k = d;
+                }
+            }
+        }
+        stop = -1;
+        inc = -1;
+    } else {
+        k = 0;
+        if (argc > 1) {
+            if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len))
+                goto exception;
+        }
+        stop = len;
+        inc = 1;
+    }
+
+    is_big = 0;
+    is_int = 0; /* avoid warning */
+    v64 = 0; /* avoid warning */
+    tag = JS_VALUE_GET_NORM_TAG(argv[0]);
+    if (tag == JS_TAG_INT) {
+        is_int = 1;
+        v64 = JS_VALUE_GET_INT(argv[0]);
+        d = v64;
+    } else
+    if (tag == JS_TAG_FLOAT64) {
+        d = JS_VALUE_GET_FLOAT64(argv[0]);
+        v64 = d;
+        is_int = (v64 == d);
+    } else
+#ifdef CONFIG_BIGNUM
+    if (tag == JS_TAG_BIG_INT || tag == JS_TAG_BIG_FLOAT) {
+        /* will a generic loop for bigint and bigfloat */
+        /* XXX: should use the generic loop in math_mode? */
+        is_big = 1;
+    } else
+#endif
+    {
+        goto done;
+    }
+
+    p = JS_VALUE_GET_OBJ(this_val);
+    switch (p->class_id) {
+    case JS_CLASS_INT8_ARRAY:
+        if (is_int && (int8_t)v64 == v64)
+            goto scan8;
+        break;
+    case JS_CLASS_UINT8C_ARRAY:
+    case JS_CLASS_UINT8_ARRAY:
+        if (is_int && (uint8_t)v64 == v64) {
+            const uint8_t *pv, *pp;
+            uint16_t v;
+        scan8:
+            pv = p->u.array.u.uint8_ptr;
+            v = v64;
+            if (inc > 0) {
+                pp = memchr(pv + k, v, len - k);
+                if (pp)
+                    res = pp - pv;
+            } else {
+                for (; k != stop; k += inc) {
+                    if (pv[k] == v) {
+                        res = k;
+                        break;
+                    }
+                }
+            }
+        }
+        break;
+    case JS_CLASS_INT16_ARRAY:
+        if (is_int && (int16_t)v64 == v64)
+            goto scan16;
+        break;
+    case JS_CLASS_UINT16_ARRAY:
+        if (is_int && (uint16_t)v64 == v64) {
+            const uint16_t *pv;
+            uint16_t v;
+        scan16:
+            pv = p->u.array.u.uint16_ptr;
+            v = v64;
+            for (; k != stop; k += inc) {
+                if (pv[k] == v) {
+                    res = k;
+                    break;
+                }
+            }
+        }
+        break;
+    case JS_CLASS_INT32_ARRAY:
+        if (is_int && (int32_t)v64 == v64)
+            goto scan32;
+        break;
+    case JS_CLASS_UINT32_ARRAY:
+        if (is_int && (uint32_t)v64 == v64) {
+            const uint32_t *pv;
+            uint32_t v;
+        scan32:
+            pv = p->u.array.u.uint32_ptr;
+            v = v64;
+            for (; k != stop; k += inc) {
+                if (pv[k] == v) {
+                    res = k;
+                    break;
+                }
+            }
+        }
+        break;
+    case JS_CLASS_FLOAT32_ARRAY:
+        if (is_big)
+            break;
+        if (isnan(d)) {
+            const float *pv = p->u.array.u.float_ptr;
+            /* special case: indexOf returns -1, includes finds NaN */
+            if (special != special_includes)
+                goto done;
+            for (; k != stop; k += inc) {
+                if (isnan(pv[k])) {
+                    res = k;
+                    break;
+                }
+            }
+        } else if ((f = (float)d) == d) {
+            const float *pv = p->u.array.u.float_ptr;
+            for (; k != stop; k += inc) {
+                if (pv[k] == f) {
+                    res = k;
+                    break;
+                }
+            }
+        }
+        break;
+    case JS_CLASS_FLOAT64_ARRAY:
+        if (is_big)
+            break;
+        if (isnan(d)) {
+            const double *pv = p->u.array.u.double_ptr;
+            /* special case: indexOf returns -1, includes finds NaN */
+            if (special != special_includes)
+                goto done;
+            for (; k != stop; k += inc) {
+                if (isnan(pv[k])) {
+                    res = k;
+                    break;
+                }
+            }
+        } else {
+            const double *pv = p->u.array.u.double_ptr;
+            for (; k != stop; k += inc) {
+                if (pv[k] == d) {
+                    res = k;
+                    break;
+                }
+            }
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_CLASS_BIG_INT64_ARRAY:
+    case JS_CLASS_BIG_UINT64_ARRAY:
+        if (is_big || is_strict_mode(ctx)) {
+            /* generic loop for bignums, argv[0] is a bignum != NaN */
+            /* XXX: optimize with explicit values */
+            for (; k != stop; k += inc) {
+                JSValue v = JS_GetPropertyUint32(ctx, this_val, k);
+                int ret;
+                if (JS_IsException(v))
+                    goto exception;
+                ret = js_same_value_zero(ctx, v, argv[0]);
+                JS_FreeValue(ctx, v);
+                if (ret) {
+                    if (ret < 0)
+                        goto exception;
+                    res = k;
+                    break;
+                }
+            }
+        }
+        break;
+#endif
+    }
+
+done:
+    if (special == special_includes)
+        return JS_NewBool(ctx, res >= 0);
+    else
+        return JS_NewInt32(ctx, res);
+
+exception:
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_join(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv, int toLocaleString)
+{
+    JSValue sep = JS_UNDEFINED, el;
+    StringBuffer b_s, *b = &b_s;
+    JSString *p = NULL;
+    int i, n;
+    int c;
+
+    n = js_typed_array_get_length_internal(ctx, this_val);
+    if (n < 0)
+        goto exception;
+
+    c = ',';    /* default separator */
+    if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) {
+        sep = JS_ToString(ctx, argv[0]);
+        if (JS_IsException(sep))
+            goto exception;
+        p = JS_VALUE_GET_STRING(sep);
+        if (p->len == 1 && !p->is_wide_char)
+            c = p->u.str8[0];
+        else
+            c = -1;
+    }
+    string_buffer_init(ctx, b, 0);
+
+    /* XXX: optimize with direct access */
+    for(i = 0; i < n; i++) {
+        if (i > 0) {
+            if (c >= 0) {
+                if (string_buffer_putc8(b, c))
+                    goto fail;
+            } else {
+                if (string_buffer_concat(b, p, 0, p->len))
+                    goto fail;
+            }
+        }
+        el = JS_GetPropertyUint32(ctx, this_val, i);
+        if (JS_IsException(el))
+            goto fail;
+        if (toLocaleString) {
+            el = JS_ToLocaleStringFree(ctx, el);
+        }
+        if (string_buffer_concat_value_free(b, el))
+            goto fail;
+    }
+    JS_FreeValue(ctx, sep);
+    return string_buffer_end(b);
+
+fail:
+    string_buffer_free(b);
+    JS_FreeValue(ctx, sep);
+exception:
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_reverse(JSContext *ctx, JSValueConst this_val,
+                                      int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    int len;
+
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        return JS_EXCEPTION;
+    if (len > 0) {
+        p = JS_VALUE_GET_OBJ(this_val);
+        switch (typed_array_size_log2(p->class_id)) {
+        case 0:
+            {
+                uint8_t *p1 = p->u.array.u.uint8_ptr;
+                uint8_t *p2 = p1 + len - 1;
+                while (p1 < p2) {
+                    uint8_t v = *p1;
+                    *p1++ = *p2;
+                    *p2-- = v;
+                }
+            }
+            break;
+        case 1:
+            {
+                uint16_t *p1 = p->u.array.u.uint16_ptr;
+                uint16_t *p2 = p1 + len - 1;
+                while (p1 < p2) {
+                    uint16_t v = *p1;
+                    *p1++ = *p2;
+                    *p2-- = v;
+                }
+            }
+            break;
+        case 2:
+            {
+                uint32_t *p1 = p->u.array.u.uint32_ptr;
+                uint32_t *p2 = p1 + len - 1;
+                while (p1 < p2) {
+                    uint32_t v = *p1;
+                    *p1++ = *p2;
+                    *p2-- = v;
+                }
+            }
+            break;
+        case 3:
+            {
+                uint64_t *p1 = p->u.array.u.uint64_ptr;
+                uint64_t *p2 = p1 + len - 1;
+                while (p1 < p2) {
+                    uint64_t v = *p1;
+                    *p1++ = *p2;
+                    *p2-- = v;
+                }
+            }
+            break;
+        default:
+            abort();
+        }
+    }
+    return JS_DupValue(ctx, this_val);
+}
+
+static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val,
+                                    int argc, JSValueConst *argv)
+{
+    JSValueConst args[2];
+    JSValue arr, val;
+    JSObject *p, *p1;
+    int n, len, start, final, count, shift;
+
+    arr = JS_UNDEFINED;
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        goto exception;
+
+    if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
+        goto exception;
+    final = len;
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
+            goto exception;
+    }
+    count = max_int(final - start, 0);
+
+    p = get_typed_array(ctx, this_val, 0);
+    if (p == NULL)
+        goto exception;
+    shift = typed_array_size_log2(p->class_id);
+
+    args[0] = this_val;
+    args[1] = JS_NewInt32(ctx, count);
+    arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
+    if (JS_IsException(arr))
+        goto exception;
+
+    if (count > 0) {
+        if (validate_typed_array(ctx, this_val)
+        ||  validate_typed_array(ctx, arr))
+            goto exception;
+
+        p1 = get_typed_array(ctx, arr, 0);
+        if (p1 != NULL && p->class_id == p1->class_id &&
+            typed_array_get_length(ctx, p1) >= count &&
+            typed_array_get_length(ctx, p) >= start + count) {
+            memcpy(p1->u.array.u.uint8_ptr,
+                   p->u.array.u.uint8_ptr + (start << shift),
+                   count << shift);
+        } else {
+            for (n = 0; n < count; n++) {
+                val = JS_GetPropertyValue(ctx, this_val, JS_NewInt32(ctx, start + n));
+                if (JS_IsException(val))
+                    goto exception;
+                if (JS_SetPropertyValue(ctx, arr, JS_NewInt32(ctx, n), val,
+                                        JS_PROP_THROW) < 0)
+                    goto exception;
+            }
+        }
+    }
+    return arr;
+
+ exception:
+    JS_FreeValue(ctx, arr);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_subarray(JSContext *ctx, JSValueConst this_val,
+                                       int argc, JSValueConst *argv)
+{
+    JSValueConst args[4];
+    JSValue arr, byteOffset, ta_buffer;
+    JSObject *p;
+    int len, start, final, count, shift, offset;
+
+    p = get_typed_array(ctx, this_val, 0);
+    if (!p)
+        goto exception;
+    len = p->u.array.count;
+    if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
+        goto exception;
+
+    final = len;
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
+            goto exception;
+    }
+    count = max_int(final - start, 0);
+    byteOffset = js_typed_array_get_byteOffset(ctx, this_val, 0);
+    if (JS_IsException(byteOffset))
+        goto exception;
+    shift = typed_array_size_log2(p->class_id);
+    offset = JS_VALUE_GET_INT(byteOffset) + (start << shift);
+    JS_FreeValue(ctx, byteOffset);
+    ta_buffer = js_typed_array_get_buffer(ctx, this_val, 0);
+    if (JS_IsException(ta_buffer))
+        goto exception;
+    args[0] = this_val;
+    args[1] = ta_buffer;
+    args[2] = JS_NewInt32(ctx, offset);
+    args[3] = JS_NewInt32(ctx, count);
+    arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args);
+    JS_FreeValue(ctx, ta_buffer);
+    return arr;
+
+ exception:
+    return JS_EXCEPTION;
+}
+
+/* TypedArray.prototype.sort */
+
+static int js_cmp_doubles(double x, double y)
+{
+    if (isnan(x))    return isnan(y) ? 0 : +1;
+    if (isnan(y))    return -1;
+    if (x < y)       return -1;
+    if (x > y)       return 1;
+    if (x != 0)      return 0;
+    if (signbit(x))  return signbit(y) ? 0 : -1;
+    else             return signbit(y) ? 1 : 0;
+}
+
+static int js_TA_cmp_int8(const void *a, const void *b, void *opaque) {
+    return *(const int8_t *)a - *(const int8_t *)b;
+}
+
+static int js_TA_cmp_uint8(const void *a, const void *b, void *opaque) {
+    return *(const uint8_t *)a - *(const uint8_t *)b;
+}
+
+static int js_TA_cmp_int16(const void *a, const void *b, void *opaque) {
+    return *(const int16_t *)a - *(const int16_t *)b;
+}
+
+static int js_TA_cmp_uint16(const void *a, const void *b, void *opaque) {
+    return *(const uint16_t *)a - *(const uint16_t *)b;
+}
+
+static int js_TA_cmp_int32(const void *a, const void *b, void *opaque) {
+    int32_t x = *(const int32_t *)a;
+    int32_t y = *(const int32_t *)b;
+    return (y < x) - (y > x);
+}
+
+static int js_TA_cmp_uint32(const void *a, const void *b, void *opaque) {
+    uint32_t x = *(const uint32_t *)a;
+    uint32_t y = *(const uint32_t *)b;
+    return (y < x) - (y > x);
+}
+
+#ifdef CONFIG_BIGNUM
+static int js_TA_cmp_int64(const void *a, const void *b, void *opaque) {
+    int64_t x = *(const int64_t *)a;
+    int64_t y = *(const int64_t *)b;
+    return (y < x) - (y > x);
+}
+
+static int js_TA_cmp_uint64(const void *a, const void *b, void *opaque) {
+    uint64_t x = *(const uint64_t *)a;
+    uint64_t y = *(const uint64_t *)b;
+    return (y < x) - (y > x);
+}
+#endif
+
+static int js_TA_cmp_float32(const void *a, const void *b, void *opaque) {
+    return js_cmp_doubles(*(const float *)a, *(const float *)b);
+}
+
+static int js_TA_cmp_float64(const void *a, const void *b, void *opaque) {
+    return js_cmp_doubles(*(const double *)a, *(const double *)b);
+}
+
+static JSValue js_TA_get_int8(JSContext *ctx, const void *a) {
+    return JS_NewInt32(ctx, *(const int8_t *)a);
+}
+
+static JSValue js_TA_get_uint8(JSContext *ctx, const void *a) {
+    return JS_NewInt32(ctx, *(const uint8_t *)a);
+}
+
+static JSValue js_TA_get_int16(JSContext *ctx, const void *a) {
+    return JS_NewInt32(ctx, *(const int16_t *)a);
+}
+
+static JSValue js_TA_get_uint16(JSContext *ctx, const void *a) {
+    return JS_NewInt32(ctx, *(const uint16_t *)a);
+}
+
+static JSValue js_TA_get_int32(JSContext *ctx, const void *a) {
+    return JS_NewInt32(ctx, *(const int32_t *)a);
+}
+
+static JSValue js_TA_get_uint32(JSContext *ctx, const void *a) {
+    return JS_NewUint32(ctx, *(const uint32_t *)a);
+}
+
+#ifdef CONFIG_BIGNUM
+static JSValue js_TA_get_int64(JSContext *ctx, const void *a) {
+    return JS_NewBigInt64(ctx, *(int64_t *)a);
+}
+
+static JSValue js_TA_get_uint64(JSContext *ctx, const void *a) {
+    return JS_NewBigUint64(ctx, *(uint64_t *)a);
+}
+#endif
+
+static JSValue js_TA_get_float32(JSContext *ctx, const void *a) {
+    return __JS_NewFloat64(ctx, *(const float *)a);
+}
+
+static JSValue js_TA_get_float64(JSContext *ctx, const void *a) {
+    return __JS_NewFloat64(ctx, *(const double *)a);
+}
+
+struct TA_sort_context {
+    JSContext *ctx;
+    int exception;
+    JSValueConst arr;
+    JSValueConst cmp;
+    JSValue (*getfun)(JSContext *ctx, const void *a);
+    void *array_ptr; /* cannot change unless the array is detached */
+    int elt_size;
+};
+
+static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) {
+    struct TA_sort_context *psc = opaque;
+    JSContext *ctx = psc->ctx;
+    uint32_t a_idx, b_idx;
+    JSValueConst argv[2];
+    JSValue res;
+    int cmp;
+
+    cmp = 0;
+    if (!psc->exception) {
+        a_idx = *(uint32_t *)a;
+        b_idx = *(uint32_t *)b;
+        argv[0] = psc->getfun(ctx, psc->array_ptr +
+                              a_idx * (size_t)psc->elt_size);
+        argv[1] = psc->getfun(ctx, psc->array_ptr +
+                              b_idx * (size_t)(psc->elt_size));
+        res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv);
+        if (JS_IsException(res)) {
+            psc->exception = 1;
+            goto done;
+        }
+        if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) {
+            int val = JS_VALUE_GET_INT(res);
+            cmp = (val > 0) - (val < 0);
+        } else {
+            double val;
+            if (JS_ToFloat64Free(ctx, &val, res) < 0) {
+                psc->exception = 1;
+                goto done;
+            } else {
+                cmp = (val > 0) - (val < 0);
+            }
+        }
+        if (cmp == 0) {
+            /* make sort stable: compare array offsets */
+            cmp = (a_idx > b_idx) - (a_idx < b_idx);
+        }
+        if (validate_typed_array(ctx, psc->arr) < 0) {
+            psc->exception = 1;
+        }
+    done:
+        JS_FreeValue(ctx, (JSValue)argv[0]);
+        JS_FreeValue(ctx, (JSValue)argv[1]);
+    }
+    return cmp;
+}
+
+static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val,
+                                   int argc, JSValueConst *argv)
+{
+    JSObject *p;
+    int len;
+    size_t elt_size;
+    struct TA_sort_context tsc;
+    void *array_ptr;
+    int (*cmpfun)(const void *a, const void *b, void *opaque);
+
+    tsc.ctx = ctx;
+    tsc.exception = 0;
+    tsc.arr = this_val;
+    tsc.cmp = argv[0];
+
+    len = js_typed_array_get_length_internal(ctx, this_val);
+    if (len < 0)
+        return JS_EXCEPTION;
+    if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp))
+        return JS_EXCEPTION;
+
+    if (len > 1) {
+        p = JS_VALUE_GET_OBJ(this_val);
+        switch (p->class_id) {
+        case JS_CLASS_INT8_ARRAY:
+            tsc.getfun = js_TA_get_int8;
+            cmpfun = js_TA_cmp_int8;
+            break;
+        case JS_CLASS_UINT8C_ARRAY:
+        case JS_CLASS_UINT8_ARRAY:
+            tsc.getfun = js_TA_get_uint8;
+            cmpfun = js_TA_cmp_uint8;
+            break;
+        case JS_CLASS_INT16_ARRAY:
+            tsc.getfun = js_TA_get_int16;
+            cmpfun = js_TA_cmp_int16;
+            break;
+        case JS_CLASS_UINT16_ARRAY:
+            tsc.getfun = js_TA_get_uint16;
+            cmpfun = js_TA_cmp_uint16;
+            break;
+        case JS_CLASS_INT32_ARRAY:
+            tsc.getfun = js_TA_get_int32;
+            cmpfun = js_TA_cmp_int32;
+            break;
+        case JS_CLASS_UINT32_ARRAY:
+            tsc.getfun = js_TA_get_uint32;
+            cmpfun = js_TA_cmp_uint32;
+            break;
+#ifdef CONFIG_BIGNUM
+        case JS_CLASS_BIG_INT64_ARRAY:
+            tsc.getfun = js_TA_get_int64;
+            cmpfun = js_TA_cmp_int64;
+            break;
+        case JS_CLASS_BIG_UINT64_ARRAY:
+            tsc.getfun = js_TA_get_uint64;
+            cmpfun = js_TA_cmp_uint64;
+            break;
+#endif
+        case JS_CLASS_FLOAT32_ARRAY:
+            tsc.getfun = js_TA_get_float32;
+            cmpfun = js_TA_cmp_float32;
+            break;
+        case JS_CLASS_FLOAT64_ARRAY:
+            tsc.getfun = js_TA_get_float64;
+            cmpfun = js_TA_cmp_float64;
+            break;
+        default:
+            abort();
+        }
+        array_ptr = p->u.array.u.ptr;
+        elt_size = 1 << typed_array_size_log2(p->class_id);
+        if (!JS_IsUndefined(tsc.cmp)) {
+            uint32_t *array_idx;
+            void *array_tmp;
+            size_t i, j;
+            
+            /* XXX: a stable sort would use less memory */
+            array_idx = js_malloc(ctx, len * sizeof(array_idx[0]));
+            if (!array_idx)
+                return JS_EXCEPTION;
+            for(i = 0; i < len; i++)
+                array_idx[i] = i;
+            tsc.array_ptr = array_ptr;
+            tsc.elt_size = elt_size;
+            rqsort(array_idx, len, sizeof(array_idx[0]),
+                   js_TA_cmp_generic, &tsc);
+            if (tsc.exception)
+                goto fail;
+            array_tmp = js_malloc(ctx, len * elt_size);
+            if (!array_tmp) {
+            fail:
+                js_free(ctx, array_idx);
+                return JS_EXCEPTION;
+            }
+            memcpy(array_tmp, array_ptr, len * elt_size);
+            switch(elt_size) {
+            case 1:
+                for(i = 0; i < len; i++) {
+                    j = array_idx[i];
+                    ((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j];
+                }
+                break;
+            case 2:
+                for(i = 0; i < len; i++) {
+                    j = array_idx[i];
+                    ((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j];
+                }
+                break;
+            case 4:
+                for(i = 0; i < len; i++) {
+                    j = array_idx[i];
+                    ((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j];
+                }
+                break;
+            case 8:
+                for(i = 0; i < len; i++) {
+                    j = array_idx[i];
+                    ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j];
+                }
+                break;
+            default:
+                abort();
+            }
+            js_free(ctx, array_tmp);
+            js_free(ctx, array_idx);
+        } else {
+            rqsort(array_ptr, len, elt_size, cmpfun, &tsc);
+            if (tsc.exception)
+                return JS_EXCEPTION;
+        }
+    }
+    return JS_DupValue(ctx, this_val);
+}
+
+static const JSCFunctionListEntry js_typed_array_base_funcs[] = {
+    JS_CFUNC_DEF("from", 1, js_typed_array_from ),
+    JS_CFUNC_DEF("of", 0, js_typed_array_of ),
+    JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
+    //JS_CFUNC_DEF("__getLength", 2, js_typed_array___getLength ),
+    //JS_CFUNC_DEF("__create", 2, js_typed_array___create ),
+    //JS_CFUNC_DEF("__speciesCreate", 2, js_typed_array___speciesCreate ),
+};
+
+static const JSCFunctionListEntry js_typed_array_base_proto_funcs[] = {
+    JS_CGETSET_DEF("length", js_typed_array_get_length, NULL ),
+    JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 0 ),
+    JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 0 ),
+    JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 0 ),
+    JS_CFUNC_DEF("set", 1, js_typed_array_set ),
+    JS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_VALUE ),
+    JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
+    JS_CFUNC_MAGIC_DEF("keys", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY ),
+    JS_CFUNC_MAGIC_DEF("entries", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ),
+    JS_CGETSET_DEF("[Symbol.toStringTag]", js_typed_array_get_toStringTag, NULL ),
+    JS_CFUNC_DEF("copyWithin", 2, js_typed_array_copyWithin ),
+    JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every | special_TA ),
+    JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some | special_TA ),
+    JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach | special_TA ),
+    JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map | special_TA ),
+    JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter | special_TA ),
+    JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce | special_TA ),
+    JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight | special_TA ),
+    JS_CFUNC_DEF("fill", 1, js_typed_array_fill ),
+    JS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, 0 ),
+    JS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, 1 ),
+    JS_CFUNC_DEF("reverse", 0, js_typed_array_reverse ),
+    JS_CFUNC_DEF("slice", 2, js_typed_array_slice ),
+    JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ),
+    JS_CFUNC_DEF("sort", 1, js_typed_array_sort ),
+    JS_CFUNC_MAGIC_DEF("join", 1, js_typed_array_join, 0 ),
+    JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_typed_array_join, 1 ),
+    JS_CFUNC_MAGIC_DEF("indexOf", 1, js_typed_array_indexOf, special_indexOf ),
+    JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_typed_array_indexOf, special_lastIndexOf ),
+    JS_CFUNC_MAGIC_DEF("includes", 1, js_typed_array_indexOf, special_includes ),
+    //JS_ALIAS_BASE_DEF("toString", "toString", 2 /* Array.prototype. */), @@@
+};
+
+static JSValue js_typed_array_base_constructor(JSContext *ctx,
+                                               JSValueConst this_val,
+                                               int argc, JSValueConst *argv)
+{
+    return JS_ThrowTypeError(ctx, "cannot be called");
+}
+
+/* 'obj' must be an allocated typed array object */
+static int typed_array_init(JSContext *ctx, JSValueConst obj,
+                            JSValue buffer, uint64_t offset, uint64_t len)
+{
+    JSTypedArray *ta;
+    JSObject *p, *pbuffer;
+    JSArrayBuffer *abuf;
+    int size_log2;
+
+    p = JS_VALUE_GET_OBJ(obj);
+    size_log2 = typed_array_size_log2(p->class_id);
+    ta = js_malloc(ctx, sizeof(*ta));
+    if (!ta) {
+        JS_FreeValue(ctx, buffer);
+        return -1;
+    }
+    pbuffer = JS_VALUE_GET_OBJ(buffer);
+    abuf = pbuffer->u.array_buffer;
+    ta->obj = p;
+    ta->buffer = pbuffer;
+    ta->offset = offset;
+    ta->length = len << size_log2;
+    list_add_tail(&ta->link, &abuf->array_list);
+    p->u.typed_array = ta;
+    p->u.array.count = len;
+    p->u.array.u.ptr = abuf->data + offset;
+    return 0;
+}
+
+
+static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen,
+                                      JSValueConst obj, JSValueConst method)
+{
+    JSValue arr, iter, next_method = JS_UNDEFINED, val;
+    BOOL done;
+    uint32_t k;
+
+    *plen = 0;
+    arr = JS_NewArray(ctx);
+    if (JS_IsException(arr))
+        return arr;
+    iter = JS_GetIterator2(ctx, obj, method);
+    if (JS_IsException(iter))
+        goto fail;
+    next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
+    if (JS_IsException(next_method))
+        goto fail;
+    k = 0;
+    for(;;) {
+        val = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
+        if (JS_IsException(val))
+            goto fail;
+        if (done) {
+            JS_FreeValue(ctx, val);
+            break;
+        }
+        if (JS_CreateDataPropertyUint32(ctx, arr, k, val, JS_PROP_THROW) < 0)
+            goto fail;
+        k++;
+    }
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    *plen = k;
+    return arr;
+ fail:
+    JS_FreeValue(ctx, next_method);
+    JS_FreeValue(ctx, iter);
+    JS_FreeValue(ctx, arr);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_constructor_obj(JSContext *ctx,
+                                              JSValueConst new_target,
+                                              JSValueConst obj,
+                                              int classid)
+{
+    JSValue iter, ret, arr = JS_UNDEFINED, val, buffer;
+    uint32_t i;
+    int size_log2;
+    int64_t len;
+
+    size_log2 = typed_array_size_log2(classid);
+    ret = js_create_from_ctor(ctx, new_target, classid);
+    if (JS_IsException(ret))
+        return JS_EXCEPTION;
+
+    iter = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator);
+    if (JS_IsException(iter))
+        goto fail;
+    if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) {
+        uint32_t len1;
+        arr = js_array_from_iterator(ctx, &len1, obj, iter);
+        JS_FreeValue(ctx, iter);
+        if (JS_IsException(arr))
+            goto fail;
+        len = len1;
+    } else {
+        if (js_get_length64(ctx, &len, obj))
+            goto fail;
+        arr = JS_DupValue(ctx, obj);
+    }
+
+    buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED,
+                                          len << size_log2);
+    if (JS_IsException(buffer))
+        goto fail;
+    if (typed_array_init(ctx, ret, buffer, 0, len))
+        goto fail;
+
+    for(i = 0; i < len; i++) {
+        val = JS_GetPropertyUint32(ctx, arr, i);
+        if (JS_IsException(val))
+            goto fail;
+        if (JS_SetPropertyUint32(ctx, ret, i, val) < 0)
+            goto fail;
+    }
+    JS_FreeValue(ctx, arr);
+    return ret;
+ fail:
+    JS_FreeValue(ctx, arr);
+    JS_FreeValue(ctx, ret);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_constructor_ta(JSContext *ctx,
+                                             JSValueConst new_target,
+                                             JSValueConst src_obj,
+                                             int classid)
+{
+    JSObject *p, *src_buffer;
+    JSTypedArray *ta;
+    JSValue ctor, obj, buffer;
+    uint32_t len, i;
+    int size_log2;
+    JSArrayBuffer *src_abuf, *abuf;
+
+    obj = js_create_from_ctor(ctx, new_target, classid);
+    if (JS_IsException(obj))
+        return obj;
+    p = JS_VALUE_GET_OBJ(src_obj);
+    if (typed_array_is_detached(ctx, p)) {
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    ta = p->u.typed_array;
+    len = p->u.array.count;
+    src_buffer = ta->buffer;
+    src_abuf = src_buffer->u.array_buffer;
+    if (!src_abuf->shared) {
+        ctor = JS_SpeciesConstructor(ctx, JS_MKPTR(JS_TAG_OBJECT, src_buffer),
+                                     JS_UNDEFINED);
+        if (JS_IsException(ctor))
+            goto fail;
+    } else {
+        /* force ArrayBuffer default constructor */
+        ctor = JS_UNDEFINED;
+    }
+    size_log2 = typed_array_size_log2(classid);
+    buffer = js_array_buffer_constructor1(ctx, ctor,
+                                          (uint64_t)len << size_log2);
+    JS_FreeValue(ctx, ctor);
+    if (JS_IsException(buffer))
+        goto fail;
+    /* necessary because it could have been detached */
+    if (typed_array_is_detached(ctx, p)) {
+        JS_FreeValue(ctx, buffer);
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER);
+    if (typed_array_init(ctx, obj, buffer, 0, len))
+        goto fail;
+    if (p->class_id == classid) {
+        /* same type: copy the content */
+        memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length);
+    } else {
+        for(i = 0; i < len; i++) {
+            JSValue val;
+            val = JS_GetPropertyUint32(ctx, src_obj, i);
+            if (JS_IsException(val))
+                goto fail;
+            if (JS_SetPropertyUint32(ctx, obj, i, val) < 0)
+                goto fail;
+        }
+    }
+    return obj;
+ fail:
+    JS_FreeValue(ctx, obj);
+    return JS_EXCEPTION;
+}
+
+static JSValue js_typed_array_constructor(JSContext *ctx,
+                                          JSValueConst new_target,
+                                          int argc, JSValueConst *argv,
+                                          int classid)
+{
+    JSValue buffer, obj;
+    JSArrayBuffer *abuf;
+    int size_log2;
+    uint64_t len, offset;
+
+    size_log2 = typed_array_size_log2(classid);
+    if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) {
+        if (JS_ToIndex(ctx, &len, argv[0]))
+            return JS_EXCEPTION;
+        buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED,
+                                              len << size_log2);
+        if (JS_IsException(buffer))
+            return JS_EXCEPTION;
+        offset = 0;
+    } else {
+        JSObject *p = JS_VALUE_GET_OBJ(argv[0]);
+        if (p->class_id == JS_CLASS_ARRAY_BUFFER ||
+            p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) {
+            abuf = p->u.array_buffer;
+            if (JS_ToIndex(ctx, &offset, argv[1]))
+                return JS_EXCEPTION;
+            if (abuf->detached)
+                return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+            if ((offset & ((1 << size_log2) - 1)) != 0 ||
+                offset > abuf->byte_length)
+                return JS_ThrowRangeError(ctx, "invalid offset");
+            if (JS_IsUndefined(argv[2])) {
+                if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0)
+                    goto invalid_length;
+                len = (abuf->byte_length - offset) >> size_log2;
+            } else {
+                if (JS_ToIndex(ctx, &len, argv[2]))
+                    return JS_EXCEPTION;
+                if (abuf->detached)
+                    return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+                if ((offset + (len << size_log2)) > abuf->byte_length) {
+                invalid_length:
+                    return JS_ThrowRangeError(ctx, "invalid length");
+                }
+            }
+            buffer = JS_DupValue(ctx, argv[0]);
+        } else {
+            if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
+                p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
+                return js_typed_array_constructor_ta(ctx, new_target, argv[0], classid);
+            } else {
+                return js_typed_array_constructor_obj(ctx, new_target, argv[0], classid);
+            }
+        }
+    }
+
+    obj = js_create_from_ctor(ctx, new_target, classid);
+    if (JS_IsException(obj)) {
+        JS_FreeValue(ctx, buffer);
+        return JS_EXCEPTION;
+    }
+    if (typed_array_init(ctx, obj, buffer, offset, len)) {
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    return obj;
+}
+
+static void js_typed_array_finalizer(JSRuntime *rt, JSValue val)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSTypedArray *ta = p->u.typed_array;
+    if (ta) {
+        /* during the GC the finalizers are called in an arbitrary
+           order so the ArrayBuffer finalizer may have been called */
+        if (JS_IsLiveObject(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer))) {
+            list_del(&ta->link);
+        }
+        JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer));
+        js_free_rt(rt, ta);
+    }
+}
+
+static void js_typed_array_mark(JSRuntime *rt, JSValueConst val,
+                                JS_MarkFunc *mark_func)
+{
+    JSObject *p = JS_VALUE_GET_OBJ(val);
+    JSTypedArray *ta = p->u.typed_array;
+    if (ta) {
+        JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer), mark_func);
+    }
+}
+
+static JSValue js_dataview_constructor(JSContext *ctx,
+                                       JSValueConst new_target,
+                                       int argc, JSValueConst *argv)
+{
+    JSArrayBuffer *abuf;
+    uint64_t offset;
+    uint32_t len;
+    JSValueConst buffer;
+    JSValue obj;
+    JSTypedArray *ta;
+    JSObject *p;
+
+    buffer = argv[0];
+    abuf = js_get_array_buffer(ctx, buffer);
+    if (!abuf)
+        return JS_EXCEPTION;
+    offset = 0;
+    if (argc > 1) {
+        if (JS_ToIndex(ctx, &offset, argv[1]))
+            return JS_EXCEPTION;
+    }
+    if (abuf->detached)
+        return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+    if (offset > abuf->byte_length)
+        return JS_ThrowRangeError(ctx, "invalid byteOffset");
+    len = abuf->byte_length - offset;
+    if (argc > 2 && !JS_IsUndefined(argv[2])) {
+        uint64_t l;
+        if (JS_ToIndex(ctx, &l, argv[2]))
+            return JS_EXCEPTION;
+        if (l > len)
+            return JS_ThrowRangeError(ctx, "invalid byteLength");
+        len = l;
+    }
+
+    obj = js_create_from_ctor(ctx, new_target, JS_CLASS_DATAVIEW);
+    if (JS_IsException(obj))
+        return JS_EXCEPTION;
+    if (abuf->detached) {
+        /* could have been detached in js_create_from_ctor() */
+        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+        goto fail;
+    }
+    ta = js_malloc(ctx, sizeof(*ta));
+    if (!ta) {
+    fail:
+        JS_FreeValue(ctx, obj);
+        return JS_EXCEPTION;
+    }
+    p = JS_VALUE_GET_OBJ(obj);
+    ta->obj = p;
+    ta->buffer = JS_VALUE_GET_OBJ(JS_DupValue(ctx, buffer));
+    ta->offset = offset;
+    ta->length = len;
+    list_add_tail(&ta->link, &abuf->array_list);
+    p->u.typed_array = ta;
+    return obj;
+}
+
+static JSValue js_dataview_getValue(JSContext *ctx,
+                                    JSValueConst this_obj,
+                                    int argc, JSValueConst *argv, int class_id)
+{
+    JSTypedArray *ta;
+    JSArrayBuffer *abuf;
+    int is_swap, size;
+    uint8_t *ptr;
+    uint32_t v;
+    uint64_t pos;
+
+    ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW);
+    if (!ta)
+        return JS_EXCEPTION;
+    size = 1 << typed_array_size_log2(class_id);
+    if (JS_ToIndex(ctx, &pos, argv[0]))
+        return JS_EXCEPTION;
+    is_swap = FALSE;
+    if (argc > 1)
+        is_swap = JS_ToBool(ctx, argv[1]);
+#ifndef WORDS_BIGENDIAN
+    is_swap ^= 1;
+#endif
+    abuf = ta->buffer->u.array_buffer;
+    if (abuf->detached)
+        return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+    if ((pos + size) > ta->length)
+        return JS_ThrowRangeError(ctx, "out of bound");
+    ptr = abuf->data + ta->offset + pos;
+
+    switch(class_id) {
+    case JS_CLASS_INT8_ARRAY:
+        return JS_NewInt32(ctx, *(int8_t *)ptr);
+    case JS_CLASS_UINT8_ARRAY:
+        return JS_NewInt32(ctx, *(uint8_t *)ptr);
+    case JS_CLASS_INT16_ARRAY:
+        v = get_u16(ptr);
+        if (is_swap)
+            v = bswap16(v);
+        return JS_NewInt32(ctx, (int16_t)v);
+    case JS_CLASS_UINT16_ARRAY:
+        v = get_u16(ptr);
+        if (is_swap)
+            v = bswap16(v);
+        return JS_NewInt32(ctx, v);
+    case JS_CLASS_INT32_ARRAY:
+        v = get_u32(ptr);
+        if (is_swap)
+            v = bswap32(v);
+        return JS_NewInt32(ctx, v);
+    case JS_CLASS_UINT32_ARRAY:
+        v = get_u32(ptr);
+        if (is_swap)
+            v = bswap32(v);
+        return JS_NewUint32(ctx, v);
+#ifdef CONFIG_BIGNUM
+    case JS_CLASS_BIG_INT64_ARRAY:
+        {
+            uint64_t v;
+            v = get_u64(ptr);
+            if (is_swap)
+                v = bswap64(v);
+            return JS_NewBigInt64(ctx, v);
+        }
+        break;
+    case JS_CLASS_BIG_UINT64_ARRAY:
+        {
+            uint64_t v;
+            v = get_u64(ptr);
+            if (is_swap)
+                v = bswap64(v);
+            return JS_NewBigUint64(ctx, v);
+        }
+        break;
+#endif
+    case JS_CLASS_FLOAT32_ARRAY:
+        {
+            union {
+                float f;
+                uint32_t i;
+            } u;
+            v = get_u32(ptr);
+            if (is_swap)
+                v = bswap32(v);
+            u.i = v;
+            return __JS_NewFloat64(ctx, u.f);
+        }
+    case JS_CLASS_FLOAT64_ARRAY:
+        {
+            union {
+                double f;
+                uint64_t i;
+            } u;
+            u.i = get_u64(ptr);
+            if (is_swap)
+                u.i = bswap64(u.i);
+            return __JS_NewFloat64(ctx, u.f);
+        }
+    default:
+        abort();
+    }
+}
+
+static JSValue js_dataview_setValue(JSContext *ctx,
+                                    JSValueConst this_obj,
+                                    int argc, JSValueConst *argv, int class_id)
+{
+    JSTypedArray *ta;
+    JSArrayBuffer *abuf;
+    int is_swap, size;
+    uint8_t *ptr;
+    uint64_t v64;
+    uint32_t v;
+    uint64_t pos;
+    JSValueConst val;
+
+    ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW);
+    if (!ta)
+        return JS_EXCEPTION;
+    size = 1 << typed_array_size_log2(class_id);
+    if (JS_ToIndex(ctx, &pos, argv[0]))
+        return JS_EXCEPTION;
+    val = argv[1];
+    v = 0; /* avoid warning */
+    v64 = 0; /* avoid warning */
+    if (class_id <= JS_CLASS_UINT32_ARRAY) {
+        if (JS_ToUint32(ctx, &v, val))
+            return JS_EXCEPTION;
+    } else
+#ifdef CONFIG_BIGNUM
+    if (class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
+        if (JS_ToBigInt64(ctx, (int64_t *)&v64, val))
+            return JS_EXCEPTION;
+    } else
+#endif
+    {
+        double d;
+        if (JS_ToFloat64(ctx, &d, val))
+            return JS_EXCEPTION;
+        if (class_id == JS_CLASS_FLOAT32_ARRAY) {
+            union {
+                float f;
+                uint32_t i;
+            } u;
+            u.f = d;
+            v = u.i;
+        } else {
+            JSFloat64Union u;
+            u.d = d;
+            v64 = u.u64;
+        }
+    }
+    is_swap = FALSE;
+    if (argc > 2)
+        is_swap = JS_ToBool(ctx, argv[2]);
+#ifndef WORDS_BIGENDIAN
+    is_swap ^= 1;
+#endif
+    abuf = ta->buffer->u.array_buffer;
+    if (abuf->detached)
+        return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+    if ((pos + size) > ta->length)
+        return JS_ThrowRangeError(ctx, "out of bound");
+    ptr = abuf->data + ta->offset + pos;
+
+    switch(class_id) {
+    case JS_CLASS_INT8_ARRAY:
+    case JS_CLASS_UINT8_ARRAY:
+        *ptr = v;
+        break;
+    case JS_CLASS_INT16_ARRAY:
+    case JS_CLASS_UINT16_ARRAY:
+        if (is_swap)
+            v = bswap16(v);
+        put_u16(ptr, v);
+        break;
+    case JS_CLASS_INT32_ARRAY:
+    case JS_CLASS_UINT32_ARRAY:
+    case JS_CLASS_FLOAT32_ARRAY:
+        if (is_swap)
+            v = bswap32(v);
+        put_u32(ptr, v);
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_CLASS_BIG_INT64_ARRAY:
+    case JS_CLASS_BIG_UINT64_ARRAY:
+#endif
+    case JS_CLASS_FLOAT64_ARRAY:
+        if (is_swap)
+            v64 = bswap64(v64);
+        put_u64(ptr, v64);
+        break;
+    default:
+        abort();
+    }
+    return JS_UNDEFINED;
+}
+
+static const JSCFunctionListEntry js_dataview_proto_funcs[] = {
+    JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 1 ),
+    JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 1 ),
+    JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 1 ),
+    JS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, JS_CLASS_INT8_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, JS_CLASS_UINT8_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, JS_CLASS_INT16_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getUint16", 1, js_dataview_getValue, JS_CLASS_UINT16_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getInt32", 1, js_dataview_getValue, JS_CLASS_INT32_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getUint32", 1, js_dataview_getValue, JS_CLASS_UINT32_ARRAY ),
+#ifdef CONFIG_BIGNUM
+    JS_CFUNC_MAGIC_DEF("getBigInt64", 1, js_dataview_getValue, JS_CLASS_BIG_INT64_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getBigUint64", 1, js_dataview_getValue, JS_CLASS_BIG_UINT64_ARRAY ),
+#endif
+    JS_CFUNC_MAGIC_DEF("getFloat32", 1, js_dataview_getValue, JS_CLASS_FLOAT32_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("getFloat64", 1, js_dataview_getValue, JS_CLASS_FLOAT64_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setInt8", 2, js_dataview_setValue, JS_CLASS_INT8_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setUint8", 2, js_dataview_setValue, JS_CLASS_UINT8_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setInt16", 2, js_dataview_setValue, JS_CLASS_INT16_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setUint16", 2, js_dataview_setValue, JS_CLASS_UINT16_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setInt32", 2, js_dataview_setValue, JS_CLASS_INT32_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setUint32", 2, js_dataview_setValue, JS_CLASS_UINT32_ARRAY ),
+#ifdef CONFIG_BIGNUM
+    JS_CFUNC_MAGIC_DEF("setBigInt64", 2, js_dataview_setValue, JS_CLASS_BIG_INT64_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setBigUint64", 2, js_dataview_setValue, JS_CLASS_BIG_UINT64_ARRAY ),
+#endif
+    JS_CFUNC_MAGIC_DEF("setFloat32", 2, js_dataview_setValue, JS_CLASS_FLOAT32_ARRAY ),
+    JS_CFUNC_MAGIC_DEF("setFloat64", 2, js_dataview_setValue, JS_CLASS_FLOAT64_ARRAY ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "DataView", JS_PROP_CONFIGURABLE ),
+};
+
+/* Atomics */
+#ifdef CONFIG_ATOMICS
+
+typedef enum AtomicsOpEnum {
+    ATOMICS_OP_ADD,
+    ATOMICS_OP_AND,
+    ATOMICS_OP_OR,
+    ATOMICS_OP_SUB,
+    ATOMICS_OP_XOR,
+    ATOMICS_OP_EXCHANGE,
+    ATOMICS_OP_COMPARE_EXCHANGE,
+    ATOMICS_OP_LOAD,
+} AtomicsOpEnum;
+
+static void *js_atomics_get_ptr(JSContext *ctx,
+                                int *psize_log2, JSClassID *pclass_id,
+                                JSValueConst obj, JSValueConst idx_val,
+                                BOOL is_waitable)
+{
+    JSObject *p;
+    JSTypedArray *ta;
+    JSArrayBuffer *abuf;
+    void *ptr;
+    uint64_t idx;
+    BOOL err;
+    int size_log2;
+
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+        goto fail;
+    p = JS_VALUE_GET_OBJ(obj);
+#ifdef CONFIG_BIGNUM
+    if (is_waitable)
+        err = (p->class_id != JS_CLASS_INT32_ARRAY &&
+               p->class_id != JS_CLASS_BIG_INT64_ARRAY);
+    else
+        err = !(p->class_id >= JS_CLASS_INT8_ARRAY &&
+                p->class_id <= JS_CLASS_BIG_UINT64_ARRAY);
+#else
+    if (is_waitable)
+        err = (p->class_id != JS_CLASS_INT32_ARRAY);
+    else
+        err = !(p->class_id >= JS_CLASS_INT8_ARRAY &&
+                p->class_id <= JS_CLASS_UINT32_ARRAY);
+#endif
+    if (err) {
+    fail:
+        JS_ThrowTypeError(ctx, "integer TypedArray expected");
+        return NULL;
+    }
+    ta = p->u.typed_array;
+    abuf = ta->buffer->u.array_buffer;
+    if (!abuf->shared) {
+        JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray");
+        return NULL;
+    }
+    if (JS_ToIndex(ctx, &idx, idx_val)) {
+        return NULL;
+    }
+    if (idx >= p->u.array.count) {
+        JS_ThrowRangeError(ctx, "out-of-bound access");
+        return NULL;
+    }
+    size_log2 = typed_array_size_log2(p->class_id);
+    ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2);
+    if (psize_log2)
+        *psize_log2 = size_log2;
+    if (pclass_id)
+        *pclass_id = p->class_id;
+    return ptr;
+}
+
+static JSValue js_atomics_op(JSContext *ctx,
+                             JSValueConst this_obj,
+                             int argc, JSValueConst *argv, int op)
+{
+    int size_log2;
+#ifdef CONFIG_BIGNUM
+    uint64_t v, a, rep_val;
+#else
+    uint32_t v, a, rep_val;
+#endif
+    void *ptr;
+    JSValue ret;
+    JSClassID class_id;
+
+    ptr = js_atomics_get_ptr(ctx, &size_log2, &class_id,
+                             argv[0], argv[1], FALSE);
+    if (!ptr)
+        return JS_EXCEPTION;
+    rep_val = 0;
+    if (op == ATOMICS_OP_LOAD) {
+        v = 0;
+    } else
+#ifdef CONFIG_BIGNUM
+   if (size_log2 == 3) {
+            int64_t v64;
+            if (JS_ToBigInt64(ctx, &v64, argv[2]))
+                return JS_EXCEPTION;
+            v = v64;
+            if (op == ATOMICS_OP_COMPARE_EXCHANGE) {
+                if (JS_ToBigInt64(ctx, &v64, argv[3]))
+                    return JS_EXCEPTION;
+                rep_val = v64;
+            }
+   } else
+#endif
+   {
+       uint32_t v32;
+       if (JS_ToUint32(ctx, &v32, argv[2]))
+           return JS_EXCEPTION;
+       v = v32;
+       if (op == ATOMICS_OP_COMPARE_EXCHANGE) {
+           if (JS_ToUint32(ctx, &v32, argv[3]))
+               return JS_EXCEPTION;
+           rep_val = v32;
+       }
+   }
+   switch(op | (size_log2 << 3)) {
+            
+#ifdef CONFIG_BIGNUM
+#define OP(op_name, func_name)                          \
+    case ATOMICS_OP_ ## op_name | (0 << 3):             \
+       a = func_name((_Atomic(uint8_t) *)ptr, v);       \
+       break;                                           \
+    case ATOMICS_OP_ ## op_name | (1 << 3):             \
+        a = func_name((_Atomic(uint16_t) *)ptr, v);     \
+        break;                                          \
+    case ATOMICS_OP_ ## op_name | (2 << 3):             \
+        a = func_name((_Atomic(uint32_t) *)ptr, v);     \
+        break;                                          \
+    case ATOMICS_OP_ ## op_name | (3 << 3):             \
+        a = func_name((_Atomic(uint64_t) *)ptr, v);     \
+        break;
+#else
+#define OP(op_name, func_name)                          \
+    case ATOMICS_OP_ ## op_name | (0 << 3):             \
+       a = func_name((_Atomic(uint8_t) *)ptr, v);       \
+       break;                                           \
+    case ATOMICS_OP_ ## op_name | (1 << 3):             \
+        a = func_name((_Atomic(uint16_t) *)ptr, v);     \
+        break;                                          \
+    case ATOMICS_OP_ ## op_name | (2 << 3):             \
+        a = func_name((_Atomic(uint32_t) *)ptr, v);     \
+        break;
+#endif
+        OP(ADD, atomic_fetch_add)
+        OP(AND, atomic_fetch_and)
+        OP(OR, atomic_fetch_or)
+        OP(SUB, atomic_fetch_sub)
+        OP(XOR, atomic_fetch_xor)
+        OP(EXCHANGE, atomic_exchange)
+#undef OP
+
+    case ATOMICS_OP_LOAD | (0 << 3):
+        a = atomic_load((_Atomic(uint8_t) *)ptr);
+        break;
+    case ATOMICS_OP_LOAD | (1 << 3):
+        a = atomic_load((_Atomic(uint16_t) *)ptr);
+        break;
+    case ATOMICS_OP_LOAD | (2 << 3):
+        a = atomic_load((_Atomic(uint32_t) *)ptr);
+        break;
+#ifdef CONFIG_BIGNUM
+    case ATOMICS_OP_LOAD | (3 << 3):
+        a = atomic_load((_Atomic(uint64_t) *)ptr);
+        break;
+#endif
+        
+    case ATOMICS_OP_COMPARE_EXCHANGE | (0 << 3):
+        {
+            uint8_t v1 = v;
+            atomic_compare_exchange_strong((_Atomic(uint8_t) *)ptr, &v1, rep_val);
+            a = v1;
+        }
+        break;
+    case ATOMICS_OP_COMPARE_EXCHANGE | (1 << 3):
+        {
+            uint16_t v1 = v;
+            atomic_compare_exchange_strong((_Atomic(uint16_t) *)ptr, &v1, rep_val);
+            a = v1;
+        }
+        break;
+    case ATOMICS_OP_COMPARE_EXCHANGE | (2 << 3):
+        {
+            uint32_t v1 = v;
+            atomic_compare_exchange_strong((_Atomic(uint32_t) *)ptr, &v1, rep_val);
+            a = v1;
+        }
+        break;
+#ifdef CONFIG_BIGNUM
+    case ATOMICS_OP_COMPARE_EXCHANGE | (3 << 3):
+        {
+            uint64_t v1 = v;
+            atomic_compare_exchange_strong((_Atomic(uint64_t) *)ptr, &v1, rep_val);
+            a = v1;
+        }
+        break;
+#endif
+    default:
+        abort();
+    }
+
+    switch(class_id) {
+    case JS_CLASS_INT8_ARRAY:
+        a = (int8_t)a;
+        goto done;
+    case JS_CLASS_UINT8_ARRAY:
+        a = (uint8_t)a;
+        goto done;
+    case JS_CLASS_INT16_ARRAY:
+        a = (int16_t)a;
+        goto done;
+    case JS_CLASS_UINT16_ARRAY:
+        a = (uint16_t)a;
+        goto done;
+    case JS_CLASS_INT32_ARRAY:
+    done:
+        ret = JS_NewInt32(ctx, a);
+        break;
+    case JS_CLASS_UINT32_ARRAY:
+        ret = JS_NewUint32(ctx, a);
+        break;
+#ifdef CONFIG_BIGNUM
+    case JS_CLASS_BIG_INT64_ARRAY:
+        ret = JS_NewBigInt64(ctx, a);
+        break;
+    case JS_CLASS_BIG_UINT64_ARRAY:
+        ret = JS_NewBigUint64(ctx, a);
+        break;
+#endif
+    default:
+        abort();
+    }
+    return ret;
+}
+
+static JSValue js_atomics_store(JSContext *ctx,
+                                JSValueConst this_obj,
+                                int argc, JSValueConst *argv)
+{
+    int size_log2;
+    void *ptr;
+    JSValue ret;
+
+    ptr = js_atomics_get_ptr(ctx, &size_log2, NULL, argv[0], argv[1], FALSE);
+    if (!ptr)
+        return JS_EXCEPTION;
+#ifdef CONFIG_BIGNUM
+    if (size_log2 == 3) {
+        int64_t v64;
+        ret = JS_ToBigIntValueFree(ctx, JS_DupValue(ctx, argv[2]));
+        if (JS_IsException(ret))
+            return ret;
+        if (JS_ToBigInt64(ctx, &v64, ret)) {
+            JS_FreeValue(ctx, ret);
+            return JS_EXCEPTION;
+        }
+        atomic_store((_Atomic(uint64_t) *)ptr, v64);
+    } else
+#endif
+    {
+        uint32_t v;
+        /* XXX: spec, would be simpler to return the written value */
+        ret = JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv[2]));
+        if (JS_IsException(ret))
+            return ret;
+        if (JS_ToUint32(ctx, &v, ret)) {
+            JS_FreeValue(ctx, ret);
+            return JS_EXCEPTION;
+        }
+        switch(size_log2) {
+        case 0:
+            atomic_store((_Atomic(uint8_t) *)ptr, v);
+            break;
+        case 1:
+            atomic_store((_Atomic(uint16_t) *)ptr, v);
+            break;
+        case 2:
+            atomic_store((_Atomic(uint32_t) *)ptr, v);
+            break;
+        default:
+            abort();
+        }
+    }
+    return ret;
+}
+
+static JSValue js_atomics_isLockFree(JSContext *ctx,
+                                     JSValueConst this_obj,
+                                     int argc, JSValueConst *argv)
+{
+    int v, ret;
+    if (JS_ToInt32Sat(ctx, &v, argv[0]))
+        return JS_EXCEPTION;
+    ret = (v == 1 || v == 2 || v == 4
+#ifdef CONFIG_BIGNUM
+           || v == 8
+#endif
+           );
+    return JS_NewBool(ctx, ret);
+}
+
+typedef struct JSAtomicsWaiter {
+    struct list_head link;
+    BOOL linked;
+    pthread_cond_t cond;
+    int32_t *ptr;
+} JSAtomicsWaiter;
+
+static pthread_mutex_t js_atomics_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct list_head js_atomics_waiter_list =
+    LIST_HEAD_INIT(js_atomics_waiter_list);
+
+static JSValue js_atomics_wait(JSContext *ctx,
+                               JSValueConst this_obj,
+                               int argc, JSValueConst *argv)
+{
+    int64_t v;
+    int32_t v32;
+    void *ptr;
+    int64_t timeout;
+    struct timespec ts;
+    JSAtomicsWaiter waiter_s, *waiter;
+    int ret, size_log2, res;
+    double d;
+
+    ptr = js_atomics_get_ptr(ctx, &size_log2, NULL, argv[0], argv[1], TRUE);
+    if (!ptr)
+        return JS_EXCEPTION;
+#ifdef CONFIG_BIGNUM
+    if (size_log2 == 3) {
+        if (JS_ToBigInt64(ctx, &v, argv[2]))
+            return JS_EXCEPTION;
+    } else
+#endif
+    {        
+        if (JS_ToInt32(ctx, &v32, argv[2]))
+            return JS_EXCEPTION;
+        v = v32;
+    }
+    if (JS_ToFloat64(ctx, &d, argv[3]))
+        return JS_EXCEPTION;
+    if (isnan(d) || d > INT64_MAX)
+        timeout = INT64_MAX;
+    else if (d < 0)
+        timeout = 0;
+    else
+        timeout = (int64_t)d;
+    if (!ctx->rt->can_block)
+        return JS_ThrowTypeError(ctx, "cannot block in this thread");
+
+    /* XXX: inefficient if large number of waiters, should hash on
+       'ptr' value */
+    /* XXX: use Linux futexes when available ? */
+    pthread_mutex_lock(&js_atomics_mutex);
+    if (size_log2 == 3) {
+        res = *(int64_t *)ptr != v;
+    } else {
+        res = *(int32_t *)ptr != v;
+    }
+    if (res) {
+        pthread_mutex_unlock(&js_atomics_mutex);
+        return JS_AtomToString(ctx, JS_ATOM_not_equal);
+    }
+
+    waiter = &waiter_s;
+    waiter->ptr = ptr;
+    pthread_cond_init(&waiter->cond, NULL);
+    waiter->linked = TRUE;
+    list_add_tail(&waiter->link, &js_atomics_waiter_list);
+
+    if (timeout == INT64_MAX) {
+        pthread_cond_wait(&waiter->cond, &js_atomics_mutex);
+        ret = 0;
+    } else {
+        /* XXX: use clock monotonic */
+        clock_gettime(CLOCK_REALTIME, &ts);
+        ts.tv_sec += timeout / 1000;
+        ts.tv_nsec += (timeout % 1000) * 1000000;
+        if (ts.tv_nsec >= 1000000000) {
+            ts.tv_nsec -= 1000000000;
+            ts.tv_sec++;
+        }
+        ret = pthread_cond_timedwait(&waiter->cond, &js_atomics_mutex,
+                                     &ts);
+    }
+    if (waiter->linked)
+        list_del(&waiter->link);
+    pthread_mutex_unlock(&js_atomics_mutex);
+    pthread_cond_destroy(&waiter->cond);
+    if (ret == ETIMEDOUT) {
+        return JS_AtomToString(ctx, JS_ATOM_timed_out);
+    } else {
+        return JS_AtomToString(ctx, JS_ATOM_ok);
+    }
+}
+
+static JSValue js_atomics_notify(JSContext *ctx,
+                                 JSValueConst this_obj,
+                                 int argc, JSValueConst *argv)
+{
+    struct list_head *el, *el1, waiter_list;
+    int32_t count, n;
+    void *ptr;
+    JSAtomicsWaiter *waiter;
+
+    ptr = js_atomics_get_ptr(ctx, NULL, NULL, argv[0], argv[1], TRUE);
+    if (!ptr)
+        return JS_EXCEPTION;
+
+    if (JS_IsUndefined(argv[2])) {
+        count = INT32_MAX;
+    } else {
+        if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0))
+            return JS_EXCEPTION;
+    }
+
+    n = 0;
+    if (count > 0) {
+        pthread_mutex_lock(&js_atomics_mutex);
+        init_list_head(&waiter_list);
+        list_for_each_safe(el, el1, &js_atomics_waiter_list) {
+            waiter = list_entry(el, JSAtomicsWaiter, link);
+            if (waiter->ptr == ptr) {
+                list_del(&waiter->link);
+                waiter->linked = FALSE;
+                list_add_tail(&waiter->link, &waiter_list);
+                n++;
+                if (n >= count)
+                    break;
+            }
+        }
+        list_for_each(el, &waiter_list) {
+            waiter = list_entry(el, JSAtomicsWaiter, link);
+            pthread_cond_signal(&waiter->cond);
+        }
+        pthread_mutex_unlock(&js_atomics_mutex);
+    }
+    return JS_NewInt32(ctx, n);
+}
+
+static const JSCFunctionListEntry js_atomics_funcs[] = {
+    JS_CFUNC_MAGIC_DEF("add", 3, js_atomics_op, ATOMICS_OP_ADD ),
+    JS_CFUNC_MAGIC_DEF("and", 3, js_atomics_op, ATOMICS_OP_AND ),
+    JS_CFUNC_MAGIC_DEF("or", 3, js_atomics_op, ATOMICS_OP_OR ),
+    JS_CFUNC_MAGIC_DEF("sub", 3, js_atomics_op, ATOMICS_OP_SUB ),
+    JS_CFUNC_MAGIC_DEF("xor", 3, js_atomics_op, ATOMICS_OP_XOR ),
+    JS_CFUNC_MAGIC_DEF("exchange", 3, js_atomics_op, ATOMICS_OP_EXCHANGE ),
+    JS_CFUNC_MAGIC_DEF("compareExchange", 4, js_atomics_op, ATOMICS_OP_COMPARE_EXCHANGE ),
+    JS_CFUNC_MAGIC_DEF("load", 2, js_atomics_op, ATOMICS_OP_LOAD ),
+    JS_CFUNC_DEF("store", 3, js_atomics_store ),
+    JS_CFUNC_DEF("isLockFree", 1, js_atomics_isLockFree ),
+    JS_CFUNC_DEF("wait", 4, js_atomics_wait ),
+    JS_CFUNC_DEF("notify", 3, js_atomics_notify ),
+    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Atomics", JS_PROP_CONFIGURABLE ),
+};
+
+static const JSCFunctionListEntry js_atomics_obj[] = {
+    JS_OBJECT_DEF("Atomics", js_atomics_funcs, countof(js_atomics_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
+};
+
+void JS_AddIntrinsicAtomics(JSContext *ctx)
+{
+    /* add Atomics as autoinit object */
+    JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj));
+}
+
+#endif /* CONFIG_ATOMICS */
+
+void JS_AddIntrinsicTypedArrays(JSContext *ctx)
+{
+    JSValue typed_array_base_proto, typed_array_base_func;
+    JSValueConst array_buffer_func, shared_array_buffer_func;
+    int i;
+
+    ctx->class_proto[JS_CLASS_ARRAY_BUFFER] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_BUFFER],
+                               js_array_buffer_proto_funcs,
+                               countof(js_array_buffer_proto_funcs));
+
+    array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "ArrayBuffer",
+                                                 js_array_buffer_constructor, 1,
+                                                 ctx->class_proto[JS_CLASS_ARRAY_BUFFER]);
+    JS_SetPropertyFunctionList(ctx, array_buffer_func,
+                               js_array_buffer_funcs,
+                               countof(js_array_buffer_funcs));
+
+    ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER],
+                               js_shared_array_buffer_proto_funcs,
+                               countof(js_shared_array_buffer_proto_funcs));
+
+    shared_array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "SharedArrayBuffer",
+                                                 js_shared_array_buffer_constructor, 1,
+                                                 ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER]);
+    JS_SetPropertyFunctionList(ctx, shared_array_buffer_func,
+                               js_shared_array_buffer_funcs,
+                               countof(js_shared_array_buffer_funcs));
+
+    typed_array_base_proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, typed_array_base_proto,
+                               js_typed_array_base_proto_funcs,
+                               countof(js_typed_array_base_proto_funcs));
+
+    /* TypedArray.prototype.toString must be the same object as Array.prototype.toString */
+    JSValue obj = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_toString);
+    /* XXX: should use alias method in JSCFunctionListEntry */ //@@@
+    JS_DefinePropertyValue(ctx, typed_array_base_proto, JS_ATOM_toString, obj,
+                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+
+    typed_array_base_func = JS_NewCFunction(ctx, js_typed_array_base_constructor,
+                                            "TypedArray", 0);
+    JS_SetPropertyFunctionList(ctx, typed_array_base_func,
+                               js_typed_array_base_funcs,
+                               countof(js_typed_array_base_funcs));
+    JS_SetConstructor(ctx, typed_array_base_func, typed_array_base_proto);
+
+    for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) {
+        JSValue func_obj;
+        char buf[ATOM_GET_STR_BUF_SIZE];
+        const char *name;
+
+        ctx->class_proto[i] = JS_NewObjectProto(ctx, typed_array_base_proto);
+        JS_DefinePropertyValueStr(ctx, ctx->class_proto[i],
+                                  "BYTES_PER_ELEMENT",
+                                  JS_NewInt32(ctx, 1 << typed_array_size_log2(i)),
+                                  0);
+        name = JS_AtomGetStr(ctx, buf, sizeof(buf),
+                             JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY);
+        func_obj = JS_NewCFunction3(ctx, (JSCFunction *)js_typed_array_constructor,
+                                    name, 3, JS_CFUNC_constructor_magic, i,
+                                    typed_array_base_func);
+        JS_NewGlobalCConstructor2(ctx, func_obj, name, ctx->class_proto[i]);
+        JS_DefinePropertyValueStr(ctx, func_obj,
+                                  "BYTES_PER_ELEMENT",
+                                  JS_NewInt32(ctx, 1 << typed_array_size_log2(i)),
+                                  0);
+    }
+    JS_FreeValue(ctx, typed_array_base_proto);
+    JS_FreeValue(ctx, typed_array_base_func);
+
+    /* DataView */
+    ctx->class_proto[JS_CLASS_DATAVIEW] = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATAVIEW],
+                               js_dataview_proto_funcs,
+                               countof(js_dataview_proto_funcs));
+    JS_NewGlobalCConstructorOnly(ctx, "DataView",
+                                 js_dataview_constructor, 1,
+                                 ctx->class_proto[JS_CLASS_DATAVIEW]);
+    /* Atomics */
+#ifdef CONFIG_ATOMICS
+    JS_AddIntrinsicAtomics(ctx);
+#endif
+}
diff --git a/quickjs.h b/quickjs.h
new file mode 100644
index 0000000..eb4eb8d
--- /dev/null
+++ b/quickjs.h
@@ -0,0 +1,914 @@
+/*
+ * QuickJS Javascript Engine
+ *
+ * Copyright (c) 2017-2019 Fabrice Bellard
+ * Copyright (c) 2017-2019 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef QUICKJS_H
+#define QUICKJS_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define js_likely(x)          __builtin_expect(!!(x), 1)
+#define js_unlikely(x)        __builtin_expect(!!(x), 0)
+#define js_force_inline       inline __attribute__((always_inline))
+#define __js_printf_like(f, a)   __attribute__((format(printf, f, a)))
+#else
+#define js_likely(x)     (x)
+#define js_unlikely(x)   (x)
+#define js_force_inline  inline
+#define __js_printf_like(a, b)
+#endif
+
+#define JS_BOOL int
+
+typedef struct JSRuntime JSRuntime;
+typedef struct JSContext JSContext;
+typedef struct JSObject JSObject;
+typedef struct JSClass JSClass;
+typedef uint32_t JSClassID;
+typedef uint32_t JSAtom;
+
+#if defined(__x86_64__) || defined(__aarch64__)
+#define JS_PTR64
+#define JS_PTR64_DEF(a) a
+#else
+#define JS_PTR64_DEF(a)
+#endif
+
+#ifndef JS_PTR64
+#define JS_NAN_BOXING
+#endif
+
+enum {
+    /* all tags with a reference count are negative */
+    JS_TAG_FIRST       = -10, /* first negative tag */
+    JS_TAG_BIG_INT     = -10,
+    JS_TAG_BIG_FLOAT   = -9,
+    JS_TAG_SYMBOL      = -8,
+    JS_TAG_STRING      = -7,
+    JS_TAG_SHAPE       = -6, /* used internally during GC */
+    JS_TAG_ASYNC_FUNCTION = -5, /* used internally during GC */
+    JS_TAG_VAR_REF     = -4, /* used internally during GC */
+    JS_TAG_MODULE      = -3, /* used internally */
+    JS_TAG_FUNCTION_BYTECODE = -2, /* used internally */
+    JS_TAG_OBJECT      = -1,
+
+    JS_TAG_INT         = 0,
+    JS_TAG_BOOL        = 1,
+    JS_TAG_NULL        = 2,
+    JS_TAG_UNDEFINED   = 3,
+    JS_TAG_UNINITIALIZED = 4,
+    JS_TAG_CATCH_OFFSET = 5,
+    JS_TAG_EXCEPTION   = 6,
+    JS_TAG_FLOAT64     = 7,
+    /* any larger tag is FLOAT64 if JS_NAN_BOXING */
+};
+
+typedef struct JSRefCountHeader {
+    int ref_count;
+} JSRefCountHeader;
+
+#define JS_FLOAT64_NAN NAN
+
+#ifdef CONFIG_CHECK_JSVALUE
+/* JSValue consistency : it is not possible to run the code in this
+   mode, but it is useful to detect simple reference counting
+   errors. It would be interesting to modify a static C analyzer to
+   handle specific annotations (clang has such annotations but only
+   for objective C) */
+typedef struct __JSValue *JSValue;
+typedef const struct __JSValue *JSValueConst;
+
+#define JS_VALUE_GET_TAG(v) (int)((uintptr_t)(v) & 0xf)
+/* same as JS_VALUE_GET_TAG, but return JS_TAG_FLOAT64 with NaN boxing */
+#define JS_VALUE_GET_NORM_TAG(v) JS_VALUE_GET_TAG(v)
+#define JS_VALUE_GET_INT(v) (int)((intptr_t)(v) >> 4)
+#define JS_VALUE_GET_BOOL(v) JS_VALUE_GET_INT(v)
+#define JS_VALUE_GET_FLOAT64(v) (double)JS_VALUE_GET_INT(v)
+#define JS_VALUE_GET_PTR(v) (void *)((intptr_t)(v) & ~0xf)
+
+#define JS_MKVAL(tag, val) (JSValue)(intptr_t)(((val) << 4) | (tag))
+#define JS_MKPTR(tag, p) (JSValue)((intptr_t)(p) | (tag))
+
+#define JS_TAG_IS_FLOAT64(tag) ((unsigned)(tag) == JS_TAG_FLOAT64)
+
+#define JS_NAN JS_MKVAL(JS_TAG_FLOAT64, 1)
+
+static inline JSValue __JS_NewFloat64(JSContext *ctx, double d)
+{
+    return JS_MKVAL(JS_TAG_FLOAT64, (int)d);
+}
+
+#elif defined(JS_NAN_BOXING)
+
+typedef uint64_t JSValue;
+
+#define JSValueConst JSValue
+
+#define JS_VALUE_GET_TAG(v) (int)((v) >> 32)
+#define JS_VALUE_GET_INT(v) (int)(v)
+#define JS_VALUE_GET_BOOL(v) (int)(v)
+#define JS_VALUE_GET_PTR(v) (void *)(intptr_t)(v)
+
+#define JS_MKVAL(tag, val) (((uint64_t)(tag) << 32) | (uint32_t)(val))
+#define JS_MKPTR(tag, ptr) (((uint64_t)(tag) << 32) | (uintptr_t)(ptr))
+
+#define JS_FLOAT64_TAG_ADDEND (0x7ff80000 - JS_TAG_FIRST + 1) /* quiet NaN encoding */
+
+static inline double JS_VALUE_GET_FLOAT64(JSValue v)
+{
+    union {
+        JSValue v;
+        double d;
+    } u;
+    u.v = v;
+    u.v += (uint64_t)JS_FLOAT64_TAG_ADDEND << 32;
+    return u.d;
+}
+
+#define JS_NAN (0x7ff8000000000000 - ((uint64_t)JS_FLOAT64_TAG_ADDEND << 32))
+
+static inline JSValue __JS_NewFloat64(JSContext *ctx, double d)
+{
+    union {
+        double d;
+        uint64_t u64;
+    } u;
+    JSValue v;
+    u.d = d;
+    /* normalize NaN */
+    if (js_unlikely((u.u64 & 0x7fffffffffffffff) > 0x7ff0000000000000))
+        v = JS_NAN;
+    else
+        v = u.u64 - ((uint64_t)JS_FLOAT64_TAG_ADDEND << 32);
+    return v;
+}
+
+#define JS_TAG_IS_FLOAT64(tag) ((unsigned)((tag) - JS_TAG_FIRST) >= (JS_TAG_FLOAT64 - JS_TAG_FIRST))
+
+/* same as JS_VALUE_GET_TAG, but return JS_TAG_FLOAT64 with NaN boxing */
+static inline int JS_VALUE_GET_NORM_TAG(JSValue v)
+{
+    uint32_t tag;
+    tag = JS_VALUE_GET_TAG(v);
+    if (JS_TAG_IS_FLOAT64(tag))
+        return JS_TAG_FLOAT64;
+    else
+        return tag;
+}
+
+#else /* !JS_NAN_BOXING */
+
+typedef union JSValueUnion {
+    int32_t int32;
+    double float64;
+    void *ptr;
+} JSValueUnion;
+
+typedef struct JSValue {
+    JSValueUnion u;
+    int64_t tag;
+} JSValue;
+
+#define JSValueConst JSValue
+
+#define JS_VALUE_GET_TAG(v) ((int32_t)(v).tag)
+/* same as JS_VALUE_GET_TAG, but return JS_TAG_FLOAT64 with NaN boxing */
+#define JS_VALUE_GET_NORM_TAG(v) JS_VALUE_GET_TAG(v)
+#define JS_VALUE_GET_INT(v) ((v).u.int32)
+#define JS_VALUE_GET_BOOL(v) ((v).u.int32)
+#define JS_VALUE_GET_FLOAT64(v) ((v).u.float64)
+#define JS_VALUE_GET_PTR(v) ((v).u.ptr)
+
+#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .int32 = val }, tag }
+#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }
+
+#define JS_TAG_IS_FLOAT64(tag) ((unsigned)(tag) == JS_TAG_FLOAT64)
+
+#define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 }
+
+static inline JSValue __JS_NewFloat64(JSContext *ctx, double d)
+{
+    JSValue v;
+    v.tag = JS_TAG_FLOAT64;
+    v.u.float64 = d;
+    return v;
+}
+
+#endif /* !JS_NAN_BOXING */
+
+#define JS_VALUE_IS_BOTH_INT(v1, v2) ((JS_VALUE_GET_TAG(v1) | JS_VALUE_GET_TAG(v2)) == 0)
+#define JS_VALUE_IS_BOTH_FLOAT(v1, v2) (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v1)) && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v2)))
+
+#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v))
+#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v))
+#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST)
+
+/* special values */
+#define JS_NULL      JS_MKVAL(JS_TAG_NULL, 0)
+#define JS_UNDEFINED JS_MKVAL(JS_TAG_UNDEFINED, 0)
+#define JS_FALSE     JS_MKVAL(JS_TAG_BOOL, 0)
+#define JS_TRUE      JS_MKVAL(JS_TAG_BOOL, 1)
+#define JS_EXCEPTION JS_MKVAL(JS_TAG_EXCEPTION, 0)
+#define JS_UNINITIALIZED JS_MKVAL(JS_TAG_UNINITIALIZED, 0)
+
+/* flags for object properties */
+#define JS_PROP_CONFIGURABLE  (1 << 0)
+#define JS_PROP_WRITABLE      (1 << 1)
+#define JS_PROP_ENUMERABLE    (1 << 2)
+#define JS_PROP_C_W_E         (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)
+#define JS_PROP_LENGTH        (1 << 3) /* used internally in Arrays */
+#define JS_PROP_TMASK         (3 << 4) /* mask for NORMAL, GETSET, VARREF, AUTOINIT */
+#define JS_PROP_NORMAL         (0 << 4)
+#define JS_PROP_GETSET         (1 << 4)
+#define JS_PROP_VARREF         (2 << 4) /* used internally */
+#define JS_PROP_AUTOINIT       (3 << 4) /* used internally */
+
+/* flags for JS_DefineProperty */
+#define JS_PROP_HAS_SHIFT        8
+#define JS_PROP_HAS_CONFIGURABLE (1 << 8)
+#define JS_PROP_HAS_WRITABLE     (1 << 9)
+#define JS_PROP_HAS_ENUMERABLE   (1 << 10)
+#define JS_PROP_HAS_GET          (1 << 11)
+#define JS_PROP_HAS_SET          (1 << 12)
+#define JS_PROP_HAS_VALUE        (1 << 13)
+
+/* throw an exception if false would be returned
+   (JS_DefineProperty/JS_SetProperty) */
+#define JS_PROP_THROW            (1 << 14)
+/* throw an exception if false would be returned in strict mode
+   (JS_SetProperty) */
+#define JS_PROP_THROW_STRICT     (1 << 15)
+
+#define JS_PROP_NO_ADD           (1 << 16) /* internal use */
+#define JS_PROP_NO_EXOTIC        (1 << 17) /* internal use */
+
+#define JS_DEFAULT_STACK_SIZE (256 * 1024)
+
+/* JS_Eval() flags */
+#define JS_EVAL_TYPE_GLOBAL   (0 << 0) /* global code (default) */
+#define JS_EVAL_TYPE_MODULE   (1 << 0) /* module code */
+#define JS_EVAL_TYPE_DIRECT   (2 << 0) /* direct call (internal use) */
+#define JS_EVAL_TYPE_INDIRECT (3 << 0) /* indirect call (internal use) */
+#define JS_EVAL_TYPE_MASK     (3 << 0)
+
+#define JS_EVAL_FLAG_STRICT   (1 << 3) /* force 'strict' mode */
+#define JS_EVAL_FLAG_STRIP    (1 << 4) /* force 'strip' mode */
+/* compile but do not run. The result is an object with a
+   JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed
+   with JS_EvalFunction(). */
+#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5)
+
+typedef JSValue JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
+typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic);
+typedef JSValue JSCFunctionData(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data);
+
+typedef struct JSMallocState {
+    size_t malloc_count;
+    size_t malloc_size;
+    size_t malloc_limit;
+    void *opaque; /* user opaque */
+} JSMallocState;
+
+typedef struct JSMallocFunctions {
+    void *(*js_malloc)(JSMallocState *s, size_t size);
+    void (*js_free)(JSMallocState *s, void *ptr);
+    void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
+    size_t (*js_malloc_usable_size)(const void *ptr);
+} JSMallocFunctions;
+
+JSRuntime *JS_NewRuntime(void);
+/* info lifetime must exceed that of rt */
+void JS_SetRuntimeInfo(JSRuntime *rt, const char *info);
+void JS_SetMemoryLimit(JSRuntime *rt, size_t limit);
+void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold);
+JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque);
+void JS_FreeRuntime(JSRuntime *rt);
+typedef void JS_MarkFunc(JSRuntime *rt, JSValueConst val);
+void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func);
+void JS_RunGC(JSRuntime *rt);
+JS_BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj);
+JS_BOOL JS_IsInGCSweep(JSRuntime *rt);
+
+JSContext *JS_NewContext(JSRuntime *rt);
+void JS_FreeContext(JSContext *s);
+void *JS_GetContextOpaque(JSContext *ctx);
+void JS_SetContextOpaque(JSContext *ctx, void *opaque);
+JSRuntime *JS_GetRuntime(JSContext *ctx);
+void JS_SetMaxStackSize(JSContext *ctx, size_t stack_size);
+void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj);
+JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id);
+
+/* the following functions are used to select the intrinsic object to
+   save memory */
+JSContext *JS_NewContextRaw(JSRuntime *rt);
+void JS_AddIntrinsicBaseObjects(JSContext *ctx);
+void JS_AddIntrinsicDate(JSContext *ctx);
+void JS_AddIntrinsicEval(JSContext *ctx);
+void JS_AddIntrinsicStringNormalize(JSContext *ctx);
+void JS_AddIntrinsicRegExpCompiler(JSContext *ctx);
+void JS_AddIntrinsicRegExp(JSContext *ctx);
+void JS_AddIntrinsicJSON(JSContext *ctx);
+void JS_AddIntrinsicProxy(JSContext *ctx);
+void JS_AddIntrinsicMapSet(JSContext *ctx);
+void JS_AddIntrinsicTypedArrays(JSContext *ctx);
+void JS_AddIntrinsicPromise(JSContext *ctx);
+JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val,
+                                 int argc, JSValueConst *argv);
+
+void *js_malloc_rt(JSRuntime *rt, size_t size);
+void js_free_rt(JSRuntime *rt, void *ptr);
+void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size);
+size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr);
+void *js_mallocz_rt(JSRuntime *rt, size_t size);
+
+void *js_malloc(JSContext *ctx, size_t size);
+void js_free(JSContext *ctx, void *ptr);
+void *js_realloc(JSContext *ctx, void *ptr, size_t size);
+size_t js_malloc_usable_size(JSContext *ctx, const void *ptr);
+void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack);
+void *js_mallocz(JSContext *ctx, size_t size);
+char *js_strdup(JSContext *ctx, const char *str);
+char *js_strndup(JSContext *ctx, const char *s, size_t n);
+
+typedef struct JSMemoryUsage {
+    int64_t malloc_size, malloc_limit, memory_used_size;
+    int64_t malloc_count;
+    int64_t memory_used_count;
+    int64_t atom_count, atom_size;
+    int64_t str_count, str_size;
+    int64_t obj_count, obj_size;
+    int64_t prop_count, prop_size;
+    int64_t shape_count, shape_size;
+    int64_t js_func_count, js_func_size, js_func_code_size;
+    int64_t js_func_pc2line_count, js_func_pc2line_size;
+    int64_t c_func_count, array_count;
+    int64_t fast_array_count, fast_array_elements;
+    int64_t binary_object_count, binary_object_size;
+} JSMemoryUsage;
+
+void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s);
+void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt);
+
+/* atom support */
+JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len);
+JSAtom JS_NewAtom(JSContext *ctx, const char *str);
+JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n);
+JSAtom JS_DupAtom(JSContext *ctx, JSAtom v);
+void JS_FreeAtom(JSContext *ctx, JSAtom v);
+void JS_FreeAtomRT(JSRuntime *rt, JSAtom v);
+JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom);
+JSValue JS_AtomToString(JSContext *ctx, JSAtom atom);
+const char *JS_AtomToCString(JSContext *ctx, JSAtom atom);
+JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val);
+
+/* object class support */
+
+typedef struct JSPropertyEnum {
+    JS_BOOL is_enumerable;
+    JSAtom atom;
+} JSPropertyEnum;
+
+typedef struct JSPropertyDescriptor {
+    int flags;
+    JSValue value;
+    JSValue getter;
+    JSValue setter;
+} JSPropertyDescriptor;
+
+typedef struct JSClassExoticMethods {
+    /* Return -1 if exception (can only happen in case of Proxy object),
+       FALSE if the property does not exists, TRUE if it exists. If 1 is
+       returned, the property descriptor 'desc' is filled if != NULL. */
+    int (*get_own_property)(JSContext *ctx, JSPropertyDescriptor *desc,
+                             JSValueConst obj, JSAtom prop);
+    /* '*ptab' should hold the '*plen' property keys. Return 0 if OK,
+       -1 if exception. The 'is_enumerable' field is ignored.
+    */
+    int (*get_own_property_names)(JSContext *ctx, JSPropertyEnum **ptab,
+                                  uint32_t *plen,
+                                  JSValueConst obj);
+    /* return < 0 if exception, or TRUE/FALSE */
+    int (*delete_property)(JSContext *ctx, JSValueConst obj, JSAtom prop);
+    /* return < 0 if exception or TRUE/FALSE */
+    int (*define_own_property)(JSContext *ctx, JSValueConst this_obj,
+                               JSAtom prop, JSValueConst val,
+                               JSValueConst getter, JSValueConst setter,
+                               int flags);
+    /* The following methods can be emulated with the previous ones,
+       so they are usually not needed */
+    /* return < 0 if exception or TRUE/FALSE */
+    int (*has_property)(JSContext *ctx, JSValueConst obj, JSAtom atom);
+    JSValue (*get_property)(JSContext *ctx, JSValueConst obj, JSAtom atom,
+                            JSValueConst receiver);
+    /* return < 0 if exception or TRUE/FALSE */
+    int (*set_property)(JSContext *ctx, JSValueConst obj, JSAtom atom,
+                        JSValueConst value, JSValueConst receiver, int flags);
+} JSClassExoticMethods;
+
+typedef void JSClassFinalizer(JSRuntime *rt, JSValue val);
+typedef void JSClassGCMark(JSRuntime *rt, JSValueConst val,
+                           JS_MarkFunc *mark_func);
+typedef JSValue JSClassCall(JSContext *ctx, JSValueConst func_obj,
+                            JSValueConst this_val, int argc, JSValueConst *argv);
+
+typedef struct JSClassDef {
+    const char *class_name;
+    JSClassFinalizer *finalizer;
+    JSClassGCMark *gc_mark;
+    JSClassCall *call;
+    /* XXX: suppress this indirection ? It is here only to save memory
+       because only a few classes need these methods */
+    JSClassExoticMethods *exotic;
+} JSClassDef;
+
+JSClassID JS_NewClassID(JSClassID *pclass_id);
+int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def);
+int JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id);
+
+/* value handling */
+
+static js_force_inline JSValue JS_NewBool(JSContext *ctx, JS_BOOL val)
+{
+    return JS_MKVAL(JS_TAG_BOOL, val);
+}
+
+static js_force_inline JSValue JS_NewInt32(JSContext *ctx, int32_t val)
+{
+    return JS_MKVAL(JS_TAG_INT, val);
+}
+
+static js_force_inline JSValue JS_NewCatchOffset(JSContext *ctx, int32_t val)
+{
+    return JS_MKVAL(JS_TAG_CATCH_OFFSET, val);
+}
+
+JSValue JS_NewInt64(JSContext *ctx, int64_t v);
+JSValue JS_NewBigInt64(JSContext *ctx, int64_t v);
+JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v);
+
+static js_force_inline JSValue JS_NewFloat64(JSContext *ctx, double d)
+{
+    JSValue v;
+    int32_t val;
+    union {
+        double d;
+        uint64_t u;
+    } u, t;
+    u.d = d;
+    val = (int32_t)d;
+    t.d = val;
+    /* -0 cannot be represented as integer, so we compare the bit
+        representation */
+    if (u.u == t.u) {
+        v = JS_MKVAL(JS_TAG_INT, val);
+    } else {
+        v = __JS_NewFloat64(ctx, d);
+    }
+    return v;
+}
+
+JS_BOOL JS_IsNumber(JSValueConst v);
+
+static inline JS_BOOL JS_IsInteger(JSValueConst v)
+{
+    int tag = JS_VALUE_GET_TAG(v);
+    return tag == JS_TAG_INT || tag == JS_TAG_BIG_INT;
+}
+
+static inline JS_BOOL JS_IsBigFloat(JSValueConst v)
+{
+    int tag = JS_VALUE_GET_TAG(v);
+    return tag == JS_TAG_BIG_FLOAT;
+}
+
+static inline JS_BOOL JS_IsBool(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_BOOL;
+}
+
+static inline JS_BOOL JS_IsNull(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_NULL;
+}
+
+static inline JS_BOOL JS_IsUndefined(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_UNDEFINED;
+}
+
+static inline JS_BOOL JS_IsException(JSValueConst v)
+{
+    return js_unlikely(JS_VALUE_GET_TAG(v) == JS_TAG_EXCEPTION);
+}
+
+static inline JS_BOOL JS_IsUninitialized(JSValueConst v)
+{
+    return js_unlikely(JS_VALUE_GET_TAG(v) == JS_TAG_UNINITIALIZED);
+}
+
+static inline JS_BOOL JS_IsString(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_STRING;
+}
+
+static inline JS_BOOL JS_IsSymbol(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_SYMBOL;
+}
+
+static inline JS_BOOL JS_IsObject(JSValueConst v)
+{
+    return JS_VALUE_GET_TAG(v) == JS_TAG_OBJECT;
+}
+
+JSValue JS_Throw(JSContext *ctx, JSValue obj);
+JSValue JS_GetException(JSContext *ctx);
+JS_BOOL JS_IsError(JSContext *ctx, JSValueConst val);
+void JS_EnableIsErrorProperty(JSContext *ctx, JS_BOOL enable);
+void JS_ResetUncatchableError(JSContext *ctx);
+JSValue JS_NewError(JSContext *ctx);
+JSValue __js_printf_like(2, 3) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...);
+JSValue __js_printf_like(2, 3) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...);
+JSValue __js_printf_like(2, 3) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...);
+JSValue __js_printf_like(2, 3) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...);
+JSValue __js_printf_like(2, 3) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...);
+JSValue JS_ThrowOutOfMemory(JSContext *ctx);
+
+void __JS_FreeValue(JSContext *ctx, JSValue v);
+static inline void JS_FreeValue(JSContext *ctx, JSValue v)
+{
+    if (JS_VALUE_HAS_REF_COUNT(v)) {
+        JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
+        if (--p->ref_count <= 0) {
+            __JS_FreeValue(ctx, v);
+        }
+    }
+}
+void __JS_FreeValueRT(JSRuntime *rt, JSValue v);
+static inline void JS_FreeValueRT(JSRuntime *rt, JSValue v)
+{
+    if (JS_VALUE_HAS_REF_COUNT(v)) {
+        JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
+        if (--p->ref_count <= 0) {
+            __JS_FreeValueRT(rt, v);
+        }
+    }
+}
+
+static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v)
+{
+    if (JS_VALUE_HAS_REF_COUNT(v)) {
+        JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
+        p->ref_count++;
+    }
+    return (JSValue)v;
+}
+
+static inline JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v)
+{
+    if (JS_VALUE_HAS_REF_COUNT(v)) {
+        JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
+        p->ref_count++;
+    }
+    return (JSValue)v;
+}
+
+int JS_ToBool(JSContext *ctx, JSValueConst val); /* return -1 for JS_EXCEPTION */
+int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val);
+static int inline JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValueConst val)
+{
+    return JS_ToInt32(ctx, (int32_t*)pres, val);
+}
+int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val);
+int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val);
+int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val);
+int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val);
+
+JSValue JS_NewStringLen(JSContext *ctx, const char *str1, size_t len1);
+JSValue JS_NewString(JSContext *ctx, const char *str);
+JSValue JS_NewAtomString(JSContext *ctx, const char *str);
+JSValue JS_ToString(JSContext *ctx, JSValueConst val);
+JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val);
+const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, JS_BOOL cesu8);
+static inline const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValueConst val1)
+{
+    return JS_ToCStringLen2(ctx, plen, val1, 0);
+}
+static inline const char *JS_ToCString(JSContext *ctx, JSValueConst val1)
+{
+    return JS_ToCStringLen2(ctx, NULL, val1, 0);
+}
+void JS_FreeCString(JSContext *ctx, const char *ptr);
+
+JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto, JSClassID class_id);
+JSValue JS_NewObjectClass(JSContext *ctx, int class_id);
+JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto);
+JSValue JS_NewObject(JSContext *ctx);
+
+JS_BOOL JS_IsFunction(JSContext* ctx, JSValueConst val);
+JS_BOOL JS_IsConstructor(JSContext* ctx, JSValueConst val);
+
+JSValue JS_NewArray(JSContext *ctx);
+int JS_IsArray(JSContext *ctx, JSValueConst val);
+
+JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj,
+                               JSAtom prop, JSValueConst receiver,
+                               JS_BOOL throw_ref_error);
+static js_force_inline JSValue JS_GetProperty(JSContext *ctx, JSValueConst this_obj,
+                                              JSAtom prop)
+{
+    return JS_GetPropertyInternal(ctx, this_obj, prop, this_obj, 0);
+}
+JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj,
+                          const char *prop);
+JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj,
+                             uint32_t idx);
+
+int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj,
+                           JSAtom prop, JSValue val,
+                           int flags);
+static inline int JS_SetProperty(JSContext *ctx, JSValueConst this_obj,
+                                 JSAtom prop, JSValue val)
+{
+    return JS_SetPropertyInternal(ctx, this_obj, prop, val, JS_PROP_THROW);
+}
+int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj,
+                         uint32_t idx, JSValue val);
+int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj,
+                        int64_t idx, JSValue val);
+int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj,
+                      const char *prop, JSValue val);
+int JS_HasProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop);
+int JS_IsExtensible(JSContext *ctx, JSValueConst obj);
+int JS_PreventExtensions(JSContext *ctx, JSValueConst obj);
+int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags);
+int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val);
+JSValueConst JS_GetPrototype(JSContext *ctx, JSValueConst val);
+
+#define JS_GPN_STRING_MASK  (1 << 0)
+#define JS_GPN_SYMBOL_MASK  (1 << 1)
+#define JS_GPN_PRIVATE_MASK (1 << 2)
+/* only include the enumerable properties */
+#define JS_GPN_ENUM_ONLY    (1 << 4)
+/* set theJSPropertyEnum.is_enumerable field */
+#define JS_GPN_SET_ENUM     (1 << 5)
+
+int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab,
+                           uint32_t *plen, JSValueConst obj, int flags);
+int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc,
+                      JSValueConst obj, JSAtom prop);
+
+JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len,
+                     const char *filename);
+JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj,
+                int argc, JSValueConst *argv);
+JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom,
+                  int argc, JSValueConst *argv);
+JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj,
+                           int argc, JSValueConst *argv);
+JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj,
+                            JSValueConst new_target,
+                            int argc, JSValueConst *argv);
+JS_BOOL JS_DetectModule(const char *input, size_t input_len);
+JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,
+                const char *filename, int eval_flags);
+JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj);
+JSValue JS_GetGlobalObject(JSContext *ctx);
+int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj);
+int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj,
+                      JSAtom prop, JSValueConst val,
+                      JSValueConst getter, JSValueConst setter, int flags);
+int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj,
+                           JSAtom prop, JSValue val, int flags);
+int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj,
+                                 uint32_t idx, JSValue val, int flags);
+int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj,
+                              const char *prop, JSValue val, int flags);
+int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj,
+                            JSAtom prop, JSValue getter, JSValue setter,
+                            int flags);
+void JS_SetOpaque(JSValue obj, void *opaque);
+void *JS_GetOpaque(JSValueConst obj, JSClassID class_id);
+void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id);
+
+typedef void JSFreeArrayBufferDataFunc(JSRuntime *rt, void *opaque, void *ptr);
+JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len,
+                          JSFreeArrayBufferDataFunc *free_func, void *opaque,
+                          JS_BOOL is_shared);
+JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len);
+void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj);
+uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj);
+
+JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs);
+
+/* return != 0 if the JS code needs to be interrupted */
+typedef int JSInterruptHandler(JSRuntime *rt, void *opaque);
+void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque);
+/* if can_block is TRUE, Atomics.wait() can be used */
+void JS_SetCanBlock(JSRuntime *rt, JS_BOOL can_block);
+
+typedef struct JSModuleDef JSModuleDef;
+
+/* return the module specifier (allocated with js_malloc()) or NULL if
+   exception */
+typedef char *JSModuleNormalizeFunc(JSContext *ctx,
+                                    const char *module_base_name,
+                                    const char *module_name, void *opaque);
+typedef JSModuleDef *JSModuleLoaderFunc(JSContext *ctx,
+                                        const char *module_name, void *opaque);
+
+/* module_normalize = NULL is allowed and invokes the default module
+   filename normalizer */
+void JS_SetModuleLoaderFunc(JSRuntime *rt,
+                            JSModuleNormalizeFunc *module_normalize,
+                            JSModuleLoaderFunc *module_loader, void *opaque);
+/* return the import.meta object of a module */
+JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m);
+JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m);
+
+/* JS Job support */
+
+typedef JSValue JSJobFunc(JSContext *ctx, int argc, JSValueConst *argv);
+int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, int argc, JSValueConst *argv);
+
+JS_BOOL JS_IsJobPending(JSRuntime *rt);
+int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx);
+
+/* Object Writer/Reader (currently only used to handle precompiled code) */
+#define JS_WRITE_OBJ_BYTECODE (1 << 0) /* allow function/module */
+#define JS_WRITE_OBJ_BSWAP    (1 << 1) /* byte swapped output */
+uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj,
+                        int flags);
+#define JS_READ_OBJ_BYTECODE  (1 << 0) /* allow function/module */
+#define JS_READ_OBJ_ROM_DATA  (1 << 1) /* avoid duplicating 'buf' data */
+JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len,
+                      int flags);
+/* load the dependencies of the module 'obj'. Useful when JS_ReadObject()
+   returns a module. */
+int JS_ResolveModule(JSContext *ctx, JSValueConst obj);
+
+/* C function definition */
+typedef enum JSCFunctionEnum {  /* XXX: should rename for namespace isolation */
+    JS_CFUNC_generic,
+    JS_CFUNC_generic_magic,
+    JS_CFUNC_constructor,
+    JS_CFUNC_constructor_magic,
+    JS_CFUNC_constructor_or_func,
+    JS_CFUNC_constructor_or_func_magic,
+    JS_CFUNC_f_f,
+    JS_CFUNC_f_f_f,
+    JS_CFUNC_getter,
+    JS_CFUNC_setter,
+    JS_CFUNC_getter_magic,
+    JS_CFUNC_setter_magic,
+    JS_CFUNC_iterator_next,
+} JSCFunctionEnum;
+
+typedef union JSCFunctionType {
+    JSCFunction *generic;
+    JSValue (*generic_magic)(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic);
+    JSCFunction *constructor;
+    JSValue (*constructor_magic)(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic);
+    JSCFunction *constructor_or_func;
+    double (*f_f)(double);
+    double (*f_f_f)(double, double);
+    JSValue (*getter)(JSContext *ctx, JSValueConst this_val);
+    JSValue (*setter)(JSContext *ctx, JSValueConst this_val, JSValueConst val);
+    JSValue (*getter_magic)(JSContext *ctx, JSValueConst this_val, int magic);
+    JSValue (*setter_magic)(JSContext *ctx, JSValueConst this_val, JSValueConst val, int magic);
+    JSValue (*iterator_next)(JSContext *ctx, JSValueConst this_val,
+                             int argc, JSValueConst *argv, int *pdone, int magic);
+} JSCFunctionType;
+
+JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func,
+                         const char *name,
+                         int length, JSCFunctionEnum cproto, int magic);
+JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func,
+                            int length, int magic, int data_len,
+                            JSValueConst *data);
+
+static inline JSValue JS_NewCFunction(JSContext *ctx, JSCFunction *func, const char *name,
+                                      int length)
+{
+    return JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_generic, 0);
+}
+
+static inline JSValue JS_NewCFunctionMagic(JSContext *ctx, JSCFunctionMagic *func,
+                                           const char *name,
+                                           int length, JSCFunctionEnum cproto, int magic)
+{
+    return JS_NewCFunction2(ctx, (JSCFunction *)func, name, length, cproto, magic);
+}
+
+/* C property definition */
+
+typedef struct JSCFunctionListEntry {
+    const char *name;
+    uint8_t prop_flags;
+    uint8_t def_type;
+    int16_t magic;
+    union {
+        struct {
+            uint8_t length; /* XXX: should move outside union */
+            uint8_t cproto; /* XXX: should move outside union */
+            JSCFunctionType cfunc;
+        } func;
+        struct {
+            JSCFunctionType get;
+            JSCFunctionType set;
+        } getset;
+        struct {
+            const char *name;
+            int base;
+        } alias;
+        struct {
+            const struct JSCFunctionListEntry *tab;
+            int len;
+        } prop_list;
+        const char *str;
+        int32_t i32;
+        int64_t i64;
+        double f64;
+    } u;
+} JSCFunctionListEntry;
+
+#define JS_DEF_CFUNC          0
+#define JS_DEF_CGETSET        1
+#define JS_DEF_CGETSET_MAGIC  2
+#define JS_DEF_PROP_STRING    3
+#define JS_DEF_PROP_INT32     4
+#define JS_DEF_PROP_INT64     5
+#define JS_DEF_PROP_DOUBLE    6
+#define JS_DEF_PROP_UNDEFINED  7
+#define JS_DEF_OBJECT         8
+#define JS_DEF_ALIAS          9
+
+#define JS_CFUNC_DEF(name, length, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, 0, .u.func = { length, JS_CFUNC_generic, { .generic = func1 } } }
+#define JS_CFUNC_MAGIC_DEF(name, length, func1, magic) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, magic, .u.func = { length, JS_CFUNC_generic_magic, { .generic_magic = func1 } } }
+#define JS_CFUNC_SPECIAL_DEF(name, length, cproto, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, 0, .u.func = { length, JS_CFUNC_ ## cproto, { .cproto = func1 } } }
+#define JS_ITERATOR_NEXT_DEF(name, length, func1, magic) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, magic, .u.func = { length, JS_CFUNC_iterator_next, { .iterator_next = func1 } } }
+#define JS_CGETSET_DEF(name, fgetter, fsetter) { name, JS_PROP_CONFIGURABLE, JS_DEF_CGETSET, 0, .u.getset.get.getter = fgetter, .u.getset.set.setter = fsetter }
+#define JS_CGETSET_MAGIC_DEF(name, fgetter, fsetter, magic) { name, JS_PROP_CONFIGURABLE, JS_DEF_CGETSET_MAGIC, magic, .u.getset.get.getter_magic = fgetter, .u.getset.set.setter_magic = fsetter }
+#define JS_PROP_STRING_DEF(name, cstr, prop_flags) { name, prop_flags, JS_DEF_PROP_STRING, 0, .u.str = cstr }
+#define JS_PROP_INT32_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_INT32, 0, .u.i32 = val }
+#define JS_PROP_INT64_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_INT64, 0, .u.i64 = val }
+#define JS_PROP_DOUBLE_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_DOUBLE, 0, .u.f64 = val }
+#define JS_PROP_UNDEFINED_DEF(name, prop_flags) { name, prop_flags, JS_DEF_PROP_UNDEFINED, 0, .u.i32 = 0 }
+#define JS_OBJECT_DEF(name, tab, len, prop_flags) { name, prop_flags, JS_DEF_OBJECT, 0, .u.prop_list = { tab, len } }
+#define JS_ALIAS_DEF(name, from) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_ALIAS, 0, .u.alias = { from, -1 } }
+#define JS_ALIAS_BASE_DEF(name, from, base) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_ALIAS, 0, .u.alias = { from, base } }
+
+void JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj,
+                                const JSCFunctionListEntry *tab,
+                                int len);
+
+/* C module definition */
+
+typedef int JSModuleInitFunc(JSContext *ctx, JSModuleDef *m);
+
+JSModuleDef *JS_NewCModule(JSContext *ctx, const char *name_str,
+                           JSModuleInitFunc *func);
+/* can only be called before the module is instantiated */
+int JS_AddModuleExport(JSContext *ctx, JSModuleDef *m, const char *name_str);
+int JS_AddModuleExportList(JSContext *ctx, JSModuleDef *m,
+                           const JSCFunctionListEntry *tab, int len);
+/* can only be called after the module is instantiated */
+int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name,
+                       JSValue val);
+int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
+                           const JSCFunctionListEntry *tab, int len);
+
+#undef js_unlikely
+#undef js_force_inline
+
+#ifdef __cplusplus
+} /* extern "C" { */
+#endif
+
+#endif /* QUICKJS_H */
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..789521d
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1 @@
+The main documentation is in doc/quickjs.pdf or doc/quickjs.html.
diff --git a/release.sh b/release.sh
new file mode 100755
index 0000000..7ff0272
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,131 @@
+#!/bin/sh
+# Release the QuickJS source code
+
+set -e
+
+version=`cat VERSION`
+
+if [ "$1" = "-h" ] ; then
+    echo "release.sh [all]"
+    echo ""
+    echo "all: build all the archives. Otherwise only build the quickjs source archive."
+    exit 1
+fi
+
+unicode="no"
+tests="no"
+binary="no"
+quickjs="no"
+
+if [ "$1" = "all" ] ; then
+    unicode="yes"
+    tests="yes"
+    binary="yes"
+    quickjs="yes"
+elif [ "$1" = "binary" ] ; then
+    binary="yes"
+else
+    quickjs="yes"
+fi
+
+#################################################"
+# unicode data
+
+if [ "$unicode" = "yes" ] ; then
+
+d="quickjs-${version}"
+name="quickjs-unicode-data-${version}"
+outdir="/tmp/${d}"
+
+rm -rf $outdir
+mkdir -p $outdir $outdir/unicode
+
+cp unicode/* $outdir/unicode
+
+( cd /tmp && tar Jcvf /tmp/${name}.tar.xz ${d} )
+
+fi
+
+#################################################"
+# all tests
+
+if [ "$tests" = "yes" ] ; then
+
+d="quickjs-${version}"
+name="quickjs-tests-${version}"
+outdir="/tmp/${d}"
+
+rm -rf $outdir
+mkdir -p $outdir $outdir/test262o $outdir/test262 $outdir/tests 
+
+cp -a test262o/test $outdir/test262o
+
+cp -a test262/test test262/harness $outdir/test262
+
+cp -a tests/bench-v8 $outdir/tests
+
+( cd /tmp && tar Jcvf /tmp/${name}.tar.xz ${d} )
+
+fi
+
+#################################################"
+# binary release
+
+if [ "$binary" = "yes" ] ; then
+
+d="quickjs-linux-x86_64-${version}"
+name="quickjs-linux-x86_64-${version}"
+outdir="/tmp/${d}"
+
+rm -rf $outdir
+mkdir -p $outdir
+
+files="qjs qjsbn run-test262 run-test262-bn"
+
+make -j4 $files
+
+strip $files
+cp $files $outdir
+
+( cd /tmp/$d && rm -f ../${name}.zip && zip -r ../${name}.zip . )
+
+fi
+
+#################################################"
+# quickjs
+
+if [ "$quickjs" = "yes" ] ; then
+
+make build_doc
+
+d="quickjs-${version}"
+outdir="/tmp/${d}"
+
+rm -rf $outdir
+mkdir -p $outdir $outdir/doc $outdir/tests $outdir/examples
+
+cp Makefile VERSION TODO Changelog readme.txt release.sh unicode_download.sh \
+   qjs.c qjsc.c qjscalc.js repl.js \
+   quickjs.c quickjs.h quickjs-atom.h \
+   quickjs-libc.c quickjs-libc.h quickjs-opcode.h \
+   cutils.c cutils.h list.h \
+   libregexp.c libregexp.h libregexp-opcode.h \
+   libunicode.c libunicode.h libunicode-table.h \
+   libbf.c libbf.h \
+   jscompress.c unicode_gen.c unicode_gen_def.h \
+   bjson.c \
+   run-test262.c test262o.conf test262.conf test262bn.conf \
+   test262o_errors.txt test262_errors.txt test262bn_errors.txt \
+   $outdir
+
+cp tests/*.js tests/*.patch $outdir/tests
+
+cp examples/*.js examples/*.c $outdir/examples
+
+cp doc/quickjs.texi doc/quickjs.pdf doc/quickjs.html \
+   doc/jsbignum.texi doc/jsbignum.html doc/jsbignum.pdf \
+   $outdir/doc 
+
+( cd /tmp && tar Jcvf /tmp/${d}.tar.xz ${d} )
+
+fi
diff --git a/repl.js b/repl.js
new file mode 100644
index 0000000..b51d2d6
--- /dev/null
+++ b/repl.js
@@ -0,0 +1,1507 @@
+/*
+ * QuickJS Read Eval Print Loop
+ * 
+ * Copyright (c) 2017-2019 Fabrice Bellard
+ * Copyright (c) 2017-2019 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+"use strip";
+
+import * as std from "std";
+import * as os from "os";
+
+(function(g) {
+    /* add 'os' and 'std' bindings */
+    g.os = os;
+    g.std = std;
+    
+    /* close global objects */
+    var Object = g.Object;
+    var String = g.String;
+    var Array = g.Array;
+    var Date = g.Date;
+    var Math = g.Math;
+    var isFinite = g.isFinite;
+    var parseFloat = g.parseFloat;
+
+    /* XXX: use preprocessor ? */
+    var config_numcalc = (typeof os.open === "undefined");
+    var has_jscalc = (typeof Fraction === "function");
+    var has_bignum = (typeof BigFloat === "function");
+    
+    var colors = {
+        none:    "\x1b[0m",
+        black:   "\x1b[30m",
+        red:     "\x1b[31m",
+        green:   "\x1b[32m",
+        yellow:  "\x1b[33m",
+        blue:    "\x1b[34m",
+        magenta: "\x1b[35m",
+        cyan:    "\x1b[36m",
+        white:   "\x1b[37m",
+        gray:    "\x1b[30;1m",
+        grey:    "\x1b[30;1m",
+        bright_red:     "\x1b[31;1m",
+        bright_green:   "\x1b[32;1m",
+        bright_yellow:  "\x1b[33;1m",
+        bright_blue:    "\x1b[34;1m",
+        bright_magenta: "\x1b[35;1m",
+        bright_cyan:    "\x1b[36;1m",
+        bright_white:   "\x1b[37;1m",
+    };
+
+    var styles;
+    if (config_numcalc) {
+        styles = {
+            'default':    'black',
+            'comment':    'white',
+            'string':     'green',
+            'regex':      'cyan',
+            'number':     'green',
+            'keyword':    'blue',
+            'function':   'gray',
+            'type':       'bright_magenta',
+            'identifier': 'yellow',
+            'error':      'bright_red',
+            'result':     'black',
+            'error_msg':  'bright_red',
+        };
+    } else {
+        styles = {
+            'default':    'bright_green',
+            'comment':    'white',
+            'string':     'bright_cyan',
+            'regex':      'cyan',
+            'number':     'green',
+            'keyword':    'bright_white',
+            'function':   'bright_yellow',
+            'type':       'bright_magenta',
+            'identifier': 'bright_green',
+            'error':      'red',
+            'result':     'bright_white',
+            'error_msg':  'bright_red',
+        };
+    }
+
+    var history = [];
+    var clip_board = "";
+    var prec;
+    var expBits;
+    var log2_10;
+    
+    var pstate = "";
+    var prompt = "";
+    var plen = 0;
+    var ps1;
+    if (config_numcalc)
+        ps1 = "> ";
+    else
+        ps1 = "qjs > ";
+    var ps2 = "  ... ";
+    var utf8 = true;
+    var show_time = false;
+    var show_colors = true;
+    var eval_time = 0;
+    
+    var mexpr = "";
+    var level = 0;
+    var cmd = "";
+    var cursor_pos = 0;
+    var last_cmd = "";
+    var last_cursor_pos = 0;
+    var history_index;
+    var this_fun, last_fun;
+    var quote_flag = false;
+
+    var utf8_state = 0;
+    var utf8_val = 0;
+
+    var term_fd;
+    var term_read_buf;
+    var term_width;
+    /* current X position of the cursor in the terminal */
+    var term_cursor_x = 0; 
+    
+    function termInit() {
+        var tab;
+        term_fd = std.in.fileno();
+        
+        /* get the terminal size */
+        term_width = 80;
+        if (os.isatty(term_fd)) {
+            if (os.ttyGetWinSize) {
+                tab = os.ttyGetWinSize(term_fd);
+                if (tab)
+                    term_width = tab[0];
+            }
+            if (os.ttySetRaw) {
+                /* set the TTY to raw mode */
+                os.ttySetRaw(term_fd);
+            }
+        }
+
+        /* install a Ctrl-C signal handler */
+        os.signal(os.SIGINT, sigint_handler);
+
+        /* install a handler to read stdin */
+        term_read_buf = new Uint8Array(64);
+        os.setReadHandler(term_fd, term_read_handler);
+    }
+
+    function sigint_handler() {
+        /* send Ctrl-C to readline */
+        handle_byte(3);
+    }
+    
+    function term_read_handler() {
+        var l, i;
+        l = os.read(term_fd, term_read_buf.buffer, 0, term_read_buf.length);
+        for(i = 0; i < l; i++)
+            handle_byte(term_read_buf[i]);
+    }
+    
+    function handle_byte(c) {
+        if (!utf8) {
+            handle_char(c);
+        } else if (utf8_state !== 0 && (c >= 0x80 && c < 0xc0)) {
+            utf8_val = (utf8_val << 6) | (c & 0x3F);
+            utf8_state--;
+            if (utf8_state === 0) {
+                handle_char(utf8_val);
+            }
+        } else if (c >= 0xc0 && c < 0xf8) {
+            utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0);
+            utf8_val = c & ((1 << (6 - utf8_state)) - 1);
+        } else {
+            utf8_state = 0;
+            handle_char(c);
+        }
+    }
+    
+    function is_alpha(c) {
+        return typeof c === "string" &&
+            ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
+    }
+    
+    function is_digit(c) {
+        return typeof c === "string" && (c >= '0' && c <= '9');
+    }
+
+    function is_word(c) {
+        return typeof c === "string" &&
+            (is_alpha(c) || is_digit(c) || c == '_' || c == '$');
+    }
+
+    function is_balanced(a, b) {
+        switch (a + b) {
+        case "()":
+        case "[]":
+        case "{}":
+            return true;
+        }
+        return false;
+    }
+
+    function print_color_text(str, start, style_names) {
+        var i, j;
+        for (j = start; j < str.length;) {
+            var style = style_names[i = j];
+            while (++j < str.length && style_names[j] == style)
+                continue;
+            std.puts(colors[styles[style] || 'default']);
+            std.puts(str.substring(i, j));
+            std.puts(colors['none']);
+        }
+    }
+
+    function print_csi(n, code) {
+        std.puts("\x1b[" + ((n != 1) ? n : "") + code);
+    }
+
+    function move_cursor(delta) {
+        var i, l;
+        if (delta > 0) {
+            while (delta != 0) {
+                if (term_cursor_x == (term_width - 1)) {
+                    std.puts("\n"); /* translated to CRLF */
+                    term_cursor_x = 0;
+                    delta--;
+                } else {
+                    l = Math.min(term_width - 1 - term_cursor_x, delta);
+                    print_csi(l, "C"); /* right */
+                    delta -= l; 
+                    term_cursor_x += l;
+                }
+            }
+        } else {
+            delta = -delta;
+            while (delta != 0) {
+                if (term_cursor_x == 0) {
+                    print_csi(1, "A"); /* up */
+                    print_csi(term_width - 1, "C"); /* right */
+                    delta--;
+                    term_cursor_x = term_width - 1;
+                } else {
+                    l = Math.min(delta, term_cursor_x);
+                    print_csi(l, "D"); /* left */
+                    delta -= l;
+                    term_cursor_x -= l;
+                }
+            }
+        }
+    }
+
+    function update() {
+        var i;
+
+        if (cmd != last_cmd) {
+            if (!show_colors && last_cmd.substring(0, last_cursor_pos) == cmd.substring(0, last_cursor_pos)) {
+                /* optimize common case */
+                std.puts(cmd.substring(last_cursor_pos));
+            } else {
+                /* goto the start of the line */
+                move_cursor(-last_cursor_pos);
+                if (show_colors) {
+                    var str = mexpr ? mexpr + '\n' + cmd : cmd;
+                    var start = str.length - cmd.length;
+                    var colorstate = colorize_js(str);
+                    print_color_text(str, start, colorstate[2]);
+                } else {
+                    std.puts(cmd);
+                }
+            }
+            /* Note: assuming no surrogate pairs */
+            term_cursor_x = (term_cursor_x + cmd.length) % term_width;
+            if (term_cursor_x == 0) {
+                /* show the cursor on the next line */
+                std.puts(" \x08");
+            }
+            /* remove the trailing characters */
+            std.puts("\x1b[J");
+            last_cmd = cmd;
+            last_cursor_pos = cmd.length;
+        }
+        move_cursor(cursor_pos - last_cursor_pos);
+        last_cursor_pos = cursor_pos;
+        std.out.flush();
+    }
+
+    /* editing commands */
+    function insert(str) {
+        if (str) {
+            cmd = cmd.substring(0, cursor_pos) + str + cmd.substring(cursor_pos);
+            cursor_pos += str.length;
+        }
+    }
+
+    function quoted_insert() {
+        quote_flag = true;
+    }
+
+    function abort() {
+        cmd = "";
+        cursor_pos = 0;
+        return -2;
+    }
+
+    function alert() {
+    }
+
+    function beginning_of_line() {
+        cursor_pos = 0;
+    }
+
+    function end_of_line() {
+        cursor_pos = cmd.length;
+    }
+
+    function forward_char() {
+        if (cursor_pos < cmd.length)
+            cursor_pos++;
+    }
+
+    function backward_char() {
+        if (cursor_pos > 0)
+            cursor_pos--;
+    }
+
+    function skip_word_forward(pos) {
+        while (pos < cmd.length && !is_word(cmd.charAt(pos)))
+            pos++;
+        while (pos < cmd.length && is_word(cmd.charAt(pos)))
+            pos++;
+        return pos;
+    }
+
+    function skip_word_backward(pos) {
+        while (pos > 0 && !is_word(cmd.charAt(pos - 1)))
+            pos--;
+        while (pos > 0 && is_word(cmd.charAt(pos - 1)))
+            pos--;
+        return pos;
+    }
+
+    function forward_word() {
+        cursor_pos = skip_word_forward(cursor_pos);
+    }
+
+    function backward_word() {
+        cursor_pos = skip_word_backward(cursor_pos);
+    }        
+
+    function accept_line() {
+        std.puts("\n");
+        history_add(cmd);
+        return -1;
+    }
+
+    function history_add(str) {
+        if (str) {
+            history.push(str);
+        }
+        history_index = history.length;
+    }
+
+    function previous_history() {
+        if (history_index > 0) {
+            if (history_index == history.length) {
+                history.push(cmd);
+            }
+            history_index--;
+            cmd = history[history_index];
+            cursor_pos = cmd.length;
+        }
+    }
+
+    function next_history() {
+        if (history_index < history.length - 1) {
+            history_index++;
+            cmd = history[history_index];
+            cursor_pos = cmd.length;
+        }
+    }
+
+    function history_search(dir) {
+        var pos = cursor_pos;
+        for (var i = 1; i <= history.length; i++) {
+            var index = (history.length + i * dir + history_index) % history.length;
+            if (history[index].substring(0, pos) == cmd.substring(0, pos)) {
+                history_index = index;
+                cmd = history[index];
+                return;
+            }
+        }
+    }
+
+    function history_search_backward() {
+        return history_search(-1);
+    }
+
+    function history_search_forward() {
+        return history_search(1);
+    }
+
+    function delete_char_dir(dir) {
+        var start = cursor_pos - (dir < 0);
+        var end = start + 1;
+        if (start >= 0 && start < cmd.length) {
+            if (last_fun === kill_region) {
+                kill_region(start, end, dir);
+            } else {
+                cmd = cmd.substring(0, start) + cmd.substring(end);
+                cursor_pos = start;
+            }
+        }
+    }
+
+    function delete_char() {
+        delete_char_dir(1);
+    }
+
+    function control_d() {
+        if (cmd.length == 0) {
+            std.puts("\n");
+            return -3; /* exit read eval print loop */
+        } else {
+            delete_char_dir(1);
+        }
+    }
+
+    function backward_delete_char() {
+        delete_char_dir(-1);
+    }
+
+    function transpose_chars() {
+        var pos = cursor_pos;
+        if (cmd.length > 1 && pos > 0) {
+            if (pos == cmd.length)
+                pos--;
+            cmd = cmd.substring(0, pos - 1) + cmd.substring(pos, pos + 1) +
+                cmd.substring(pos - 1, pos) + cmd.substring(pos + 1);
+            cursor_pos = pos + 1;
+        }
+    }
+
+    function transpose_words() {
+        var p1 = skip_word_backward(cursor_pos);
+        var p2 = skip_word_forward(p1);
+        var p4 = skip_word_forward(cursor_pos);
+        var p3 = skip_word_backward(p4);
+
+        if (p1 < p2 && p2 <= cursor_pos && cursor_pos <= p3 && p3 < p4) {
+            cmd = cmd.substring(0, p1) + cmd.substring(p3, p4) +
+            cmd.substring(p2, p3) + cmd.substring(p1, p2);
+            cursor_pos = p4;
+        }
+    }
+
+    function upcase_word() {
+        var end = skip_word_forward(cursor_pos);
+        cmd = cmd.substring(0, cursor_pos) +
+            cmd.substring(cursor_pos, end).toUpperCase() +
+            cmd.substring(end);
+    }
+
+    function downcase_word() {
+        var end = skip_word_forward(cursor_pos);
+        cmd = cmd.substring(0, cursor_pos) +
+            cmd.substring(cursor_pos, end).toLowerCase() +
+            cmd.substring(end);
+    }
+
+    function kill_region(start, end, dir) {
+        var s = cmd.substring(start, end);
+        if (last_fun !== kill_region)
+            clip_board = s;
+        else if (dir < 0)
+            clip_board = s + clip_board;
+        else
+            clip_board = clip_board + s;
+
+        cmd = cmd.substring(0, start) + cmd.substring(end);
+        if (cursor_pos > end)
+            cursor_pos -= end - start;
+        else if (cursor_pos > start)
+            cursor_pos = start;
+        this_fun = kill_region;
+    }
+
+    function kill_line() {
+        kill_region(cursor_pos, cmd.length, 1);
+    }
+
+    function backward_kill_line() {
+        kill_region(0, cursor_pos, -1);
+    }
+
+    function kill_word() {
+        kill_region(cursor_pos, skip_word_forward(cursor_pos), 1);
+    }
+
+    function backward_kill_word() {
+        kill_region(skip_word_backward(cursor_pos), cursor_pos, -1);
+    }
+
+    function yank() {
+        insert(clip_board);
+    }
+
+    function control_c() {
+        if (last_fun === control_c) {
+            std.puts("\n");
+            std.exit(0);
+        } else {
+            std.puts("\n(Press Ctrl-C again to quit)\n");
+            readline_print_prompt();
+        }
+    }
+    
+    function reset() {
+        cmd = "";
+        cursor_pos = 0;
+    }
+
+    function get_context_word(line, pos) {
+        var s = "";
+        while (pos > 0 && is_word(line[pos - 1])) {
+            pos--;
+            s = line[pos] + s;
+        }
+        return s;
+    }
+    function get_context_object(line, pos) {
+        var obj, base, c;
+        if (pos <= 0 || " ~!%^&*(-+={[|:;,<>?/".indexOf(line[pos - 1]) >= 0)
+            return g;
+        if (pos >= 2 && line[pos - 1] === ".") {
+            pos--;
+            obj = {};
+            switch (c = line[pos - 1]) {
+            case '\'':
+            case '\"':
+                return "a";
+            case ']':
+                return [];
+            case '}':
+                return {};
+            case '/':
+                return / /;
+            default:
+                if (is_word(c)) {
+                    base = get_context_word(line, pos);
+                    if (["true", "false", "null", "this"].includes(base) || !isNaN(+base))
+                        return eval(base);
+                    obj = get_context_object(line, pos - base.length);
+                    if (obj === null || obj === void 0)
+                        return obj;
+                    if (obj === g && obj[base] === void 0)
+                        return eval(base);
+                    else
+                        return obj[base];
+                }
+                return {};
+            }
+        }
+        return void 0;
+    }
+
+    function get_completions(line, pos) {
+        var s, obj, ctx_obj, r, i, j, paren;
+
+        s = get_context_word(line, pos);
+        ctx_obj = get_context_object(line, pos - s.length);
+        r = [];
+        /* enumerate properties from object and its prototype chain,
+           add non-numeric regular properties with s as e prefix
+         */
+        for (i = 0, obj = ctx_obj; i < 10 && obj !== null && obj !== void 0; i++) {
+            var props = Object.getOwnPropertyNames(obj);
+            /* add non-numeric regular properties */
+            for (j = 0; j < props.length; j++) {
+                var prop = props[j];
+                if (typeof prop == "string" && ""+(+prop) != prop && prop.startsWith(s))
+                    r.push(prop);
+            }
+            obj = Object.getPrototypeOf(obj);
+        }
+        if (r.length > 1) {
+            /* sort list with internal names last and remove duplicates */
+            function symcmp(a, b) {
+                if (a[0] != b[0]) {
+                    if (a[0] == '_')
+                        return 1;
+                    if (b[0] == '_')
+                        return -1;
+                }
+                if (a < b)
+                    return -1;
+                if (a > b)
+                    return +1;
+                return 0;
+            }
+            r.sort(symcmp);
+            for(i = j = 1; i < r.length; i++) {
+                if (r[i] != r[i - 1])
+                    r[j++] = r[i];
+            }
+            r.length = j;
+        }
+        /* 'tab' = list of completions, 'pos' = cursor position inside
+           the completions */
+        return { tab: r, pos: s.length, ctx: ctx_obj };
+    }
+
+    function completion() {
+        var tab, res, s, i, j, len, t, max_width, col, n_cols, row, n_rows;
+        res = get_completions(cmd, cursor_pos);
+        tab = res.tab;
+        if (tab.length === 0)
+            return;
+        s = tab[0];
+        len = s.length;
+        /* add the chars which are identical in all the completions */
+        for(i = 1; i < tab.length; i++) {
+            t = tab[i];
+            for(j = 0; j < len; j++) {
+                if (t[j] !== s[j]) {
+                    len = j;
+                    break;
+                }
+            }
+        }
+        for(i = res.pos; i < len; i++) {
+            insert(s[i]);
+        }
+        if (last_fun === completion && tab.length == 1) {
+            /* append parentheses to function names */
+            var m = res.ctx[tab[0]];
+            if (typeof m == "function") {
+                insert('(');
+                if (m.length == 0)
+                    insert(')');
+            } else if (typeof m == "object") {
+                insert('.');
+            }
+        }
+        /* show the possible completions */
+        if (last_fun === completion && tab.length >= 2) {
+            max_width = 0;
+            for(i = 0; i < tab.length; i++)
+                max_width = Math.max(max_width, tab[i].length);
+            max_width += 2;
+            n_cols = Math.max(1, Math.floor((term_width + 1) / max_width));
+            n_rows = Math.ceil(tab.length / n_cols);
+            std.puts("\n");
+            /* display the sorted list column-wise */
+            for (row = 0; row < n_rows; row++) {
+                for (col = 0; col < n_cols; col++) {
+                    i = col * n_rows + row;
+                    if (i >= tab.length)
+                        break;
+                    s = tab[i];
+                    if (col != n_cols - 1)
+                        s = s.padEnd(max_width);
+                    std.puts(s);
+                }
+                std.puts("\n");
+            }
+            /* show a new prompt */
+            readline_print_prompt();
+        }
+    }
+    
+    var commands = {        /* command table */
+        "\x01":     beginning_of_line,      /* ^A - bol */
+        "\x02":     backward_char,          /* ^B - backward-char */
+        "\x03":     control_c,              /* ^C - abort */
+        "\x04":     control_d,              /* ^D - delete-char or exit */
+        "\x05":     end_of_line,            /* ^E - eol */
+        "\x06":     forward_char,           /* ^F - forward-char */
+        "\x07":     abort,                  /* ^G - bell */
+        "\x08":     backward_delete_char,   /* ^H - backspace */
+        "\x09":     completion,             /* ^I - history-search-backward */
+        "\x0a":     accept_line,            /* ^J - newline */
+        "\x0b":     kill_line,              /* ^K - delete to end of line */
+        "\x0d":     accept_line,            /* ^M - enter */
+        "\x0e":     next_history,           /* ^N - down */
+        "\x10":     previous_history,       /* ^P - up */
+        "\x11":     quoted_insert,          /* ^Q - quoted-insert */
+        "\x12":     alert,                  /* ^R - reverse-search */
+        "\x13":     alert,                  /* ^S - search */
+        "\x14":     transpose_chars,        /* ^T - transpose */
+        "\x18":     reset,                  /* ^X - cancel */
+        "\x19":     yank,                   /* ^Y - yank */
+        "\x1bOA":   previous_history,       /* ^[OA - up */
+        "\x1bOB":   next_history,           /* ^[OB - down */
+        "\x1bOC":   forward_char,           /* ^[OC - right */
+        "\x1bOD":   backward_char,          /* ^[OD - left */
+        "\x1bOF":   forward_word,           /* ^[OF - ctrl-right */
+        "\x1bOH":   backward_word,          /* ^[OH - ctrl-left */
+        "\x1b[1;5C": forward_word,          /* ^[[1;5C - ctrl-right */
+        "\x1b[1;5D": backward_word,         /* ^[[1;5D - ctrl-left */
+        "\x1b[1~":  beginning_of_line,      /* ^[[1~ - bol */
+        "\x1b[3~":  delete_char,            /* ^[[3~ - delete */
+        "\x1b[4~":  end_of_line,            /* ^[[4~ - eol */
+        "\x1b[5~":  history_search_backward,/* ^[[5~ - page up */
+        "\x1b[6~":  history_search_forward, /* ^[[5~ - page down */
+        "\x1b[A":   previous_history,       /* ^[[A - up */
+        "\x1b[B":   next_history,           /* ^[[B - down */
+        "\x1b[C":   forward_char,           /* ^[[C - right */
+        "\x1b[D":   backward_char,          /* ^[[D - left */
+        "\x1b[F":   end_of_line,            /* ^[[F - end */
+        "\x1b[H":   beginning_of_line,      /* ^[[H - home */
+        "\x1b\x7f": backward_kill_word,     /* M-C-? - backward_kill_word */
+        "\x1bb":    backward_word,          /* M-b - backward_word */
+        "\x1bd":    kill_word,              /* M-d - kill_word */
+        "\x1bf":    forward_word,           /* M-f - backward_word */
+        "\x1bk":    backward_kill_line,     /* M-k - backward_kill_line */
+        "\x1bl":    downcase_word,          /* M-l - downcase_word */
+        "\x1bt":    transpose_words,        /* M-t - transpose_words */
+        "\x1bu":    upcase_word,            /* M-u - upcase_word */
+        "\x7f":     backward_delete_char,   /* ^? - delete */
+    };
+
+    function dupstr(str, count) {
+        var res = "";
+        while (count-- > 0)
+            res += str;
+        return res;
+    }
+
+    var readline_keys;
+    var readline_state;
+    var readline_cb;
+
+    function readline_print_prompt()
+    {
+        std.puts(prompt);
+        term_cursor_x = prompt.length % term_width;
+        last_cmd = "";
+        last_cursor_pos = 0;
+    }
+
+    function readline_start(defstr, cb) {
+        cmd = defstr || "";
+        cursor_pos = cmd.length;
+        history_index = history.length;
+        readline_cb = cb;
+        
+        prompt = pstate;
+    
+        if (mexpr) {
+            prompt += dupstr(" ", plen - prompt.length);
+            prompt += ps2;
+        } else {
+            if (show_time) {
+                var t = Math.round(eval_time) + " ";
+                eval_time = 0;
+                t = dupstr("0", 5 - t.length) + t;
+                prompt += t.substring(0, t.length - 4) + "." + t.substring(t.length - 4);
+            }
+            plen = prompt.length;
+            prompt += ps1;
+        }
+        readline_print_prompt();
+        update();
+        readline_state = 0;
+    }
+
+    function handle_char(c1) {
+        var c;
+        c = String.fromCharCode(c1);
+        switch(readline_state) {
+        case 0:
+            if (c == '\x1b') {  /* '^[' - ESC */
+                readline_keys = c;
+                readline_state = 1;
+            } else {
+                handle_key(c);
+            }
+            break;
+        case 1: /* '^[ */
+            readline_keys += c;
+            if (c == '[') {
+                readline_state = 2;
+            } else if (c == 'O') {
+                readline_state = 3;
+            } else {
+                handle_key(readline_keys);
+                readline_state = 0;
+            }
+            break;
+        case 2: /* '^[[' - CSI */
+            readline_keys += c;
+            if (!(c == ';' || (c >= '0' && c <= '9'))) {
+                handle_key(readline_keys);
+                readline_state = 0;
+            }
+            break;
+        case 3: /* '^[O' - ESC2 */
+            readline_keys += c;
+            handle_key(readline_keys);
+            readline_state = 0;
+            break;
+        }
+    }
+
+    function handle_key(keys) {
+        var fun;
+
+        if (quote_flag) {
+            if (keys.length === 1)
+                insert(keys);
+            quote_flag = false;
+        } else if (fun = commands[keys]) {
+            this_fun = fun;
+            switch (fun(keys)) {
+            case -1:
+                readline_cb(cmd);
+                return;
+            case -2:
+                readline_cb(null);
+                return;
+            case -3:
+                /* uninstall a Ctrl-C signal handler */
+                os.signal(os.SIGINT, null);
+                /* uninstall the stdin read handler */
+                os.setReadHandler(term_fd, null);
+                return;
+            }
+            last_fun = this_fun;
+        } else if (keys.length === 1 && keys >= ' ') {
+            insert(keys);
+            last_fun = insert;
+        } else {
+            alert(); /* beep! */
+        }
+        
+        cursor_pos = (cursor_pos < 0) ? 0 :
+            (cursor_pos > cmd.length) ? cmd.length : cursor_pos;
+        update();
+    }
+
+    var hex_mode = false;
+    var eval_mode = "std";
+
+    function bignum_typeof(a) {
+        "use bigint";
+        return typeof a;
+    }
+
+    function eval_mode_typeof(a) {
+        if (eval_mode === "std")
+            return typeof a;
+        else
+            return bignum_typeof(a);
+    }
+    
+    function number_to_string(a, radix) {
+        var s;
+        if (!isFinite(a)) {
+            /* NaN, Infinite */
+            if (typeof a === "bigfloat" && eval_mode !== "math") {
+                return "BigFloat(" + a.toString() + ")";
+            } else {
+                return a.toString();
+            }
+        } else {
+            if (a == 0) {
+                if (1 / a < 0)
+                    s = "-0";
+                else
+                    s = "0";
+            } else {
+                if (radix == 16) {
+                    var s;
+                    if (a < 0) {
+                        a = -a;
+                        s = "-";
+                    } else {
+                        s = "";
+                    }
+                    s += "0x" + a.toString(16);
+                } else {
+                    s = a.toString();
+                }
+            }
+            if (typeof a === "bigfloat" && eval_mode !== "math") {
+                s += "l";
+            } else if (eval_mode !== "std" && s.indexOf(".") < 0 &&
+                ((radix == 16 && s.indexOf("p") < 0) ||
+                 (radix == 10 && s.indexOf("e") < 0))) {
+                /* add a decimal point so that the floating point type
+                   is visible */
+                s += ".0";
+            }
+            return s;
+        }
+    }
+
+    function bigint_to_string(a, radix) {
+        var s;
+        if (radix == 16) {
+            var s;
+            if (a < 0) {
+                a = -a;
+                s = "-";
+            } else {
+                s = "";
+            }
+            s += "0x" + a.toString(16);
+        } else {
+            s = a.toString();
+        }
+        if (eval_mode === "std")
+            s += "n";
+        return s;
+    }
+    
+    function print(a) {
+        var stack = [];
+
+        function print_rec(a) {
+            var n, i, keys, key, type, s;
+            
+            type = eval_mode_typeof(a);
+            if (type === "object") {
+                if (a === null) {
+                    std.puts(a);
+                } else if (stack.indexOf(a) >= 0) {
+                    std.puts("[circular]");
+                } else if (has_jscalc && (a instanceof Fraction ||
+                                        a instanceof Complex ||
+                                        a instanceof Mod ||
+                                        a instanceof Polynomial ||
+                                        a instanceof PolyMod ||
+                                        a instanceof RationalFunction ||
+                                        a instanceof Series)) {
+                    std.puts(a.toString());
+                } else {
+                    stack.push(a);
+                    if (Array.isArray(a)) {
+                        n = a.length;
+                        std.puts("[ ");
+                        for(i = 0; i < n; i++) {
+                            if (i !== 0)
+                                std.puts(", ");
+                            if (i in a) {
+                                print_rec(a[i]);
+                            } else {
+                                std.puts("<empty>");
+                            }
+                            if (i > 20) {
+                                std.puts("...");
+                                break;
+                            }
+                        }
+                        std.puts(" ]");
+                    } else if (Object.__getClass(a) === "RegExp") {
+                        std.puts(a.toString());
+                    } else {
+                        keys = Object.keys(a);
+                        n = keys.length;
+                        std.puts("{ ");
+                        for(i = 0; i < n; i++) {
+                            if (i !== 0)
+                                std.puts(", ");
+                            key = keys[i];
+                            std.puts(key, ": ");
+                            print_rec(a[key]);
+                        }
+                        std.puts(" }");
+                    }
+                    stack.pop(a);
+                }
+            } else if (type === "string") {
+                s = a.__quote();
+                if (s.length > 79)
+                    s = s.substring(0, 75) + "...\"";
+                std.puts(s);
+            } else if (type === "number" || type === "bigfloat") {
+                std.puts(number_to_string(a, hex_mode ? 16 : 10));
+            } else if (type === "bigint") {
+                std.puts(bigint_to_string(a, hex_mode ? 16 : 10));
+            } else if (type === "symbol") {
+                std.puts(String(a));
+            } else if (type === "function") {
+                std.puts("function " + a.name + "()");
+            } else {
+                std.puts(a);
+            }
+        }
+        print_rec(a);
+    }
+    
+    function extract_directive(a) {
+        var pos;
+        if (a[0] !== '\\')
+            return "";
+        for (pos = 1; pos < a.length; pos++) {
+            if (!is_alpha(a[pos]))
+                break;
+        }
+        return a.substring(1, pos);
+    }
+
+    /* return true if the string after cmd can be evaluted as JS */
+    function handle_directive(cmd, expr) {
+        var param, prec1, expBits1;
+        
+        if (cmd === "h" || cmd === "?" || cmd == "help") {
+            help();
+        } else if (cmd === "load") {
+            var filename = expr.substring(cmd.length + 1).trim();
+            if (filename.lastIndexOf(".") <= filename.lastIndexOf("/"))
+                filename += ".js";
+            std.loadScript(filename);
+            return false;
+        } else if (cmd === "x") {
+            hex_mode = true;
+        } else if (cmd === "d") {
+            hex_mode = false;
+        } else if (cmd === "t") {
+            show_time = !show_time;
+        } else if (has_bignum && cmd === "p") {
+            param = expr.substring(cmd.length + 1).trim().split(" ");
+            if (param.length === 1 && param[0] === "") {
+                std.puts("BigFloat precision=" + prec + " bits (~" +
+                          Math.floor(prec / log2_10) +
+                          " digits), exponent size=" + expBits + " bits\n");
+            } else if (param[0] === "f16") {
+                prec = 11;
+                expBits = 5;
+            } else if (param[0] === "f32") {
+                prec = 24;
+                expBits = 8;
+            } else if (param[0] === "f64") {
+                prec = 53;
+                expBits = 11;
+            } else if (param[0] === "f128") {
+                prec = 113;
+                expBits = 15;
+            } else {
+                prec1 = parseInt(param[0]);
+                if (param.length >= 2)
+                    expBits1 = parseInt(param[1]);
+                else
+                    expBits1 = BigFloatEnv.expBitsMax;
+                if (Number.isNaN(prec1) ||
+                    prec1 < BigFloatEnv.precMin ||
+                    prec1 > BigFloatEnv.precMax) {
+                    std.puts("Invalid precision\n");
+                    return false;
+                }
+                if (Number.isNaN(expBits1) ||
+                    expBits1 < BigFloatEnv.expBitsMin ||
+                    expBits1 > BigFloatEnv.expBitsMax) {
+                    std.puts("Invalid exponent bits\n");
+                    return false;
+                }
+                prec = prec1;
+                expBits = expBits1;
+            }
+            return false;
+        } else if (has_bignum && cmd === "digits") {
+            param = expr.substring(cmd.length + 1).trim();
+            prec1 = Math.ceil(parseFloat(param) * log2_10);
+            if (prec1 < BigFloatEnv.precMin ||
+                prec1 > BigFloatEnv.precMax) {
+                std.puts("Invalid precision\n");
+                return false;
+            }
+            prec = prec1;
+            expBits = BigFloatEnv.expBitsMax;
+            return false;
+        } else if (has_bignum && cmd === "mode") {
+            param = expr.substring(cmd.length + 1).trim();
+            if (param === "") {
+                std.puts("Running mode=" + eval_mode + "\n");
+            } else if (param === "std" || param === "math" ||
+                       param === "bigint") {
+                eval_mode = param;
+            } else {
+                std.puts("Invalid mode\n");
+            }
+            return false;
+        } else if (cmd === "clear") {
+            std.puts("\x1b[H\x1b[J");
+        } else if (cmd === "q") {
+            std.exit(0);
+        } else if (has_jscalc && cmd === "a") {
+            algebraicMode = true;
+        } else if (has_jscalc && cmd === "n") {
+            algebraicMode = false;
+        } else {
+            std.puts("Unknown directive: " + cmd + "\n");
+            return false;
+        }
+        return true;
+    }
+
+    if (config_numcalc) {
+        /* called by the GUI */
+        g.execCmd = function (cmd) {
+            switch(cmd) {
+            case "dec":
+                hex_mode = false;
+                break;
+            case "hex":
+                hex_mode = true;
+                break;
+            case "num":
+                algebraicMode = false;
+                break;
+            case "alg":
+                algebraicMode = true;
+                break;
+            }
+        }
+    }
+    
+    function help() {
+        function sel(n) {
+            return n ? "*": " ";
+        }
+        std.puts("\\h          this help\n" +
+                 "\\x         " + sel(hex_mode) + "hexadecimal number display\n" +
+                 "\\d         " + sel(!hex_mode) + "decimal number display\n" +
+                 "\\t         " + sel(show_time) + "toggle timing display\n" +
+                  "\\clear      clear the terminal\n");
+        if (has_jscalc) {
+            std.puts("\\a         " + sel(algebraicMode) + "algegraic mode\n" +
+                     "\\n         " + sel(!algebraicMode) + "numeric mode\n");
+        }
+        if (has_bignum) {
+            std.puts("\\p [m [e]]  set the BigFloat precision to 'm' bits\n" +
+                     "\\digits n   set the BigFloat precision to 'ceil(n*log2(10))' bits\n");
+            if (!has_jscalc) {
+                std.puts("\\mode [std|bigint|math] change the running mode (current = " + eval_mode + ")\n");
+            }
+        }
+        if (!config_numcalc) {
+            std.puts("\\q          exit\n");
+        }
+    }
+
+    function eval_and_print(expr) {
+        var result;
+        
+        try {
+            if (eval_mode === "math")
+                expr = '"use math"; void 0;' + expr;
+            else if (eval_mode === "bigint")
+                expr = '"use bigint"; void 0;' + expr;
+            var now = (new Date).getTime();
+            /* eval as a script */
+            result = std.evalScript(expr);
+            eval_time = (new Date).getTime() - now;
+            std.puts(colors[styles.result]);
+            print(result);
+            std.puts("\n");
+            std.puts(colors.none);
+            /* set the last result */
+            g._ = result;
+        } catch (error) {
+            std.puts(colors[styles.error_msg]);
+            if (error instanceof Error) {
+                console.log(error);
+                if (error.stack) {
+                    std.puts(error.stack);
+                }
+            } else {
+                std.puts("Throw: ");
+                console.log(error);
+            }
+            std.puts(colors.none);
+        }
+    }
+
+    function cmd_start() {
+        if (!config_numcalc) {
+            if (has_jscalc)
+                std.puts('QJSCalc - Type "\\h" for help\n');
+            else
+                std.puts('QuickJS - Type "\\h" for help\n');
+        }
+        if (has_bignum) {
+            log2_10 = Math.log(10) / Math.log(2);
+            if (has_jscalc) {
+                prec = 113;
+                expBits = 15;
+                eval_mode = "math";
+                /* XXX: numeric mode should always be the default ? */
+                g.algebraicMode = config_numcalc;
+            } else {
+                prec = 53;
+                expBits = 11;
+            }
+        }
+
+        cmd_readline_start();
+    }
+
+    function cmd_readline_start() {
+        readline_start(dupstr("    ", level), readline_handle_cmd);
+    }
+    
+    function readline_handle_cmd(expr) {
+        handle_cmd(expr);
+        cmd_readline_start();
+    }
+
+    function handle_cmd(expr) {
+        var colorstate, cmd;
+        
+        if (expr === null) {
+            expr = "";
+            return;
+        }
+        if (expr === "?") {
+            help();
+            return;
+        }
+        cmd = extract_directive(expr);
+        if (cmd.length > 0) {
+            if (!handle_directive(cmd, expr))
+                return;
+            expr = expr.substring(cmd.length + 1);
+        }
+        if (expr === "")
+            return;
+        
+        if (mexpr)
+            expr = mexpr + '\n' + expr;
+        colorstate = colorize_js(expr);
+        pstate = colorstate[0];
+        level = colorstate[1];
+        if (pstate) {
+            mexpr = expr;
+            return;
+        }
+        mexpr = "";
+        
+        if (has_bignum) {
+            BigFloatEnv.setPrec(eval_and_print.bind(null, expr),
+                                prec, expBits);
+        } else {
+            eval_and_print(expr);
+        }
+        level = 0;
+        
+        /* run the garbage collector after each command */
+        std.gc();
+    }
+
+    function colorize_js(str) {
+        var i, c, start, n = str.length;
+        var style, state = "", level = 0;
+        var primary, can_regex = 1;
+        var r = [];
+
+        function push_state(c) { state += c; }
+        function last_state(c) { return state.substring(state.length - 1); }
+        function pop_state(c) {
+            var c = last_state();
+            state = state.substring(0, state.length - 1);
+            return c;
+        }
+
+        function parse_block_comment() {
+            style = 'comment';
+            push_state('/');
+            for (i++; i < n - 1; i++) {
+                if (str[i] == '*' && str[i + 1] == '/') {
+                    i += 2;
+                    pop_state('/');
+                    break;
+                }
+            }
+        }
+
+        function parse_line_comment() {
+            style = 'comment';
+            for (i++; i < n; i++) {
+                if (str[i] == '\n') {
+                    break;
+                }
+            }
+        }
+
+        function parse_string(delim) {
+            style = 'string';
+            push_state(delim);
+            while (i < n) {
+                c = str[i++];
+                if (c == '\n') {
+                    style = 'error';
+                    continue;
+                }
+                if (c == '\\') {
+                    if (i >= n)
+                        break;
+                    i++;
+                } else
+                if (c == delim) {
+                    pop_state();
+                    break;
+                }
+            }
+        }
+
+        function parse_regex() {
+            style = 'regex';
+            push_state('/');
+            while (i < n) {
+                c = str[i++];
+                if (c == '\n') {
+                    style = 'error';
+                    continue;
+                }
+                if (c == '\\') {
+                    if (i < n) {
+                        i++;
+                    }
+                    continue;
+                }
+                if (last_state() == '[') {
+                    if (c == ']') {
+                        pop_state()
+                    }
+                    // ECMA 5: ignore '/' inside char classes
+                    continue;
+                }
+                if (c == '[') {
+                    push_state('[');
+                    if (str[i] == '[' || str[i] == ']')
+                        i++;
+                    continue;
+                }
+                if (c == '/') {
+                    pop_state();
+                    while (i < n && is_word(str[i]))
+                        i++;
+                    break;
+                }
+            }
+        }
+
+        function parse_number() {
+            style = 'number';
+            while (i < n && (is_word(str[i]) || (str[i] == '.' && (i == n - 1 || str[i + 1] != '.')))) {
+                i++;
+            }
+        }
+
+        var js_keywords = "|" +
+            "break|case|catch|continue|debugger|default|delete|do|" +
+            "else|finally|for|function|if|in|instanceof|new|" +
+            "return|switch|this|throw|try|typeof|while|with|" +
+            "class|const|enum|import|export|extends|super|" +
+            "implements|interface|let|package|private|protected|" +
+            "public|static|yield|" +
+            "undefined|null|true|false|Infinity|NaN|" +
+            "eval|arguments|" +
+            "await|";
+
+        var js_no_regex = "|this|super|undefined|null|true|false|Infinity|NaN|arguments|";
+        var js_types = "|void|var|";
+
+        function parse_identifier() {
+            can_regex = 1;
+
+            while (i < n && is_word(str[i]))
+                i++;
+
+            var w = '|' + str.substring(start, i) + '|';
+
+            if (js_keywords.indexOf(w) >= 0) {
+                style = 'keyword';
+                if (js_no_regex.indexOf(w) >= 0)
+                    can_regex = 0;
+                return;
+            }
+
+            var i1 = i;
+            while (i1 < n && str[i1] == ' ')
+                i1++;
+
+            if (i1 < n && str[i1] == '(') {
+                style = 'function';
+                return;
+            }
+
+            if (js_types.indexOf(w) >= 0) {
+                style = 'type';
+                return;
+            }
+
+            style = 'identifier';
+            can_regex = 0;
+        }
+
+        function set_style(from, to) {
+            while (r.length < from)
+                r.push('default');
+            while (r.length < to)
+                r.push(style);
+        }
+
+        for (i = 0; i < n;) {
+            style = null;
+            start = i;
+            switch (c = str[i++]) {
+            case ' ':
+            case '\t':
+            case '\r':
+            case '\n':
+                continue;
+            case '+':
+            case '-':
+                if (i < n && str[i] == c) {
+                    i++;
+                    continue;
+                }
+                can_regex = 1;
+                continue;
+            case '/':
+                if (i < n && str[i] == '*') { // block comment
+                    parse_block_comment();
+                    break;
+                }
+                if (i < n && str[i] == '/') { // line comment
+                    parse_line_comment();
+                    break;
+                }
+                if (can_regex) {
+                    parse_regex();
+                    can_regex = 0;
+                    break;
+                }
+                can_regex = 1;
+                continue;
+            case '\'':
+            case '\"':
+            case '`':
+                parse_string(c);
+                can_regex = 0;
+                break;
+            case '(':
+            case '[':
+            case '{':
+                can_regex = 1;
+                level++;
+                push_state(c);
+                continue;
+            case ')':
+            case ']':
+            case '}':
+                can_regex = 0;
+                if (level > 0 && is_balanced(last_state(), c)) {
+                    level--;
+                    pop_state();
+                    continue;
+                }
+                style = 'error';
+                break;
+            default:
+                if (is_digit(c)) {
+                    parse_number();
+                    can_regex = 0;
+                    break;
+                }
+                if (is_word(c) || c == '$') {
+                    parse_identifier();
+                    break;
+                }
+                can_regex = 1;
+                continue;
+            }
+            if (style)
+                set_style(start, i);
+        }
+        set_style(n, n);
+        return [ state, level, r ];
+    }
+
+    termInit();
+    
+    cmd_start();
+
+})(globalThis);
diff --git a/run-test262.c b/run-test262.c
new file mode 100644
index 0000000..98f57ed
--- /dev/null
+++ b/run-test262.c
@@ -0,0 +1,2105 @@
+/*
+ * ECMA Test 262 Runner for QuickJS
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ * Copyright (c) 2017-2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <dirent.h>
+#include <ftw.h>
+
+#include "cutils.h"
+#include "list.h"
+#include "quickjs-libc.h"
+
+/* enable test262 thread support to test SharedArrayBuffer and Atomics */
+#define CONFIG_AGENT
+/* cross-realm tests (not supported yet) */
+//#define CONFIG_REALM
+
+#define CMD_NAME "run-test262"
+
+typedef struct namelist_t {
+    char **array;
+    int count;
+    int size;
+    unsigned int sorted : 1;
+} namelist_t;
+
+namelist_t test_list;
+namelist_t exclude_list;
+namelist_t exclude_dir_list;
+
+FILE *outfile;
+enum test_mode_t {
+    TEST_DEFAULT_NOSTRICT, /* run tests as nostrict unless test is flagged as strictonly */
+    TEST_DEFAULT_STRICT,   /* run tests as strict unless test is flagged as nostrict */
+    TEST_NOSTRICT,         /* run tests as nostrict, skip strictonly tests */
+    TEST_STRICT,           /* run tests as strict, skip nostrict tests */
+    TEST_ALL,              /* run tests in both strict and nostrict, unless restricted by spec */
+} test_mode = TEST_DEFAULT_NOSTRICT;
+int skip_async;
+int skip_module;
+int new_style;
+int dump_memory;
+int stats_count;
+JSMemoryUsage stats_all, stats_avg, stats_min, stats_max;
+char *stats_min_filename;
+char *stats_max_filename;
+int verbose;
+char *harness_dir;
+char *harness_exclude;
+char *harness_features;
+char *harness_skip_features;
+char *error_filename;
+char *error_file;
+FILE *error_out;
+char *report_filename;
+int update_errors;
+int test_count, test_failed, test_index, test_skipped, test_excluded;
+int new_errors, changed_errors, fixed_errors;
+int async_done;
+
+void warning(const char *, ...) __attribute__((__format__(__printf__, 1, 2)));
+void fatal(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3)));
+
+void warning(const char *fmt, ...)
+{
+    va_list ap;
+
+    fflush(stdout);
+    fprintf(stderr, "%s: ", CMD_NAME);
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+}
+
+void fatal(int errcode, const char *fmt, ...)
+{
+    va_list ap;
+
+    fflush(stdout);
+    fprintf(stderr, "%s: ", CMD_NAME);
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+
+    exit(errcode);
+}
+
+void perror_exit(int errcode, const char *s)
+{
+    fflush(stdout);
+    fprintf(stderr, "%s: ", CMD_NAME);
+    perror(s);
+    exit(errcode);
+}
+
+char *strdup_len(const char *str, int len)
+{
+    char *p = malloc(len + 1);
+    memcpy(p, str, len);
+    p[len] = '\0';
+    return p;
+}
+
+static inline int str_equal(const char *a, const char *b) {
+    return !strcmp(a, b);
+}
+
+char *str_append(char **pp, const char *sep, const char *str) {
+    char *res, *p;
+    size_t len = 0;
+    p = *pp;
+    if (p) {
+        len = strlen(p) + strlen(sep);
+    }
+    res = malloc(len + strlen(str) + 1);
+    if (p) {
+        strcpy(res, p);
+        strcat(res, sep);
+    }
+    strcpy(res + len, str);
+    free(p);
+    return *pp = res;
+}
+
+char *str_strip(char *p)
+{
+    size_t len = strlen(p);
+    while (len > 0 && isspace((unsigned char)p[len - 1]))
+        p[--len] = '\0';
+    while (isspace((unsigned char)*p))
+        p++;
+    return p;
+}
+
+int has_prefix(const char *str, const char *prefix)
+{
+    return !strncmp(str, prefix, strlen(prefix));
+}
+
+char *skip_prefix(const char *str, const char *prefix)
+{
+    int i;
+    for (i = 0;; i++) {
+        if (prefix[i] == '\0') {  /* skip the prefix */
+            str += i;
+            break;
+        }
+        if (str[i] != prefix[i])
+            break;
+    }
+    return (char *)str;
+}
+
+char *get_basename(const char *filename)
+{
+    char *p;
+
+    p = strrchr(filename, '/');
+    if (!p)
+        return NULL;
+    return strdup_len(filename, p - filename);
+}
+
+char *compose_path(const char *path, const char *name)
+{
+    int path_len, name_len;
+    char *d, *q;
+
+    if (!path || path[0] == '\0' || *name == '/') {
+        d = strdup(name);
+    } else {
+        path_len = strlen(path);
+        name_len = strlen(name);
+        d = malloc(path_len + 1 + name_len + 1);
+        if (d) {
+            q = d;
+            memcpy(q, path, path_len);
+            q += path_len;
+            if (path[path_len - 1] != '/')
+                *q++ = '/';
+            memcpy(q, name, name_len + 1);
+        }
+    }
+    return d;
+}
+
+int namelist_cmp(const char *a, const char *b)
+{
+    /* compare strings in modified lexicographical order */
+    for (;;) {
+        int ca = (unsigned char)*a++;
+        int cb = (unsigned char)*b++;
+        if (isdigit(ca) && isdigit(cb)) {
+            int na = ca - '0';
+            int nb = cb - '0';
+            while (isdigit(ca = (unsigned char)*a++))
+                na = na * 10 + ca - '0';
+            while (isdigit(cb = (unsigned char)*b++))
+                nb = nb * 10 + cb - '0';
+            if (na < nb)
+                return -1;
+            if (na > nb)
+                return +1;
+        }
+        if (ca < cb)
+            return -1;
+        if (ca > cb)
+            return +1;
+        if (ca == '\0')
+            return 0;
+    }
+}
+
+int namelist_cmp_indirect(const void *a, const void *b)
+{
+    return namelist_cmp(*(const char **)a, *(const char **)b);
+}
+
+void namelist_sort(namelist_t *lp)
+{
+    int i, count;
+    if (lp->count > 1) {
+        qsort(lp->array, lp->count, sizeof(*lp->array), namelist_cmp_indirect);
+        /* remove duplicates */
+        for (count = i = 1; i < lp->count; i++) {
+            if (namelist_cmp(lp->array[count - 1], lp->array[i]) == 0) {
+                free(lp->array[i]);
+            } else {
+                lp->array[count++] = lp->array[i];
+            }
+        }
+        lp->count = count;
+    }
+    lp->sorted = 1;
+}
+
+int namelist_find(namelist_t *lp, const char *name)
+{
+    int a, b, m, cmp;
+
+    if (!lp->sorted) {
+        namelist_sort(lp);
+    }
+    for (a = 0, b = lp->count; a < b;) {
+        m = a + (b - a) / 2;
+        cmp = namelist_cmp(lp->array[m], name);
+        if (cmp < 0)
+            a = m + 1;
+        else if (cmp > 0)
+            b = m;
+        else
+            return m;
+    }
+    return -1;
+}
+
+void namelist_add(namelist_t *lp, const char *base, const char *name)
+{
+    char *s;
+
+    s = compose_path(base, name);
+    if (!s)
+        goto fail;
+    if (lp->count == lp->size) {
+        size_t newsize = lp->size + (lp->size >> 1) + 4;
+        char **a = realloc(lp->array, sizeof(lp->array[0]) * newsize);
+        if (!a)
+            goto fail;
+        lp->array = a;
+        lp->size = newsize;
+    }
+    lp->array[lp->count] = s;
+    lp->count++;
+    return;
+fail:
+    fatal(1, "allocation failure\n");
+}
+
+void namelist_load(namelist_t *lp, const char *filename)
+{
+    char buf[1024];
+    char *base_name;
+    FILE *f;
+
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror_exit(1, filename);
+    }
+    base_name = get_basename(filename);
+
+    while (fgets(buf, sizeof(buf), f) != NULL) {
+        char *p = str_strip(buf);
+        if (*p == '#' || *p == ';' || *p == '\0')
+            continue;  /* line comment */
+        
+        namelist_add(lp, base_name, p);
+    }
+    free(base_name);
+    fclose(f);
+}
+
+void namelist_add_from_error_file(namelist_t *lp, const char *file)
+{
+    const char *p, *p0;
+    char *pp;
+
+    for (p = file; (p = strstr(p, ".js:")) != NULL; p++) {
+        for (p0 = p; p0 > file && p0[-1] != '\n'; p0--)
+            continue;
+        pp = strdup_len(p0, p + 3 - p0);
+        namelist_add(lp, NULL, pp);
+        free(pp);
+    }
+}
+
+void namelist_free(namelist_t *lp)
+{
+    while (lp->count > 0) {
+        free(lp->array[--lp->count]);
+    }
+    free(lp->array);
+    lp->array = NULL;
+    lp->size = 0;
+}
+
+static int add_test_file(const char *filename, const struct stat *ptr, int flag)
+{
+    namelist_t *lp = &test_list;
+    if (has_suffix(filename, ".js") && !has_suffix(filename, "_FIXTURE.js"))
+        namelist_add(lp, NULL, filename);
+    return 0;
+}
+
+/* find js files from the directory tree and sort the list */
+static void enumerate_tests(const char *path)
+{
+    namelist_t *lp = &test_list;
+    int start = lp->count;
+    ftw(path, add_test_file, 100);
+    qsort(lp->array + start, lp->count - start, sizeof(*lp->array),
+          namelist_cmp_indirect);
+}
+
+static JSValue js_print(JSContext *ctx, JSValueConst this_val,
+                        int argc, JSValueConst *argv)
+{
+    int i;
+    const char *str;
+
+    if (outfile) {
+        for (i = 0; i < argc; i++) {
+            if (i != 0)
+                fputc(' ', outfile);
+            str = JS_ToCString(ctx, argv[i]);
+            if (!str)
+                return JS_EXCEPTION;
+            if (!strcmp(str, "Test262:AsyncTestComplete"))
+                async_done++;
+            fputs(str, outfile);
+            JS_FreeCString(ctx, str);
+        }
+        fputc('\n', outfile);
+    }
+    return JS_UNDEFINED;
+}
+
+static JSValue js_detachArrayBuffer(JSContext *ctx, JSValue this_val,
+                                    int argc, JSValue *argv)
+{
+    JS_DetachArrayBuffer(ctx, argv[0]);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_evalScript(JSContext *ctx, JSValue this_val,
+                             int argc, JSValue *argv)
+{
+    const char *str;
+    size_t len;
+    JSValue ret;
+    str = JS_ToCStringLen(ctx, &len, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+    ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL);
+    JS_FreeCString(ctx, str);
+    return ret;
+}
+
+#ifdef CONFIG_AGENT
+
+#include <pthread.h>
+
+typedef struct {
+    struct list_head link;
+    pthread_t tid;
+    char *script;
+    JSValue broadcast_func;
+    BOOL broadcast_pending;
+    JSValue broadcast_sab; /* in the main context */
+    uint8_t *broadcast_sab_buf;
+    size_t broadcast_sab_size;
+    int32_t broadcast_val;
+} Test262Agent;
+
+typedef struct {
+    struct list_head link;
+    char *str;
+} AgentReport;
+
+static JSValue add_helpers1(JSContext *ctx);
+static void add_helpers(JSContext *ctx);
+
+static pthread_mutex_t agent_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t agent_cond = PTHREAD_COND_INITIALIZER;
+/* list of Test262Agent.link */
+static struct list_head agent_list = LIST_HEAD_INIT(agent_list);
+
+static pthread_mutex_t report_mutex = PTHREAD_MUTEX_INITIALIZER;
+/* list of AgentReport.link */
+static struct list_head report_list = LIST_HEAD_INIT(report_list);
+
+static void *agent_start(void *arg)
+{
+    Test262Agent *agent = arg;
+    JSRuntime *rt;
+    JSContext *ctx;
+    JSValue ret_val;
+    int ret;
+    
+    rt = JS_NewRuntime();
+    if (rt == NULL) {
+        fatal(1, "JS_NewRuntime failure");
+    }        
+    ctx = JS_NewContext(rt);
+    if (ctx == NULL) {
+        JS_FreeRuntime(rt);
+        fatal(1, "JS_NewContext failure");
+    }
+    JS_SetContextOpaque(ctx, agent);
+    JS_SetRuntimeInfo(rt, "agent");
+    JS_SetCanBlock(rt, TRUE);
+    
+    add_helpers(ctx);
+    ret_val = JS_Eval(ctx, agent->script, strlen(agent->script),
+                      "<evalScript>", JS_EVAL_TYPE_GLOBAL);
+    free(agent->script);
+    agent->script = NULL;
+    if (JS_IsException(ret_val))
+        js_std_dump_error(ctx);
+    JS_FreeValue(ctx, ret_val);
+    
+    for(;;) {
+        JSContext *ctx1;
+        ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
+        if (ret < 0) {
+            js_std_dump_error(ctx);
+            break;
+        } else if (ret == 0) {
+            if (JS_IsUndefined(agent->broadcast_func)) {
+                break;
+            } else {
+                JSValue args[2];
+                
+                pthread_mutex_lock(&agent_mutex);
+                while (!agent->broadcast_pending) {
+                    pthread_cond_wait(&agent_cond, &agent_mutex);
+                }
+                
+                agent->broadcast_pending = FALSE;
+                pthread_cond_signal(&agent_cond);
+
+                pthread_mutex_unlock(&agent_mutex);
+
+                args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf,
+                                            agent->broadcast_sab_size,
+                                            NULL, NULL, TRUE);
+                args[1] = JS_NewInt32(ctx, agent->broadcast_val);
+                ret_val = JS_Call(ctx, agent->broadcast_func, JS_UNDEFINED,
+                                  2, (JSValueConst *)args);
+                JS_FreeValue(ctx, args[0]);
+                JS_FreeValue(ctx, args[1]);
+                if (JS_IsException(ret_val))
+                    js_std_dump_error(ctx);
+                JS_FreeValue(ctx, ret_val);
+                JS_FreeValue(ctx, agent->broadcast_func);
+                agent->broadcast_func = JS_UNDEFINED;
+            }
+        }
+    }
+    JS_FreeValue(ctx, agent->broadcast_func);
+
+    JS_FreeContext(ctx);
+    JS_FreeRuntime(rt);
+    return NULL;
+}
+
+static JSValue js_agent_start(JSContext *ctx, JSValue this_val,
+                              int argc, JSValue *argv)
+{
+    const char *script;
+    Test262Agent *agent;
+
+    if (JS_GetContextOpaque(ctx) != NULL)
+        return JS_ThrowTypeError(ctx, "cannot be called inside an agent");
+    
+    script = JS_ToCString(ctx, argv[0]);
+    if (!script)
+        return JS_EXCEPTION;
+    agent = malloc(sizeof(*agent));
+    memset(agent, 0, sizeof(*agent));
+    agent->broadcast_func = JS_UNDEFINED;
+    agent->broadcast_sab = JS_UNDEFINED;
+    agent->script = strdup(script);
+    JS_FreeCString(ctx, script);
+    list_add_tail(&agent->link, &agent_list);
+    pthread_create(&agent->tid, NULL, agent_start, agent);
+    return JS_UNDEFINED;
+}
+
+static void js_agent_free(JSContext *ctx)
+{
+    struct list_head *el, *el1;
+    Test262Agent *agent;
+    
+    list_for_each_safe(el, el1, &agent_list) {
+        agent = list_entry(el, Test262Agent, link);
+        pthread_join(agent->tid, NULL);
+        JS_FreeValue(ctx, agent->broadcast_sab);
+        list_del(&agent->link);
+        free(agent);
+    }
+}
+ 
+static JSValue js_agent_leaving(JSContext *ctx, JSValue this_val,
+                                int argc, JSValue *argv)
+{
+    Test262Agent *agent = JS_GetContextOpaque(ctx);
+    if (!agent)
+        return JS_ThrowTypeError(ctx, "must be called inside an agent");
+    /* nothing to do */
+    return JS_UNDEFINED;
+}
+
+static BOOL is_broadcast_pending(void)
+{
+    struct list_head *el;
+    Test262Agent *agent;
+    list_for_each(el, &agent_list) {
+        agent = list_entry(el, Test262Agent, link);
+        if (agent->broadcast_pending)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val,
+                                  int argc, JSValue *argv)
+{
+    JSValueConst sab = argv[0];
+    struct list_head *el;
+    Test262Agent *agent;
+    uint8_t *buf;
+    size_t buf_size;
+    int32_t val;
+    
+    if (JS_GetContextOpaque(ctx) != NULL)
+        return JS_ThrowTypeError(ctx, "cannot be called inside an agent");
+    
+    buf = JS_GetArrayBuffer(ctx, &buf_size, sab);
+    if (!buf)
+        return JS_EXCEPTION;
+    if (JS_ToInt32(ctx, &val, argv[1]))
+        return JS_EXCEPTION;
+    
+    /* broadcast the values and wait until all agents have started
+       calling their callbacks */
+    pthread_mutex_lock(&agent_mutex);
+    list_for_each(el, &agent_list) {
+        agent = list_entry(el, Test262Agent, link);
+        agent->broadcast_pending = TRUE;
+        /* the shared array buffer is used by the thread, so increment
+           its refcount */
+        agent->broadcast_sab = JS_DupValue(ctx, sab);
+        agent->broadcast_sab_buf = buf;
+        agent->broadcast_sab_size = buf_size;
+        agent->broadcast_val = val;
+    }
+    pthread_cond_broadcast(&agent_cond);
+
+    while (is_broadcast_pending()) {
+        pthread_cond_wait(&agent_cond, &agent_mutex);
+    }
+    pthread_mutex_unlock(&agent_mutex);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_agent_receiveBroadcast(JSContext *ctx, JSValue this_val,
+                                         int argc, JSValue *argv)
+{
+    Test262Agent *agent = JS_GetContextOpaque(ctx);
+    if (!agent)
+        return JS_ThrowTypeError(ctx, "must be called inside an agent");
+    if (!JS_IsFunction(ctx, argv[0]))
+        return JS_ThrowTypeError(ctx, "expecting function");
+    JS_FreeValue(ctx, agent->broadcast_func);
+    agent->broadcast_func = JS_DupValue(ctx, argv[0]);
+    return JS_UNDEFINED;
+}
+
+static JSValue js_agent_sleep(JSContext *ctx, JSValue this_val,
+                              int argc, JSValue *argv)
+{
+    uint32_t duration;
+    if (JS_ToUint32(ctx, &duration, argv[0]))
+        return JS_EXCEPTION;
+    usleep(duration * 1000);
+    return JS_UNDEFINED;
+}
+
+static int64_t get_clock_ms(void)
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000);
+}
+
+static JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val,
+                                     int argc, JSValue *argv)
+{
+    return JS_NewInt64(ctx, get_clock_ms());
+}
+
+static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val,
+                                  int argc, JSValue *argv)
+{
+    AgentReport *rep;
+    JSValue ret;
+
+    pthread_mutex_lock(&report_mutex);
+    if (list_empty(&report_list)) {
+        rep = NULL;
+    } else {
+        rep = list_entry(report_list.next, AgentReport, link);
+        list_del(&rep->link);
+    }
+    pthread_mutex_unlock(&report_mutex);
+    if (rep) {
+        ret = JS_NewString(ctx, rep->str);
+        free(rep->str);
+        free(rep);
+    } else {
+        ret = JS_NULL;
+    }
+    return ret;
+}
+
+static JSValue js_agent_report(JSContext *ctx, JSValue this_val,
+                               int argc, JSValue *argv)
+{
+    const char *str;
+    AgentReport *rep;
+
+    str = JS_ToCString(ctx, argv[0]);
+    if (!str)
+        return JS_EXCEPTION;
+    rep = malloc(sizeof(*rep));
+    rep->str = strdup(str);
+    JS_FreeCString(ctx, str);
+    
+    pthread_mutex_lock(&report_mutex);
+    list_add_tail(&rep->link, &report_list);
+    pthread_mutex_unlock(&report_mutex);
+    return JS_UNDEFINED;
+}
+
+static const JSCFunctionListEntry js_agent_funcs[] = {
+    /* only in main */
+    JS_CFUNC_DEF("start", 1, js_agent_start ),
+    JS_CFUNC_DEF("getReport", 0, js_agent_getReport ),
+    JS_CFUNC_DEF("broadcast", 2, js_agent_broadcast ),
+    /* only in agent */
+    JS_CFUNC_DEF("report", 1, js_agent_report ),
+    JS_CFUNC_DEF("leaving", 0, js_agent_leaving ),
+    JS_CFUNC_DEF("receiveBroadcast", 1, js_agent_receiveBroadcast ),
+    /* in both */
+    JS_CFUNC_DEF("sleep", 1, js_agent_sleep ),
+    JS_CFUNC_DEF("monotonicNow", 0, js_agent_monotonicNow ),
+};
+    
+static JSValue js_new_agent(JSContext *ctx)
+{
+    JSValue agent;
+    agent = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, agent, js_agent_funcs,
+                               countof(js_agent_funcs));
+    return agent;
+}
+#endif
+
+#ifdef CONFIG_REALM
+static JSValue js_createRealm(JSContext *ctx, JSValue this_val,
+                              int argc, JSValue *argv)
+{
+    JSContext *ctx1;
+    /* XXX: the context is not freed, need a refcount */
+    ctx1 = JS_NewContext(JS_GetRuntime(ctx));
+    if (!ctx1)
+        return JS_ThrowOutOfMemory(ctx);
+    return add_helpers1(ctx1);
+}
+#endif
+
+static JSValue add_helpers1(JSContext *ctx)
+{
+    JSValue global_obj;
+    JSValue obj262;
+    
+    global_obj = JS_GetGlobalObject(ctx);
+
+    JS_SetPropertyStr(ctx, global_obj, "print",
+                      JS_NewCFunction(ctx, js_print, "print", 1));
+
+    /* $262 special object used by the tests */
+    obj262 = JS_NewObject(ctx);
+    JS_SetPropertyStr(ctx, obj262, "detachArrayBuffer",
+                      JS_NewCFunction(ctx, js_detachArrayBuffer,
+                                      "detachArrayBuffer", 1));
+    JS_SetPropertyStr(ctx, obj262, "evalScript",
+                      JS_NewCFunction(ctx, js_evalScript,
+                                      "evalScript", 1));
+    JS_SetPropertyStr(ctx, obj262, "codePointRange",
+                      JS_NewCFunction(ctx, js_string_codePointRange,
+                                      "codePointRange", 2));
+#ifdef CONFIG_AGENT
+    JS_SetPropertyStr(ctx, obj262, "agent", js_new_agent(ctx));
+#endif
+
+    JS_SetPropertyStr(ctx, obj262, "global",
+                      JS_DupValue(ctx, global_obj));
+
+#ifdef CONFIG_REALM
+    JS_SetPropertyStr(ctx, obj262, "createRealm",
+                      JS_NewCFunction(ctx, js_createRealm,
+                                      "createRealm", 0));
+#endif
+
+    JS_SetPropertyStr(ctx, global_obj, "$262", JS_DupValue(ctx, obj262));
+    
+    JS_FreeValue(ctx, global_obj);
+    return obj262;
+}
+
+static void add_helpers(JSContext *ctx)
+{
+    JS_FreeValue(ctx, add_helpers1(ctx));
+}
+
+static char *load_file(const char *filename, size_t *lenp)
+{
+    char *buf;
+    size_t buf_len;
+    buf = (char *)js_load_file(NULL, &buf_len, filename);
+    if (!buf)
+        perror_exit(1, filename);
+    if (lenp)
+        *lenp = buf_len;
+    return buf;
+}
+
+static JSModuleDef *js_module_loader_test(JSContext *ctx,
+                                          const char *module_name, void *opaque)
+{
+    size_t buf_len;
+    uint8_t *buf;
+    JSModuleDef *m;
+    JSValue func_val;
+    
+    buf = js_load_file(ctx, &buf_len, module_name);
+    if (!buf) {
+        JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
+                               module_name);
+        return NULL;
+    }
+    
+    /* compile the module */
+    func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
+                       JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+    js_free(ctx, buf);
+    if (JS_IsException(func_val))
+        return NULL;
+    /* the module is already referenced, so we must free it */
+    m = JS_VALUE_GET_PTR(func_val);
+    JS_FreeValue(ctx, func_val);
+    return m;
+}
+
+int is_line_sep(char c)
+{
+    return (c == '\0' || c == '\n' || c == '\r');
+}
+
+char *find_line(const char *str, const char *line)
+{
+    if (str) {
+        const char *p;
+        int len = strlen(line);
+        for (p = str; (p = strstr(p, line)) != NULL; p += len + 1) {
+            if ((p == str || is_line_sep(p[-1])) && is_line_sep(p[len]))
+                return (char *)p;
+        }
+    }
+    return NULL;
+}
+
+int is_word_sep(char c)
+{
+    return (c == '\0' || isspace((unsigned char)c) || c == ',');
+}
+
+char *find_word(const char *str, const char *word)
+{
+    const char *p;
+    int len = strlen(word);
+    if (str && len) {
+        for (p = str; (p = strstr(p, word)) != NULL; p += len) {
+            if ((p == str || is_word_sep(p[-1])) && is_word_sep(p[len]))
+                return (char *)p;
+        }
+    }
+    return NULL;
+}
+
+/* handle exclude directories */
+void update_exclude_dirs(void)
+{
+    namelist_t *lp = &test_list;
+    namelist_t *ep = &exclude_list;
+    namelist_t *dp = &exclude_dir_list;
+    char *name;
+    int i, j, count;
+
+    /* split directpries from exclude_list */
+    for (count = i = 0; i < ep->count; i++) {
+        name = ep->array[i];
+        if (has_suffix(name, "/")) {
+            namelist_add(dp, NULL, name);
+            free(name);
+        } else {
+            ep->array[count++] = name;
+        }
+    }
+    ep->count = count;
+
+    namelist_sort(dp);
+
+    /* filter out excluded directories */
+    for (count = i = 0; i < lp->count; i++) {
+        name = lp->array[i];
+        for (j = 0; j < dp->count; j++) {
+            if (has_prefix(name, dp->array[j])) {
+                test_excluded++;
+                free(name);
+                name = NULL;
+                break;
+            }
+        }
+        if (name) {
+            lp->array[count++] = name;
+        }
+    }
+    lp->count = count;
+}
+
+void load_config(const char *filename)
+{
+    char buf[1024];
+    FILE *f;
+    char *base_name;
+    enum {
+        SECTION_NONE = 0,
+        SECTION_CONFIG,
+        SECTION_EXCLUDE,
+        SECTION_FEATURES,
+        SECTION_TESTS,
+    } section = SECTION_NONE;
+    int lineno = 0;
+
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror_exit(1, filename);
+    }
+    base_name = get_basename(filename);
+    
+    while (fgets(buf, sizeof(buf), f) != NULL) {
+        char *p, *q;
+        lineno++;
+        p = str_strip(buf);
+        if (*p == '#' || *p == ';' || *p == '\0')
+            continue;  /* line comment */
+        
+        if (*p == "[]"[0]) {
+            /* new section */
+            p++;
+            p[strcspn(p, "]")] = '\0';
+            if (str_equal(p, "config"))
+                section = SECTION_CONFIG;
+            else if (str_equal(p, "exclude"))
+                section = SECTION_EXCLUDE;
+            else if (str_equal(p, "features"))
+                section = SECTION_FEATURES;
+            else if (str_equal(p, "tests"))
+                section = SECTION_TESTS;
+            else
+                section = SECTION_NONE;
+            continue;
+        }
+        q = strchr(p, '=');
+        if (q) {
+            /* setting: name=value */
+            *q++ = '\0';
+            q = str_strip(q);
+        }
+        switch (section) {
+        case SECTION_CONFIG:
+            if (!q) {
+                printf("%s:%d: syntax error\n", filename, lineno);
+                continue;
+            }
+            if (str_equal(p, "style")) {
+                new_style = str_equal(q, "new");
+                continue;
+            }
+            if (str_equal(p, "testdir")) {
+                char *testdir = compose_path(base_name, q);
+                enumerate_tests(testdir);
+                free(testdir);
+                continue;
+            }
+            if (str_equal(p, "harnessdir")) {
+                harness_dir = compose_path(base_name, q);
+                continue;
+            }
+            if (str_equal(p, "harnessexclude")) {
+                str_append(&harness_exclude, " ", q);
+                continue;
+            }
+            if (str_equal(p, "features")) {
+                str_append(&harness_features, " ", q);
+                continue;
+            }
+            if (str_equal(p, "skip-features")) {
+                str_append(&harness_skip_features, " ", q);
+                continue;
+            }
+            if (str_equal(p, "mode")) {
+                if (str_equal(q, "default") || str_equal(q, "default-nostrict"))
+                    test_mode = TEST_DEFAULT_NOSTRICT;
+                else if (str_equal(q, "default-strict"))
+                    test_mode = TEST_DEFAULT_STRICT;
+                else if (str_equal(q, "nostrict"))
+                    test_mode = TEST_NOSTRICT;
+                else if (str_equal(q, "strict"))
+                    test_mode = TEST_STRICT;
+                else if (str_equal(q, "all") || str_equal(q, "both"))
+                    test_mode = TEST_ALL;
+                else 
+                    fatal(2, "unknown test mode: %s", q);
+                continue;
+            }
+            if (str_equal(p, "strict")) {
+                if (str_equal(q, "skip") || str_equal(q, "no"))
+                    test_mode = TEST_NOSTRICT;
+                continue;
+            }
+            if (str_equal(p, "nostrict")) {
+                if (str_equal(q, "skip") || str_equal(q, "no"))
+                    test_mode = TEST_STRICT;
+                continue;
+            }
+            if (str_equal(p, "async")) {
+                skip_async = !str_equal(q, "yes");
+                continue;
+            }
+            if (str_equal(p, "module")) {
+                skip_module = !str_equal(q, "yes");
+                continue;
+            }
+            if (str_equal(p, "verbose")) {
+                verbose = str_equal(q, "yes");
+                continue;
+            }
+            if (str_equal(p, "errorfile")) {
+                error_filename = compose_path(base_name, q);
+                continue;
+            }
+            if (str_equal(p, "excludefile")) {
+                char *path = compose_path(base_name, q);
+                namelist_load(&exclude_list, path);
+                free(path);
+                continue;
+            }
+            if (str_equal(p, "reportfile")) {
+                report_filename = compose_path(base_name, q);
+                continue;
+            }
+        case SECTION_EXCLUDE:
+            namelist_add(&exclude_list, base_name, p);
+            break;
+        case SECTION_FEATURES:
+            if (!q || str_equal(q, "yes"))
+                str_append(&harness_features, " ", p);
+            else
+                str_append(&harness_skip_features, " ", p);
+            break;
+        case SECTION_TESTS:
+            namelist_add(&test_list, base_name, p);
+            break;
+        default:
+            /* ignore settings in other sections */
+            break;
+        }
+    }
+    fclose(f);
+    free(base_name);
+}
+
+char *find_error(const char *filename, int *pline, int is_strict)
+{
+    if (error_file) {
+        size_t len = strlen(filename);
+        const char *p, *q, *r;
+        int line;
+
+        for (p = error_file; (p = strstr(p, filename)) != NULL; p += len) {
+            if ((p == error_file || p[-1] == '\n' || p[-1] == '(') && p[len] == ':') {
+                q = p + len;
+                line = 1;
+                if (*q == ':') {
+                    line = strtol(q + 1, (char**)&q, 10);
+                    if (*q == ':')
+                        q++;
+                }
+                while (*q == ' ') {
+                    q++;
+                }
+                /* check strict mode indicator */
+                if (!strstart(q, "strict mode: ", &q) != !is_strict)
+                    continue;
+                r = q = skip_prefix(q, "unexpected error: ");
+                r += strcspn(r, "\n");
+                while (r[0] == '\n' && r[1] && strncmp(r + 1, filename, 8)) {
+                    r++;
+                    r += strcspn(r, "\n");
+                }
+                if (pline)
+                    *pline = line;
+                return strdup_len(q, r - q);
+            }
+        }
+    }
+    return NULL;
+}
+
+int skip_comments(const char *str, int line, int *pline)
+{
+    const char *p;
+    int c;
+
+    p = str;
+    while ((c = (unsigned char)*p++) != '\0') {
+        if (isspace(c)) {
+            if (c == '\n')
+                line++;
+            continue;
+        }
+        if (c == '/' && *p == '/') {
+            while (*++p && *p != '\n')
+                continue;
+            continue;
+        }
+        if (c == '/' && *p == '*') {
+            for (p += 1; *p; p++) {
+                if (*p == '\n') {
+                    line++;
+                    continue;
+                }
+                if (*p == '*' && p[1] == '/') {
+                    p += 2;
+                    break;
+                }
+            }
+            continue;
+        }
+        break;
+    }
+    if (pline)
+        *pline = line;
+
+    return p - str;
+}
+
+int longest_match(const char *str, const char *find, int pos, int *ppos, int line, int *pline)
+{
+    int len, maxlen;
+
+    maxlen = 0;
+    
+    if (*find) {
+        const char *p;
+        for (p = str + pos; *p; p++) {
+            if (*p == *find) {
+                for (len = 1; p[len] && p[len] == find[len]; len++)
+                    continue;
+                if (len > maxlen) {
+                    maxlen = len;
+                    if (ppos)
+                        *ppos = p - str;
+                    if (pline)
+                        *pline = line;
+                    if (!find[len])
+                        break;
+                }
+            }
+            if (*p == '\n')
+                line++;
+        }
+    }
+    return maxlen;
+}
+
+static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
+                    const char *filename, int is_test, int is_negative,
+                    const char *error_type, FILE *outfile, int eval_flags,
+                    int is_async)
+{
+    JSValue res_val, exception_val;
+    int ret, error_line, pos, pos_line;
+    BOOL is_error, has_error_line;
+    const char *error_name;
+    
+    pos = skip_comments(buf, 1, &pos_line);
+    error_line = pos_line;
+    has_error_line = FALSE;
+    exception_val = JS_UNDEFINED;
+    error_name = NULL;
+
+    async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */
+
+    res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
+
+    if (is_async && !JS_IsException(res_val)) {
+        JS_FreeValue(ctx, res_val);
+        for(;;) {
+            JSContext *ctx1;
+            ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
+            if (ret < 0) {
+                res_val = JS_EXCEPTION;
+                break;
+            } else if (ret == 0) {
+                /* test if the test called $DONE() once */
+                if (async_done != 1) {
+                    res_val = JS_ThrowTypeError(ctx, "$DONE() not called");
+                } else {
+                    res_val = JS_UNDEFINED;
+                }
+                break;
+            }
+        }
+    }
+
+    if (JS_IsException(res_val)) {
+        exception_val = JS_GetException(ctx);
+        is_error = JS_IsError(ctx, exception_val);
+        /* XXX: should get the filename and line number */
+        if (outfile) {
+            if (!is_error)
+                fprintf(outfile, "%sThrow: ", (eval_flags & JS_EVAL_FLAG_STRICT) ?
+                        "strict mode: " : "");
+            js_print(ctx, JS_NULL, 1, &exception_val);
+        }
+        if (is_error) {
+            JSValue name, stack;
+            const char *stack_str;
+        
+            name = JS_GetPropertyStr(ctx, exception_val, "name");
+            error_name = JS_ToCString(ctx, name);
+            stack = JS_GetPropertyStr(ctx, exception_val, "stack");
+            if (!JS_IsUndefined(stack)) {
+                stack_str = JS_ToCString(ctx, stack);
+                if (stack_str) {
+                    const char *p;
+                    int len;
+                    
+                    if (outfile)
+                        fprintf(outfile, "%s", stack_str);
+                    
+                    len = strlen(filename);
+                    p = strstr(stack_str, filename);
+                    if (p != NULL && p[len] == ':') {
+                        error_line = atoi(p + len + 1);
+                        has_error_line = TRUE;
+                    }
+                    JS_FreeCString(ctx, stack_str);
+                }
+            }
+            JS_FreeValue(ctx, stack);
+            JS_FreeValue(ctx, name);
+        }
+        if (is_negative) {
+            ret = 0;
+            if (error_type) {
+                char *error_class;
+                const char *msg;
+            
+                msg = JS_ToCString(ctx, exception_val);
+                error_class = strdup_len(msg, strcspn(msg, ":"));
+                if (!str_equal(error_class, error_type))
+                    ret = -1;
+                free(error_class);
+                JS_FreeCString(ctx, msg);
+            }
+        } else {
+            ret = -1;
+        }
+    } else {
+        if (is_negative)
+            ret = -1;
+        else
+            ret = 0;
+    }
+
+    if (verbose && is_test) {
+        JSValue msg_val = JS_UNDEFINED;
+        const char *msg = NULL;
+        int s_line;
+        char *s = find_error(filename, &s_line, eval_flags & JS_EVAL_FLAG_STRICT);
+        const char *strict_mode = (eval_flags & JS_EVAL_FLAG_STRICT) ? "strict mode: " : "";
+
+        if (!JS_IsUndefined(exception_val)) {
+            msg_val = JS_ToString(ctx, exception_val);
+            msg = JS_ToCString(ctx, msg_val);
+        }
+        if (is_negative) {  // expect error
+            if (ret == 0) {
+                if (msg && s &&
+                    (str_equal(s, "expected error") ||
+                     strstart(s, "unexpected error type:", NULL) ||
+                     str_equal(s, msg))) {     // did not have error yet
+                    if (!has_error_line) {
+                        longest_match(buf, msg, pos, &pos, pos_line, &error_line);
+                    }
+                    printf("%s:%d: %sOK, now has error %s\n",
+                           filename, error_line, strict_mode, msg);
+                    fixed_errors++;
+                }
+            } else {
+                if (!s) {   // not yet reported
+                    if (msg) {
+                        fprintf(error_out, "%s:%d: %sunexpected error type: %s\n",
+                                filename, error_line, strict_mode, msg);
+                    } else {
+                        fprintf(error_out, "%s:%d: %sexpected error\n",
+                                filename, error_line, strict_mode);
+                    }
+                    new_errors++;
+                }
+            }
+        } else {            // should not have error
+            if (msg) {
+                if (!s || !str_equal(s, msg)) {
+                    if (!has_error_line) {
+                        char *p = skip_prefix(msg, "Test262 Error: ");
+                        if (strstr(p, "Test case returned non-true value!")) {
+                            longest_match(buf, "runTestCase", pos, &pos, pos_line, &error_line);
+                        } else {
+                            longest_match(buf, p, pos, &pos, pos_line, &error_line);
+                        }
+                    }
+                    fprintf(error_out, "%s:%d: %s%s%s\n", filename, error_line, strict_mode,
+                            error_file ? "unexpected error: " : "", msg);
+
+                    if (s && (!str_equal(s, msg) || error_line != s_line)) {
+                        printf("%s:%d: %sprevious error: %s\n", filename, s_line, strict_mode, s);
+                        changed_errors++;
+                    } else {
+                        new_errors++;
+                    }
+                }
+            } else {
+                if (s) {
+                    printf("%s:%d: %sOK, fixed error: %s\n", filename, s_line, strict_mode, s);
+                    fixed_errors++;
+                }
+            }
+        }
+        JS_FreeValue(ctx, msg_val);
+        JS_FreeCString(ctx, msg);
+        free(s);
+    }
+    JS_FreeCString(ctx, error_name);
+    JS_FreeValue(ctx, exception_val);
+    JS_FreeValue(ctx, res_val);
+    return ret;
+}
+
+static int eval_file(JSContext *ctx, const char *base, const char *p,
+                     int eval_flags)
+{
+    char *buf;
+    size_t buf_len;
+    char *filename = compose_path(base, p);
+
+    buf = load_file(filename, &buf_len);
+    if (!buf) {
+        warning("cannot load %s", filename);
+        goto fail;
+    }
+    if (eval_buf(ctx, buf, buf_len, filename, FALSE, FALSE, NULL, stderr,
+                 eval_flags, FALSE)) {
+        warning("error evaluating %s", filename);
+        goto fail;
+    }
+    free(buf);
+    free(filename);
+    return 0;
+
+fail:
+    free(buf);
+    free(filename);
+    return 1;
+}
+
+char *extract_desc(const char *buf, char style)
+{
+    const char *p, *desc_start;
+    char *desc;
+    int len;
+    
+    p = buf;
+    while (*p != '\0') {
+        if (p[0] == '/' && p[1] == '*' && p[2] == style && p[3] != '/') {
+            p += 3;
+            desc_start = p;
+            while (*p != '\0' && (p[0] != '*' || p[1] != '/'))
+                p++;
+            if (*p == '\0') {
+                warning("Expecting end of desc comment");
+                return NULL;
+            }
+            len = p - desc_start;
+            desc = malloc(len + 1);
+            memcpy(desc, desc_start, len);
+            desc[len] = '\0';
+            return desc;
+        } else {
+            p++;
+        }
+    }
+    return NULL;
+}
+
+static char *find_tag(char *desc, const char *tag, int *state)
+{
+    char *p;
+    p = strstr(desc, tag);
+    if (p) {
+        p += strlen(tag);
+        *state = 0;
+    }
+    return p;
+}
+
+static char *get_option(char **pp, int *state)
+{
+    char *p, *p0, *option = NULL;
+    if (*pp) {
+        for (p = *pp;; p++) {
+            switch (*p) {
+            case '[':
+                *state += 1;
+                continue;
+            case ']':
+                *state -= 1;
+                if (*state > 0)
+                    continue;
+                p = NULL;
+                break;
+            case ' ':
+            case '\t':
+            case '\r':
+            case ',':
+            case '-':
+                continue;
+            case '\n':
+                if (*state > 0 || p[1] == ' ')
+                    continue;
+                p = NULL;
+                break;
+            case '\0':
+                p = NULL;
+                break;
+            default:
+                p0 = p;
+                p += strcspn(p0, " \t\r\n,]");
+                option = strdup_len(p0, p - p0);
+                break;
+            }
+            break;
+        }
+        *pp = p;
+    }
+    return option;
+}
+
+void update_stats(JSRuntime *rt, const char *filename) {
+    JSMemoryUsage stats;
+    JS_ComputeMemoryUsage(rt, &stats);
+    if (stats_count++ == 0) {
+        stats_avg = stats_all = stats_min = stats_max = stats;
+        stats_min_filename = strdup(filename);
+        stats_max_filename = strdup(filename);
+    } else {
+        if (stats_max.malloc_size < stats.malloc_size) {
+            stats_max = stats;
+            free(stats_max_filename);
+            stats_max_filename = strdup(filename);
+        }
+        if (stats_min.malloc_size > stats.malloc_size) {
+            stats_min = stats;
+            free(stats_min_filename);
+            stats_min_filename = strdup(filename);
+        }
+
+#define update(f)  stats_avg.f = (stats_all.f += stats.f) / stats_count
+        update(malloc_count);
+        update(malloc_size);
+        update(memory_used_count);
+        update(memory_used_size);
+        update(atom_count);
+        update(atom_size);
+        update(str_count);
+        update(str_size);
+        update(obj_count);
+        update(obj_size);
+        update(prop_count);
+        update(prop_size);
+        update(shape_count);
+        update(shape_size);
+        update(js_func_count);
+        update(js_func_size);
+        update(js_func_code_size);
+        update(js_func_pc2line_count);
+        update(js_func_pc2line_size);
+        update(c_func_count);
+        update(array_count);
+        update(fast_array_count);
+        update(fast_array_elements);
+    }
+#undef update
+}
+
+int run_test_buf(const char *filename, char *harness, namelist_t *ip,
+                 char *buf, size_t buf_len, const char* error_type,
+                 int eval_flags, BOOL is_negative, BOOL is_async,
+                 BOOL can_block)
+{
+    JSRuntime *rt;
+    JSContext *ctx;
+    int i, ret;
+        
+    rt = JS_NewRuntime();
+    if (rt == NULL) {
+        fatal(1, "JS_NewRuntime failure");
+    }        
+    ctx = JS_NewContext(rt);
+    if (ctx == NULL) {
+        JS_FreeRuntime(rt);
+        fatal(1, "JS_NewContext failure");
+    }
+    JS_SetRuntimeInfo(rt, filename);
+
+    JS_SetCanBlock(rt, can_block);
+    
+    /* loader for ES6 modules */
+    JS_SetModuleLoaderFunc(rt, NULL, js_module_loader_test, NULL);
+        
+    add_helpers(ctx);
+
+    /* add backtrace if the isError property is present in a thrown
+       object */
+    JS_EnableIsErrorProperty(ctx, TRUE);
+
+    for (i = 0; i < ip->count; i++) {
+        if (eval_file(ctx, harness, ip->array[i],
+                      JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRIP)) {
+            fatal(1, "error including %s for %s", ip->array[i], filename);
+        }
+    }
+
+    ret = eval_buf(ctx, buf, buf_len, filename, TRUE, is_negative,
+                   error_type, outfile, eval_flags, is_async);
+    ret = (ret != 0);
+        
+    if (dump_memory) {
+        update_stats(rt, filename);
+    }
+#ifdef CONFIG_AGENT
+    js_agent_free(ctx);
+#endif
+    JS_FreeContext(ctx);
+    JS_FreeRuntime(rt);
+
+    test_count++;
+    if (ret) {
+        test_failed++;
+        if (outfile) {
+            /* do not output a failure number to minimize diff */
+            fprintf(outfile, "  FAILED\n");
+        }
+    }
+    return ret;
+}
+
+int run_test(const char *filename, int index)
+{
+    char harnessbuf[1024];
+    char *harness;
+    char *buf;
+    size_t buf_len;
+    char *desc, *p;
+    char *error_type;
+    int ret, eval_flags, use_strict, use_nostrict;
+    BOOL is_negative, is_nostrict, is_onlystrict, is_async, is_module, skip;
+    BOOL can_block;
+    namelist_t include_list = { 0 }, *ip = &include_list;
+    
+    is_nostrict = is_onlystrict = is_negative = is_async = is_module = skip = FALSE;
+    can_block = TRUE;
+    error_type = NULL;
+    buf = load_file(filename, &buf_len);
+
+    harness = harness_dir;
+
+    if (new_style) {
+        if (!harness) {
+            p = strstr(filename, "test/");
+            if (p) {
+                snprintf(harnessbuf, sizeof(harnessbuf), "%.*s%s",
+                         (int)(p - filename), filename, "harness");
+            }
+            harness = harnessbuf;
+        }
+        namelist_add(ip, NULL, "sta.js");
+        namelist_add(ip, NULL, "assert.js");
+        /* extract the YAML frontmatter */
+        desc = extract_desc(buf, '-');
+        if (desc) {
+            char *ifile, *option;
+            int state;
+            p = find_tag(desc, "includes:", &state);
+            if (p) {
+                while ((ifile = get_option(&p, &state)) != NULL) {
+                    // skip unsupported harness files
+                    if (find_word(harness_exclude, ifile)) {
+                        skip |= 1;
+                    } else {
+                        namelist_add(ip, NULL, ifile);
+                    }
+                    free(ifile);
+                }
+            }
+            p = find_tag(desc, "flags:", &state);
+            if (p) {
+                while ((option = get_option(&p, &state)) != NULL) {
+                    if (str_equal(option, "noStrict") ||
+                        str_equal(option, "raw")) {
+                        is_nostrict = TRUE;
+                        skip |= (test_mode == TEST_STRICT);
+                    }
+                    else if (str_equal(option, "onlyStrict")) {
+                        is_onlystrict = TRUE;
+                        skip |= (test_mode == TEST_NOSTRICT);
+                    }
+                    else if (str_equal(option, "async")) {
+                        is_async = TRUE;
+                        skip |= skip_async;
+                    }
+                    else if (str_equal(option, "module")) {
+                        is_module = TRUE;
+                        skip |= skip_module;
+                    }
+                    else if (str_equal(option, "CanBlockIsFalse")) {
+                        can_block = FALSE;
+                    }
+                    free(option);
+                }
+            }
+            p = find_tag(desc, "negative:", &state);
+            if (p) {
+                /* XXX: should extract the phase */
+                char *q = find_tag(p, "type:", &state);
+                if (q) {
+                    while (isspace(*q))
+                        q++;
+                    error_type = strdup_len(q, strcspn(q, " \n"));
+                }
+                is_negative = TRUE;
+            }
+            p = find_tag(desc, "features:", &state);
+            if (p) {
+                while ((option = get_option(&p, &state)) != NULL) {
+                    if (find_word(harness_features, option)) {
+                        /* feature is enabled */
+                    } else if (find_word(harness_skip_features, option)) {
+                        /* skip disabled feature */
+                        skip |= 1;
+                    } else {
+                        /* feature is not listed: skip and warn */
+                        printf("%s:%d: unknown feature: %s\n", filename, 1, option);
+                        skip |= 1;
+                    }
+                    free(option);
+                }
+            }
+            free(desc);
+        }
+        if (is_async)
+            namelist_add(ip, NULL, "doneprintHandle.js");
+    } else {
+        char *ifile;
+
+        if (!harness) {
+            p = strstr(filename, "test/");
+            if (p) {
+                snprintf(harnessbuf, sizeof(harnessbuf), "%.*s%s",
+                         (int)(p - filename), filename, "test/harness");
+            }
+            harness = harnessbuf;
+        }
+
+        namelist_add(ip, NULL, "sta.js");
+
+        /* include extra harness files */
+        for (p = buf; (p = strstr(p, "$INCLUDE(\"")) != NULL; p++) {
+            p += 10;
+            ifile = strdup_len(p, strcspn(p, "\""));
+            // skip unsupported harness files
+            if (find_word(harness_exclude, ifile)) {
+                skip |= 1;
+            } else {
+                namelist_add(ip, NULL, ifile);
+            }
+            free(ifile);
+        }
+
+        /* locate the old style configuration comment */
+        desc = extract_desc(buf, '*');
+        if (desc) {
+            if (strstr(desc, "@noStrict")) {
+                is_nostrict = TRUE;
+                skip |= (test_mode == TEST_STRICT);
+            }
+            if (strstr(desc, "@onlyStrict")) {
+                is_onlystrict = TRUE;
+                skip |= (test_mode == TEST_NOSTRICT);
+            }
+            if (strstr(desc, "@negative")) {
+                /* XXX: should extract the regex to check error type */
+                is_negative = TRUE;
+            }
+            free(desc);
+        }
+    }
+
+    if (outfile && index >= 0) {
+        fprintf(outfile, "%d: %s%s%s%s%s%s%s\n", index, filename,
+                is_nostrict ? "  @noStrict" : "",
+                is_onlystrict ? "  @onlyStrict" : "",
+                is_async ? "  async" : "",
+                is_module ? "  module" : "",
+                is_negative ? "  @negative" : "",
+                skip ? "  SKIPPED" : "");
+        fflush(outfile);
+    }
+
+    use_strict = use_nostrict = 0;
+    /* XXX: should remove 'test_mode' or simplify it just to force
+       strict or non strict mode for single file tests */
+    switch (test_mode) {
+    case TEST_DEFAULT_NOSTRICT:
+        if (is_onlystrict)
+            use_strict = 1;
+        else
+            use_nostrict = 1;
+        break;
+    case TEST_DEFAULT_STRICT:
+        if (is_nostrict)
+            use_nostrict = 1;
+        else
+            use_strict = 1;
+        break;
+    case TEST_NOSTRICT:
+        if (!is_onlystrict)
+            use_nostrict = 1;
+        break;
+    case TEST_STRICT:
+        if (!is_nostrict)
+            use_strict = 1;
+        break;
+    case TEST_ALL:
+        if (is_module) {
+            use_nostrict = 1;
+        } else {
+            if (!is_nostrict)
+                use_strict = 1;
+            if (!is_onlystrict)
+                use_nostrict = 1;
+        }
+        break;
+    }
+
+    if (skip || use_strict + use_nostrict == 0) {
+        test_skipped++;
+        ret = -2;
+    } else {
+        clock_t clocks;
+
+        if (is_module) {
+            eval_flags = JS_EVAL_TYPE_MODULE;
+        } else {
+            eval_flags = JS_EVAL_TYPE_GLOBAL;
+        }
+        clocks = clock();
+        ret = 0;
+        if (use_nostrict) {
+            ret = run_test_buf(filename, harness, ip, buf, buf_len,
+                               error_type, eval_flags, is_negative, is_async,
+                               can_block);
+        }
+        if (use_strict) {
+            ret |= run_test_buf(filename, harness, ip, buf, buf_len,
+                                error_type, eval_flags | JS_EVAL_FLAG_STRICT,
+                                is_negative, is_async, can_block);
+        }
+        clocks = clock() - clocks;
+        if (outfile && index >= 0 && clocks >= CLOCKS_PER_SEC / 10) {
+            /* output timings for tests that take more than 100 ms */
+            fprintf(outfile, " time: %d ms\n", (int)(clocks * 1000LL / CLOCKS_PER_SEC));
+        }
+    }
+    namelist_free(&include_list);
+    free(error_type);
+    free(buf);
+
+    return ret;
+}
+
+/* run a test when called by test262-harness+eshost */
+int run_test262_harness_test(const char *filename, BOOL is_module)
+{
+    JSRuntime *rt;
+    JSContext *ctx;
+    char *buf;
+    size_t buf_len;
+    int eval_flags, ret_code, ret;
+    JSValue res_val;
+    BOOL can_block;
+    
+    outfile = stdout; /* for js_print */
+
+    rt = JS_NewRuntime();
+    if (rt == NULL) {
+        fatal(1, "JS_NewRuntime failure");
+    }        
+    ctx = JS_NewContext(rt);
+    if (ctx == NULL) {
+        JS_FreeRuntime(rt);
+        fatal(1, "JS_NewContext failure");
+    }
+    JS_SetRuntimeInfo(rt, filename);
+
+    can_block = TRUE;
+    JS_SetCanBlock(rt, can_block);
+    
+    /* loader for ES6 modules */
+    JS_SetModuleLoaderFunc(rt, NULL, js_module_loader_test, NULL);
+        
+    add_helpers(ctx);
+
+    /* add backtrace if the isError property is present in a thrown
+       object */
+    JS_EnableIsErrorProperty(ctx, TRUE);
+
+    buf = load_file(filename, &buf_len);
+
+    if (is_module) {
+      eval_flags = JS_EVAL_TYPE_MODULE;
+    } else {
+      eval_flags = JS_EVAL_TYPE_GLOBAL;
+    }
+    res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
+    ret_code = 0;
+    if (JS_IsException(res_val)) {
+       js_std_dump_error(ctx);
+       ret_code = 1;
+    } else {
+        JS_FreeValue(ctx, res_val);
+        for(;;) {
+            JSContext *ctx1;
+            ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
+            if (ret < 0) {
+	      js_std_dump_error(ctx1);
+	      ret_code = 1;
+            } else if (ret == 0) {
+	      break;
+            }
+        }
+    }
+    free(buf);
+#ifdef CONFIG_AGENT
+    js_agent_free(ctx);
+#endif
+    JS_FreeContext(ctx);
+    JS_FreeRuntime(rt);
+    return ret_code;
+}
+
+clock_t last_clock;
+
+void show_progress(int force) {
+    clock_t t = clock();
+    if (force || !last_clock || (t - last_clock) > CLOCKS_PER_SEC / 20) {
+        last_clock = t;
+        /* output progress indicator: erase end of line and return to col 0 */
+        fprintf(stderr, "%d/%d/%d\033[K\r",
+                test_failed, test_count, test_skipped);
+        fflush(stderr);
+    }
+}
+
+static int slow_test_threshold;
+
+void run_test_dir_list(namelist_t *lp, int start_index, int stop_index)
+{
+    int i;
+
+    namelist_sort(lp);
+    for (i = 0; i < lp->count; i++) {
+        const char *p = lp->array[i];
+        if (namelist_find(&exclude_list, p) >= 0) {
+            test_excluded++;
+        } else if (test_index < start_index) {
+            test_skipped++;
+        } else if (stop_index >= 0 && test_index > stop_index) {
+            test_skipped++;
+        } else {
+            int ti;
+            if (slow_test_threshold != 0) {
+                ti = get_clock_ms();
+            } else {
+                ti = 0;
+            }
+            run_test(p, test_index);
+            if (slow_test_threshold != 0) {
+                ti = get_clock_ms() - ti;
+                if (ti >= slow_test_threshold)
+                    fprintf(stderr, "\n%s (%d ms)\n", p, ti);
+            }
+            show_progress(FALSE);
+        }
+        test_index++;
+    }
+    show_progress(TRUE);
+}
+
+void help(void)
+{
+    printf("run-test262 version " CONFIG_VERSION "\n"
+           "usage: run-test262 [options] {-f file ... | [dir_list] [index range]}\n"
+           "-h             help\n"
+           "-a             run tests in strict and nostrict modes\n"
+           "-m             print memory usage summary\n"
+           "-n             use new style harness\n"
+           "-N             run test prepared by test262-harness+eshost\n"
+           "-s             run tests in strict mode, skip @nostrict tests\n"
+           "-u             update error file\n"
+           "-v             verbose: output error messages\n"
+           "-T duration    display tests taking more than 'duration' ms\n"
+           "-c file        read configuration from 'file'\n"
+           "-d dir         run all test files in directory tree 'dir'\n"
+           "-e file        load the known errors from 'file'\n"
+           "-f file        execute single test from 'file'\n"
+           "-r file        set the report file name (default=none)\n"
+           "-x file        exclude tests listed in 'file'\n");
+    exit(1);
+}
+
+char *get_opt_arg(const char *option, char *arg)
+{
+    if (!arg) {
+        fatal(2, "missing argument for option %s", option);
+    }
+    return arg;
+}
+
+int main(int argc, char **argv)
+{
+    int optind, start_index, stop_index;
+    BOOL is_dir_list;
+    BOOL only_check_errors = FALSE;
+    const char *filename;
+    BOOL is_test262_harness = FALSE;
+    BOOL is_module = FALSE;
+
+#if !defined(_WIN32)
+    /* Date tests assume California local time */
+    setenv("TZ", "America/Los_Angeles", 1);
+#endif
+
+    /* cannot use getopt because we want to pass the command line to
+       the script */
+    optind = 1;
+    is_dir_list = TRUE;
+    while (optind < argc) {
+        char *arg = argv[optind];
+        if (*arg != '-')
+            break;
+        optind++;
+        if (str_equal(arg, "-h")) {
+            help();
+        } else if (str_equal(arg, "-m")) {
+            dump_memory++;
+        } else if (str_equal(arg, "-n")) {
+            new_style++;
+        } else if (str_equal(arg, "-s")) {
+            test_mode = TEST_STRICT;
+        } else if (str_equal(arg, "-a")) {
+            test_mode = TEST_ALL;
+        } else if (str_equal(arg, "-u")) {
+            update_errors++;
+        } else if (str_equal(arg, "-v")) {
+            verbose++;
+        } else if (str_equal(arg, "-c")) {
+            load_config(get_opt_arg(arg, argv[optind++]));
+        } else if (str_equal(arg, "-d")) {
+            enumerate_tests(get_opt_arg(arg, argv[optind++]));
+        } else if (str_equal(arg, "-e")) {
+            error_filename = get_opt_arg(arg, argv[optind++]);
+        } else if (str_equal(arg, "-x")) {
+            namelist_load(&exclude_list, get_opt_arg(arg, argv[optind++]));
+        } else if (str_equal(arg, "-f")) {
+            is_dir_list = FALSE;
+        } else if (str_equal(arg, "-r")) {
+            report_filename = get_opt_arg(arg, argv[optind++]);
+        } else if (str_equal(arg, "-E")) {
+            only_check_errors = TRUE;
+        } else if (str_equal(arg, "-T")) {
+            slow_test_threshold = atoi(get_opt_arg(arg, argv[optind++]));
+        } else if (str_equal(arg, "-N")) {
+            is_test262_harness = TRUE;
+        } else if (str_equal(arg, "--module")) {
+            is_module = TRUE;
+        } else {
+            fatal(1, "unknown option: %s", arg);
+            break;
+        }
+    }
+    
+    if (optind >= argc && !test_list.count)
+        help();
+
+    if (is_test262_harness) {
+        return run_test262_harness_test(argv[optind], is_module);
+    }
+			       
+    error_out = stdout;
+    if (error_filename) {
+        error_file = load_file(error_filename, NULL);
+        if (only_check_errors && error_file) {
+            namelist_free(&test_list);
+            namelist_add_from_error_file(&test_list, error_file);
+        }
+        if (update_errors) {
+            free(error_file);
+            error_file = NULL;
+            error_out = fopen(error_filename, "w");
+            if (!error_out) {
+                perror_exit(1, error_filename);
+            }
+        }
+    }
+
+    update_exclude_dirs();
+
+    if (is_dir_list) {
+        if (optind < argc && !isdigit(argv[optind][0])) {
+            filename = argv[optind++];
+            namelist_load(&test_list, filename);
+        }
+        start_index = 0;
+        stop_index = -1;
+        if (optind < argc) {
+            start_index = atoi(argv[optind++]);
+            if (optind < argc) {
+                stop_index = atoi(argv[optind++]);
+            }
+        }
+        if (!report_filename || str_equal(report_filename, "none")) {
+            outfile = NULL;
+        } else if (str_equal(report_filename, "-")) {
+            outfile = stdout;
+        } else {
+            outfile = fopen(report_filename, "wb");
+            if (!outfile) {
+                perror_exit(1, report_filename);
+            }
+        }
+        run_test_dir_list(&test_list, start_index, stop_index);
+
+        if (outfile && outfile != stdout) {
+            fclose(outfile);
+            outfile = NULL;
+        }
+    } else {
+        outfile = stdout;
+        while (optind < argc) {
+            run_test(argv[optind++], -1);
+        }
+    }
+
+    if (dump_memory) {
+        if (dump_memory > 1 && stats_count > 1) {
+            printf("\nMininum memory statistics for %s:\n\n", stats_min_filename);
+            JS_DumpMemoryUsage(stdout, &stats_min, NULL);
+            printf("\nMaximum memory statistics for %s:\n\n", stats_max_filename);
+            JS_DumpMemoryUsage(stdout, &stats_max, NULL);
+        }
+        printf("\nAverage memory statistics for %d tests:\n\n", stats_count);
+        JS_DumpMemoryUsage(stdout, &stats_avg, NULL);
+        printf("\n");
+    }
+
+    if (is_dir_list) {
+        fprintf(stderr, "Result: %d/%d error%s",
+                test_failed, test_count, test_count != 1 ? "s" : "");
+        if (test_excluded)
+            fprintf(stderr, ", %d excluded", test_excluded);
+        if (test_skipped)
+            fprintf(stderr, ", %d skipped", test_skipped);
+        if (error_file) {
+            if (new_errors)
+                fprintf(stderr, ", %d new", new_errors);
+            if (changed_errors)
+                fprintf(stderr, ", %d changed", changed_errors);
+            if (fixed_errors)
+                fprintf(stderr, ", %d fixed", fixed_errors);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    if (error_out && error_out != stdout) {
+        fclose(error_out);
+        error_out = NULL;
+    }
+
+    namelist_free(&test_list);
+    namelist_free(&exclude_list);
+    namelist_free(&exclude_dir_list);
+    free(harness_dir);
+    free(harness_features);
+    free(harness_exclude);
+    free(error_file);
+
+    return 0;
+}
diff --git a/test262.conf b/test262.conf
new file mode 100644
index 0000000..a450332
--- /dev/null
+++ b/test262.conf
@@ -0,0 +1,179 @@
+[config]
+# general settings for test262 ES6 version
+
+# framework style: old, new
+style=new
+
+# handle tests tagged as [noStrict]: yes, no, skip
+nostrict=yes
+
+# handle tests tagged as [strictOnly]: yes, no, skip
+strict=yes
+
+# test mode: default, default-nostrict, default-strict, strict, nostrict, both, all
+mode=default
+
+# handle tests flagged as [async]: yes, no, skip
+# for these, load 'harness/doneprintHandle.js' prior to test
+# and expect `print('Test262:AsyncTestComplete')` to be called for 
+# successful termination
+async=yes
+
+# handle tests flagged as [module]: yes, no, skip
+module=yes
+
+# output error messages: yes, no
+verbose=yes
+
+# load harness files from this directory
+harnessdir=test262/harness
+
+# names of harness include files to skip
+#harnessexclude=
+
+# name of the error file for known errors
+errorfile=test262_errors.txt
+
+# exclude tests enumerated in this file (see also [exclude] section)
+#excludefile=test262_exclude.txt
+
+# report test results to this file
+reportfile=test262_report.txt
+
+# enumerate tests from this directory
+testdir=test262/test
+
+[features]
+# Standard language features and proposed extensions
+# list the features that are included
+# skipped features are tagged as such to avoid warnings
+
+Array.prototype.flat
+Array.prototype.flatMap
+Array.prototype.flatten
+Array.prototype.values
+ArrayBuffer
+arrow-function
+async-functions
+async-iteration
+Atomics
+BigInt=skip
+caller
+class
+class-fields-private
+class-fields-public
+class-methods-private
+class-static-fields-public
+class-static-fields-private
+class-static-methods-private
+computed-property-names
+const
+cross-realm=skip
+DataView
+DataView.prototype.getFloat32
+DataView.prototype.getFloat64
+DataView.prototype.getInt16
+DataView.prototype.getInt32
+DataView.prototype.getInt8
+DataView.prototype.getUint16
+DataView.prototype.getUint32
+DataView.prototype.setUint8
+default-arg
+default-parameters
+destructuring-assignment
+destructuring-binding
+dynamic-import
+export-star-as-namespace-from-module
+FinalizationGroup=skip
+Float32Array
+Float64Array
+for-of
+generators
+globalThis
+hashbang
+host-gc-required=skip
+import.meta
+Int32Array
+Int8Array
+IsHTMLDDA=skip
+json-superset
+let
+Map
+new.target
+numeric-separator-literal
+object-rest
+object-spread
+Object.fromEntries
+Object.is
+optional-catch-binding
+optional-chaining=skip
+Promise.allSettled
+Promise.prototype.finally
+Proxy
+proxy-missing-checks
+Reflect
+Reflect.construct
+Reflect.set
+Reflect.setPrototypeOf
+regexp-dotall
+regexp-lookbehind
+regexp-named-groups
+regexp-unicode-property-escapes
+rest-parameters
+Set
+SharedArrayBuffer
+string-trimming
+String.fromCodePoint
+String.prototype.endsWith
+String.prototype.includes
+String.prototype.matchAll
+String.prototype.trimEnd
+String.prototype.trimStart
+super
+Symbol
+Symbol.asyncIterator
+Symbol.hasInstance
+Symbol.isConcatSpreadable
+Symbol.iterator
+Symbol.match
+Symbol.matchAll
+Symbol.prototype.description
+Symbol.replace
+Symbol.search
+Symbol.species
+Symbol.split
+Symbol.toPrimitive
+Symbol.toStringTag
+Symbol.unscopables
+tail-call-optimization=skip
+template
+top-level-await=skip
+TypedArray
+u180e
+Uint16Array
+Uint8Array
+Uint8ClampedArray
+WeakMap
+WeakRef=skip
+WeakSet
+well-formed-json-stringify
+
+[exclude]
+# list excluded tests and directories here
+
+# intl not supported
+test262/test/intl402/
+
+# these builtins are not supported:
+test262/test/built-ins/BigInt/
+
+# incompatible with the "caller" feature
+test262/test/built-ins/Function/prototype/restricted-property-caller.js
+test262/test/built-ins/ThrowTypeError/unique-per-realm-function-proto.js
+
+# slow tests
+#test262/test/built-ins/RegExp/CharacterClassEscapes/
+#test262/test/built-ins/RegExp/property-escapes/
+
+[tests]
+# list test files or use config.testdir
diff --git a/test262_errors.txt b/test262_errors.txt
new file mode 100644
index 0000000..ac2a707
--- /dev/null
+++ b/test262_errors.txt
@@ -0,0 +1,2 @@
+test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeError: $DONE() not called
+test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: strict mode: TypeError: $DONE() not called
diff --git a/test262bn.conf b/test262bn.conf
new file mode 100644
index 0000000..3351249
--- /dev/null
+++ b/test262bn.conf
@@ -0,0 +1,177 @@
+[config]
+# general settings for test262 ES6 bignum version
+
+# framework style: old, new
+style=new
+
+# handle tests tagged as [noStrict]: yes, no, skip
+nostrict=yes
+
+# handle tests tagged as [strictOnly]: yes, no, skip
+strict=yes
+
+# test mode: default, default-nostrict, default-strict, strict, nostrict, both, all
+mode=default
+
+# handle tests flagged as [async]: yes, no, skip
+# for these, load 'harness/doneprintHandle.js' prior to test
+# and expect `print('Test262:AsyncTestComplete')` to be called for 
+# successful termination
+async=yes
+
+# handle tests flagged as [module]: yes, no, skip
+module=yes
+
+# output error messages: yes, no
+verbose=yes
+
+# load harness files from this directory
+harnessdir=test262/harness
+
+# names of harness include files to skip
+# bignum version does not support Atomics
+harnessexclude=testAtomics.js
+
+# name of the error file for known errors
+errorfile=test262bn_errors.txt
+
+# exclude tests enumerated in this file (see also [exclude] section)
+#excludefile=test262bn_exclude.txt
+
+# report test results to this file
+reportfile=test262bn_report.txt
+
+# enumerate tests from this directory
+testdir=test262/test
+
+[features]
+# Standard language features and proposed extensions
+# list the features that are included
+# skipped features are tagged as such to avoid warnings
+
+Array.prototype.flat
+Array.prototype.flatMap
+Array.prototype.flatten
+Array.prototype.values
+ArrayBuffer
+arrow-function
+async-functions
+async-iteration
+Atomics
+BigInt
+caller
+class
+class-fields-private
+class-fields-public
+class-methods-private
+class-static-fields-public
+class-static-fields-private
+class-static-methods-private
+computed-property-names
+const
+cross-realm=skip
+DataView
+DataView.prototype.getFloat32
+DataView.prototype.getFloat64
+DataView.prototype.getInt16
+DataView.prototype.getInt32
+DataView.prototype.getInt8
+DataView.prototype.getUint16
+DataView.prototype.getUint32
+DataView.prototype.setUint8
+default-arg
+default-parameters
+destructuring-assignment
+destructuring-binding
+dynamic-import
+export-star-as-namespace-from-module
+FinalizationGroup=skip
+Float32Array
+Float64Array
+for-of
+generators
+globalThis=skip
+hashbang
+host-gc-required=skip
+import.meta
+Int32Array
+Int8Array
+IsHTMLDDA=skip
+json-superset
+let
+Map
+new.target
+numeric-separator-literal
+object-rest
+object-spread
+Object.fromEntries
+Object.is
+optional-catch-binding
+optional-chaining=skip
+Promise.allSettled
+Promise.prototype.finally
+Proxy
+proxy-missing-checks
+Reflect
+Reflect.construct
+Reflect.set
+Reflect.setPrototypeOf
+regexp-dotall
+regexp-lookbehind
+regexp-named-groups
+regexp-unicode-property-escapes
+rest-parameters
+Set
+SharedArrayBuffer
+string-trimming
+String.fromCodePoint
+String.prototype.endsWith
+String.prototype.includes
+String.prototype.matchAll
+String.prototype.trimEnd
+String.prototype.trimStart
+super
+Symbol
+Symbol.asyncIterator
+Symbol.hasInstance
+Symbol.isConcatSpreadable
+Symbol.iterator
+Symbol.match
+Symbol.matchAll
+Symbol.prototype.description
+Symbol.replace
+Symbol.search
+Symbol.species
+Symbol.split
+Symbol.toPrimitive
+Symbol.toStringTag
+Symbol.unscopables
+tail-call-optimization=skip
+template
+top-level-await=skip
+TypedArray
+u180e
+Uint16Array
+Uint8Array
+Uint8ClampedArray
+WeakMap
+WeakRef=skip
+WeakSet
+well-formed-json-stringify
+
+[exclude]
+# list excluded tests and directories here
+
+# intl not supported
+test262/test/intl402/
+
+# incompatible with the "caller" feature
+test262/test/built-ins/Function/prototype/restricted-property-caller.js
+test262/test/built-ins/ThrowTypeError/unique-per-realm-function-proto.js
+
+# slow tests
+#test262/test/built-ins/RegExp/CharacterClassEscapes/
+#test262/test/built-ins/RegExp/property-escapes/
+
+[tests]
+# list test files or use config.testdir
diff --git a/test262bn_errors.txt b/test262bn_errors.txt
new file mode 100644
index 0000000..ac2a707
--- /dev/null
+++ b/test262bn_errors.txt
@@ -0,0 +1,2 @@
+test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeError: $DONE() not called
+test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: strict mode: TypeError: $DONE() not called
diff --git a/test262o.conf b/test262o.conf
new file mode 100644
index 0000000..669dead
--- /dev/null
+++ b/test262o.conf
@@ -0,0 +1,410 @@
+[config]
+# general settings for test262 ES5 version
+
+# framework style: old, new
+style=old
+
+# handle tests tagged as @noStrict: yes, no, skip
+nostrict=yes
+
+# handle tests tagged as @strictOnly: yes, no, skip
+strict=yes
+
+# test mode: default, default-nostrict, default-strict, strict, nostrict, both, all
+mode=default
+
+# output error messages: yes, no
+verbose=yes
+
+# load harness files this directory
+harnessdir=test262o/test/harness
+
+# name of the error file for known errors
+errorfile=test262o_errors.txt
+
+# exclude tests enumerated in this file
+#excludefile=test262o_excluded.txt
+
+# report test results to this file
+reportfile=test262o_report.txt
+
+# enumerate tests from this directory
+testdir=test262o/test/suite
+
+[exclude]
+# list excluded tests and directories here
+
+# intl not supported
+test262o/test/suite/intl402/
+
+# ES6 != ES5: block scoped function definitions allowed in strict mode
+test262o/test/suite/bestPractice/Sbp_A1_T1.js
+test262o/test/suite/bestPractice/Sbp_A2_T1.js
+test262o/test/suite/bestPractice/Sbp_A2_T2.js
+test262o/test/suite/bestPractice/Sbp_A3_T1.js
+test262o/test/suite/bestPractice/Sbp_A3_T2.js
+test262o/test/suite/bestPractice/Sbp_A4_T1.js
+test262o/test/suite/bestPractice/Sbp_A4_T2.js
+test262o/test/suite/bestPractice/Sbp_A5_T2.js
+
+# ES6 != ES5: `y={x};` is shorthand for `y={x:x}`
+test262o/test/suite/ch12/12.1/S12.1_A4_T2.js
+test262o/test/suite/ch12/12.6/12.6.4/S12.6.4_A15.js
+
+# ES6 != ES5: function length property is configurable
+test262o/test/suite/ch11/11.4/11.4.1/11.4.1-5-a-28-s.js
+test262o/test/suite/ch13/13.2/13.2-15-1.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.1/S15.1.2.1_A4.2.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.2/S15.1.2.2_A9.2.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.3/S15.1.2.3_A7.2.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.4/S15.1.2.4_A2.2.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.5/S15.1.2.5_A2.2.js
+test262o/test/suite/ch15/15.1/15.1.3/15.1.3.1/S15.1.3.1_A5.2.js
+test262o/test/suite/ch15/15.1/15.1.3/15.1.3.2/S15.1.3.2_A5.2.js
+test262o/test/suite/ch15/15.1/15.1.3/15.1.3.3/S15.1.3.3_A5.2.js
+test262o/test/suite/ch15/15.1/15.1.3/15.1.3.4/S15.1.3.4_A5.2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-186.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-187.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-191.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-194.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-201.js
+test262o/test/suite/ch15/15.2/15.2.4/15.2.4.2/S15.2.4.2_A9.js
+test262o/test/suite/ch15/15.2/15.2.4/15.2.4.3/S15.2.4.3_A9.js
+test262o/test/suite/ch15/15.2/15.2.4/15.2.4.4/S15.2.4.4_A9.js
+test262o/test/suite/ch15/15.2/15.2.4/15.2.4.5/S15.2.4.5_A9.js
+test262o/test/suite/ch15/15.2/15.2.4/15.2.4.6/S15.2.4.6_A9.js
+test262o/test/suite/ch15/15.2/15.2.4/15.2.4.7/S15.2.4.7_A9.js
+test262o/test/suite/ch15/15.3/15.3.3/15.3.3.2/15.3.3.2-1.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.2/S15.3.4.2_A9.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.3/S15.3.4.3_A9.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.4/S15.3.4.4_A9.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-15-2.js
+test262o/test/suite/ch15/15.3/15.3.5/S15.3.5.1_A2_T1.js
+test262o/test/suite/ch15/15.3/15.3.5/S15.3.5.1_A2_T2.js
+test262o/test/suite/ch15/15.3/15.3.5/S15.3.5.1_A2_T3.js
+test262o/test/suite/ch15/15.4/15.4.3/S15.4.3_A2.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.2/S15.4.4.2_A4.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.3/S15.4.4.3_A4.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.4/S15.4.4.4_A4.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.5/S15.4.4.5_A6.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.6/S15.4.4.6_A5.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.7/S15.4.4.7_A6.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.8/S15.4.4.8_A5.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.9/S15.4.4.9_A5.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.10/S15.4.4.10_A5.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.11/S15.4.4.11_A7.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.12/S15.4.4.12_A5.2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.13/S15.4.4.13_A5.2.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.4/S15.5.4.4_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.5/S15.5.4.5_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.6/S15.5.4.6_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.7/S15.5.4.7_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.8/S15.5.4.8_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.9/S15.5.4.9_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.10/S15.5.4.10_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.11/S15.5.4.11_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.12/S15.5.4.12_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.13/S15.5.4.13_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.14/S15.5.4.14_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.15/S15.5.4.15_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.16/S15.5.4.16_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.17/S15.5.4.17_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.18/S15.5.4.18_A9.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.19/S15.5.4.19_A9.js
+test262o/test/suite/ch15/15.9/15.9.4/15.9.4.2/S15.9.4.2_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.4/15.9.4.3/S15.9.4.3_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.1/S15.9.5.1_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.2/S15.9.5.2_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.3/S15.9.5.3_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.4/S15.9.5.4_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.5/S15.9.5.5_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.6/S15.9.5.6_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.7/S15.9.5.7_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.8/S15.9.5.8_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.9/S15.9.5.9_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.10/S15.9.5.10_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.11/S15.9.5.11_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.12/S15.9.5.12_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.13/S15.9.5.13_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.14/S15.9.5.14_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.15/S15.9.5.15_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.16/S15.9.5.16_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.17/S15.9.5.17_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.18/S15.9.5.18_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.19/S15.9.5.19_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.20/S15.9.5.20_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.21/S15.9.5.21_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.22/S15.9.5.22_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.23/S15.9.5.23_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.24/S15.9.5.24_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.25/S15.9.5.25_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.26/S15.9.5.26_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.27/S15.9.5.27_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.28/S15.9.5.28_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.29/S15.9.5.29_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.30/S15.9.5.30_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.31/S15.9.5.31_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.32/S15.9.5.32_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.33/S15.9.5.33_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.34/S15.9.5.34_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.35/S15.9.5.35_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.36/S15.9.5.36_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.37/S15.9.5.37_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.38/S15.9.5.38_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.39/S15.9.5.39_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.40/S15.9.5.40_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.41/S15.9.5.41_A3_T2.js
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.42/S15.9.5.42_A3_T2.js
+test262o/test/suite/ch15/15.10/15.10.6/15.10.6.2/S15.10.6.2_A9.js
+test262o/test/suite/ch15/15.10/15.10.6/15.10.6.3/S15.10.6.3_A9.js
+test262o/test/suite/ch15/15.10/15.10.6/15.10.6.4/S15.10.6.4_A9.js
+
+# ES6 != ES5: object literals may have duplicates
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5-4-4-a-1-s.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-b-1.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-b-2.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-c-1.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-c-2.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-d-1.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-d-2.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-d-3.js
+test262o/test/suite/ch11/11.1/11.1.5/11.1.5_4-4-d-4.js
+
+# ES6 != ES5: Date.prototype is no longer an instance of Date
+test262o/test/suite/ch15/15.9/15.9.5/15.9.5.40/15.9.5.40_1.js
+
+# ES6 != ES5: Object.getPrototypeOf converts argument to object
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-1.js
+
+# ES6 != ES5: Object.getPrototypeOf(NativeError)
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-2-12.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-2-13.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-2-14.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-2-15.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-2-16.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.2/15.2.3.2-2-17.js
+
+# ES6 != ES5: Object.getOwnPropertyDescriptor converts argument to object
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-1.js
+
+# ES6 != ES5: Object.getOwnPropertyNames converts argument to object
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.4/15.2.3.4-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.4/15.2.3.4-1-5.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.4/15.2.3.4-1.js
+
+# ES6 != ES5: Object.seal accepts all types
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.8/15.2.3.8-1-1.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.8/15.2.3.8-1-2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.8/15.2.3.8-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.8/15.2.3.8-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.8/15.2.3.8-1.js
+
+# ES6 != ES5: Object.freeze accepts all types
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.9/15.2.3.9-1-1.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.9/15.2.3.9-1-2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.9/15.2.3.9-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.9/15.2.3.9-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.9/15.2.3.9-1.js
+
+# ES6 != ES5: Object.preventExtensions accepts all types
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.10/15.2.3.10-1-1.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.10/15.2.3.10-1-2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.10/15.2.3.10-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.10/15.2.3.10-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.10/15.2.3.10-1.js
+
+# ES6 != ES5: Object.isSealed accepts all types
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.11/15.2.3.11-1.js
+
+# ES6 != ES5: Object.isFrozen accepts all types
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.12/15.2.3.12-1-1.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.12/15.2.3.12-1-2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.12/15.2.3.12-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.12/15.2.3.12-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.12/15.2.3.12-1.js
+
+# ES6 != ES5: Object.isExtensible accepts all types
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.13/15.2.3.13-1-1.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.13/15.2.3.13-1-2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.13/15.2.3.13-1-3.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.13/15.2.3.13-1-4.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.13/15.2.3.13-1.js
+
+# ES6 != ES5: Object.keys converts argument to object
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.14/15.2.3.14-1-1.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.14/15.2.3.14-1-2.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.14/15.2.3.14-1-3.js
+
+# ES6 != ES5: source and other properties of RegExp.prototype are not own properties
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-212.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-213.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-214.js
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-215.js
+
+# ES6 != ES5: String numeric object properties are enumerated first
+test262o/test/suite/ch15/15.2/15.2.3/15.2.3.4/15.2.3.4-4-44.js
+
+# ES6: new RegExp(regex, flags) is valid
+test262o/test/suite/ch15/15.10/15.10.3/S15.10.3.1_A2_T1.js
+test262o/test/suite/ch15/15.10/15.10.3/S15.10.3.1_A2_T2.js
+test262o/test/suite/ch15/15.10/15.10.4/15.10.4.1/15.10.4.1-1.js
+test262o/test/suite/ch15/15.10/15.10.4/S15.10.4.1_A2_T1.js
+test262o/test/suite/ch15/15.10/15.10.4/S15.10.4.1_A2_T2.js
+
+# ES6 != ES5: RegExp.prototype.test behavior
+test262o/test/suite/ch15/15.10/15.10.6/15.10.6.2/S15.10.6.2_A5_T3.js
+
+# ES6 != ES5: source, global, ignoreCase, multiline, lastIndex are not data properties
+# of RegExp objects and RegExp.prototype is not a RegExp object
+test262o/test/suite/ch15/15.10/15.10.6/15.10.6.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.1/15.10.7.1-1.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.1/15.10.7.1-2.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.1/S15.10.7.1_A8.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.1/S15.10.7.1_A9.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.1/S15.10.7.1_A10.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.2/15.10.7.2-1.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.2/15.10.7.2-2.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.2/S15.10.7.2_A8.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.2/S15.10.7.2_A9.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.2/S15.10.7.2_A10.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.3/15.10.7.3-1.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.3/15.10.7.3-2.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.3/S15.10.7.3_A8.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.3/S15.10.7.3_A9.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.3/S15.10.7.3_A10.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.4/15.10.7.4-1.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.4/15.10.7.4-2.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.4/S15.10.7.4_A8.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.4/S15.10.7.4_A9.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.4/S15.10.7.4_A10.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.5/15.10.7.5-1.js
+test262o/test/suite/ch15/15.10/15.10.7/15.10.7.5/15.10.7.5-2.js
+
+# ES6 != ES5: Error.prototype is a normal object
+test262o/test/suite/ch15/15.11/15.11.4/S15.11.4_A2.js
+
+# ES6 different ToLength() semantics
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.5/S15.4.4.5_A4_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.6/S15.4.4.6_A2_T2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.6/S15.4.4.6_A3_T1.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.6/S15.4.4.6_A3_T2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.6/S15.4.4.6_A3_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.7/S15.4.4.7_A2_T2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.7/S15.4.4.7_A4_T1.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.7/S15.4.4.7_A4_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.8/S15.4.4.8_A3_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.9/S15.4.4.9_A3_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.10/S15.4.4.10_A3_T1.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.10/S15.4.4.10_A3_T2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.10/S15.4.4.10_A3_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.11/S15.4.4.11_A4_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.12/S15.4.4.12_A3_T1.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.12/S15.4.4.12_A3_T3.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.13/S15.4.4.13_A3_T2.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-8.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-28.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.14/15.4.4.14-3-29.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-28.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-3-8.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-3-29.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-8.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-28.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-3-29.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.18/15.4.4.18-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.18/15.4.4.18-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.18/15.4.4.18-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-8.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-28.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.19/15.4.4.19-3-29.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.20/15.4.4.20-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.20/15.4.4.20-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.20/15.4.4.20-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.21/15.4.4.21-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.21/15.4.4.21-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.21/15.4.4.21-3-25.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.22/15.4.4.22-3-7.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.22/15.4.4.22-3-12.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.22/15.4.4.22-3-25.js
+
+# ES6 different ToLength() semantics causes near infinite runtime
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.18/15.4.4.18-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.20/15.4.4.20-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.21/15.4.4.21-3-14.js
+test262o/test/suite/ch15/15.4/15.4.4/15.4.4.22/15.4.4.22-3-14.js
+
+# ES6 arguments/caller changes
+test262o/test/suite/ch10/10.6/10.6-13-b-1-s.js
+test262o/test/suite/ch10/10.6/10.6-13-b-2-s.js
+test262o/test/suite/ch10/10.6/10.6-13-b-3-s.js
+test262o/test/suite/ch10/10.6/10.6-14-1-s.js
+test262o/test/suite/ch10/10.6/10.6-14-b-1-s.js
+test262o/test/suite/ch10/10.6/10.6-14-b-4-s.js
+test262o/test/suite/ch13/13.2/13.2-29-s.js
+test262o/test/suite/ch13/13.2/13.2-30-s.js
+test262o/test/suite/ch13/13.2/13.2-31-s.js
+test262o/test/suite/ch13/13.2/13.2-32-s.js
+test262o/test/suite/ch13/13.2/13.2-33-s.js
+test262o/test/suite/ch13/13.2/13.2-34-s.js
+test262o/test/suite/ch13/13.2/13.2-35-s.js
+test262o/test/suite/ch13/13.2/13.2-36-s.js
+test262o/test/suite/ch13/13.2/S13.2.3_A1.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-20-1.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-20-4.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-20-5.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-21-1.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-21-4.js
+test262o/test/suite/ch15/15.3/15.3.4/15.3.4.5/15.3.4.5-21-5.js
+
+# u180e is no longer considered as a space
+test262o/test/suite/ch09/9.3/9.3.1/S9.3.1_A2.js
+test262o/test/suite/ch09/9.3/9.3.1/S9.3.1_A3_T1.js
+test262o/test/suite/ch09/9.3/9.3.1/S9.3.1_A3_T2.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.2/S15.1.2.2_A2_T10.js
+test262o/test/suite/ch15/15.1/15.1.2/15.1.2.3/S15.1.2.3_A2_T10.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.20/15.5.4.20-3-2.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.20/15.5.4.20-3-3.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.20/15.5.4.20-3-4.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.20/15.5.4.20-3-5.js
+test262o/test/suite/ch15/15.5/15.5.4/15.5.4.20/15.5.4.20-3-6.js
+test262o/test/suite/ch15/15.10/15.10.2/15.10.2.12/S15.10.2.12_A1_T1.js
+test262o/test/suite/ch15/15.10/15.10.2/15.10.2.12/S15.10.2.12_A2_T1.js
+
+# E6 eval return value is different
+test262o/test/suite/ch12/12.6/12.6.3/S12.6.3_A9.js
+test262o/test/suite/ch12/12.6/12.6.3/S12.6.3_A9.1.js
+
+# ECMA 2019 optional-catch-binding feature allows try{}catch{}
+test262o/test/suite/ch12/12.14/S12.14_A16_T4.js
+
+# Syntax error instead of ReferenceError in ES2020
+test262o/test/suite/ch11/11.13/11.13.1/11.13.1-1-1.js
+test262o/test/suite/ch11/11.13/11.13.1/11.13.1-1-2.js
+test262o/test/suite/ch11/11.13/11.13.1/11.13.1-1-3.js
+test262o/test/suite/ch11/11.13/11.13.1/11.13.1-1-4.js
+
+[tests]
+# list test files or use config.testdir
diff --git a/test262o_errors.txt b/test262o_errors.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test262o_errors.txt
diff --git a/tests/microbench.js b/tests/microbench.js
new file mode 100644
index 0000000..1b19de4
--- /dev/null
+++ b/tests/microbench.js
@@ -0,0 +1,1004 @@
+/*
+ * Javascript Micro benchmark
+ *
+ * Copyright (c) 2017-2019 Fabrice Bellard
+ * Copyright (c) 2017-2019 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+"use strict";
+
+function pad(str, n) {
+    str += "";
+    while (str.length < n)
+        str += " ";
+    return str;
+}
+
+function pad_left(str, n) {
+    str += "";
+    while (str.length < n)
+        str = " " + str;
+    return str;
+}
+
+function pad_center(str, n) {
+    str += "";
+    while (str.length < n) {
+        if ((n - str.length) & 1)
+            str = str + " ";
+        else
+            str = " " + str;
+    }
+    return str;
+}
+
+function toPrec(n, prec) {
+    var i, s;
+    for (i = 0; i < prec; i++)
+        n *= 10;
+    s = "" + Math.round(n);
+    for (i = s.length - prec; i <= 0; i++)
+        s = "0" + s;
+    if (prec > 0)
+        s = s.substring(0, i) + "." + s.substring(i);
+    return s;
+}
+                
+var ref_data;
+var log_data;
+
+var heads  = [ "TEST", "N", "TIME (ns)", "REF (ns)", "SCORE (%)" ];
+var widths = [    22,   10,          9,     9,       9 ];
+var precs  = [     0,   0,           2,     2,       2 ];
+var total  = [     0,   0,           0,     0,       0 ];
+var total_score = 0;
+var total_scale = 0;
+
+if (typeof console == "undefined") {
+    var console = { log: print };
+}
+
+function log_line() {
+    var i, n, s, a;
+    s = "";
+    for (i = 0, n = arguments.length; i < n; i++) {
+        if (i > 0)
+            s += " ";
+        a = arguments[i];
+        if (typeof a == "number") {
+            total[i] += a;
+            a = toPrec(a, precs[i]);
+            s += pad_left(a, widths[i]);
+        } else {
+            s += pad_left(a, widths[i]);
+        }
+    }
+    console.log(s);
+}
+
+var clocks_per_sec = 1000000;
+var max_iterations = 100;
+var clock_threshold = 2000;  /* favoring short measuring spans */
+var min_n_argument = 1;
+var __date_clock;
+
+if (typeof __date_clock != "function") {
+    console.log("using fallback millisecond clock");
+    clocks_per_sec = 1000;
+    max_iterations = 10;
+    clock_threshold = 100;
+}
+
+function log_one(text, n, ti) {
+    var ref;
+
+    if (ref_data)
+        ref = ref_data[text];
+    else
+        ref = null;
+
+    ti = Math.round(ti * 100) / 100;
+    log_data[text] = ti;
+    if (typeof ref === "number") {
+        log_line(text, n, ti, ref, ti * 100 / ref);
+        total_score += ti * 100 / ref;
+        total_scale += 100;
+    } else {
+        log_line(text, n, ti);
+        total_score += 100;
+        total_scale += 100;
+    }
+}
+
+function bench(f, text)
+{
+    var i, j, n, t, t1, ti, nb_its, ref, ti_n, ti_n1, min_ti;
+
+    nb_its = n = 1;
+    if (f.bench) {
+        ti_n = f(text);
+    } else {
+        var clock = __date_clock;
+        if (typeof clock != "function")
+            clock = Date.now;
+        ti_n = 1000000000;
+        min_ti = clock_threshold / 10;
+        for(i = 0; i < 30; i++) {
+            ti = 1000000000;
+            for (j = 0; j < max_iterations; j++) {
+                t = clock();
+                while ((t1 = clock()) == t)
+                    continue;
+                nb_its = f(n);
+                if (nb_its < 0)
+                    return; // test failure
+                t1 = clock() - t1;
+                if (ti > t1)
+                    ti = t1;
+            }
+            if (ti >= min_ti) {
+                ti_n1 = ti / nb_its;
+                if (ti_n > ti_n1)
+                    ti_n = ti_n1;
+            }
+            if (ti >= clock_threshold && n >= min_n_argument)
+                break;
+
+            n = n * [ 2, 2.5, 2 ][i % 3];
+        }
+        // to use only the best timing from the last loop, uncomment below
+        //ti_n = ti / nb_its;
+    }
+    /* nano seconds per iteration */
+    log_one(text, n, ti_n * 1e9 / clocks_per_sec);
+}
+
+var global_res; /* to be sure the code is not optimized */
+
+function empty_loop(n) {
+    var j;
+    for(j = 0; j < n; j++) {
+    }
+    return n;
+}
+
+function date_now(n) {
+    var j;
+    for(j = 0; j < n; j++) {
+        Date.now();
+    }
+    return n;
+}
+
+function prop_read(n)
+{
+    var obj, sum, j;
+    obj = {a: 1, b: 2, c:3, d:4 };
+    sum = 0;
+    for(j = 0; j < n; j++) {
+        sum += obj.a;
+        sum += obj.b;
+        sum += obj.c;
+        sum += obj.d;
+    }
+    global_res = sum;
+    return n * 4;
+}
+
+function prop_write(n)
+{
+    var obj, j;
+    obj = {a: 1, b: 2, c:3, d:4 };
+    for(j = 0; j < n; j++) {
+        obj.a = j;
+        obj.b = j;
+        obj.c = j;
+        obj.d = j;
+    }
+    return n * 4;
+}
+
+function prop_create(n)
+{
+    var obj, j;
+    for(j = 0; j < n; j++) {
+        obj = new Object();
+        obj.a = 1;
+        obj.b = 2;
+        obj.c = 3;
+        obj.d = 4;
+    }
+    return n * 4;
+}
+
+function array_read(n)
+{
+    var tab, len, sum, i, j;
+    tab = [];
+    len = 10;
+    for(i = 0; i < len; i++)
+        tab[i] = i;
+    sum = 0;
+    for(j = 0; j < n; j++) {
+        sum += tab[0];
+        sum += tab[1];
+        sum += tab[2];
+        sum += tab[3];
+        sum += tab[4];
+        sum += tab[5];
+        sum += tab[6];
+        sum += tab[7];
+        sum += tab[8];
+        sum += tab[9];
+    }
+    global_res = sum;
+    return len * n;
+}
+
+function array_write(n)
+{
+    var tab, len, i, j;
+    tab = [];
+    len = 10;
+    for(i = 0; i < len; i++)
+        tab[i] = i;
+    for(j = 0; j < n; j++) {
+        tab[0] = j;
+        tab[1] = j;
+        tab[2] = j;
+        tab[3] = j;
+        tab[4] = j;
+        tab[5] = j;
+        tab[6] = j;
+        tab[7] = j;
+        tab[8] = j;
+        tab[9] = j;
+    }
+    return len * n;
+}
+
+function array_prop_create(n)
+{
+    var tab, i, j, len;
+    len = 1000;
+    for(j = 0; j < n; j++) {
+        tab = [];
+        for(i = 0; i < len; i++)
+            tab[i] = i;
+    }
+    return len * n;
+}
+
+function array_length_decr(n)
+{
+    var tab, i, j, len;
+    len = 1000;
+    tab = [];
+    for(i = 0; i < len; i++)
+        tab[i] = i;
+    for(j = 0; j < n; j++) {
+        for(i = len - 1; i >= 0; i--)
+            tab.length = i;
+    }
+    return len * n;
+}
+
+function array_hole_length_decr(n)
+{
+    var tab, i, j, len;
+    len = 1000;
+    tab = [];
+    for(i = 0; i < len; i++) {
+        if (i != 3)
+            tab[i] = i;
+    }
+    for(j = 0; j < n; j++) {
+        for(i = len - 1; i >= 0; i--)
+            tab.length = i;
+    }
+    return len * n;
+}
+
+function array_push(n)
+{
+    var tab, i, j, len;
+    len = 500;
+    for(j = 0; j < n; j++) {
+        tab = [];
+        for(i = 0; i < len; i++)
+            tab.push(i);
+    }
+    return len * n;
+}
+
+function array_pop(n)
+{
+    var tab, i, j, len, sum;
+    len = 500;
+    for(j = 0; j < n; j++) {
+        tab = [];
+        for(i = 0; i < len; i++)
+            tab[i] = i;
+        sum = 0;
+        for(i = 0; i < len; i++)
+            sum += tab.pop();
+        global_res = sum;
+    }
+    return len * n;
+}
+
+function typed_array_read(n)
+{
+    var tab, len, sum, i, j;
+    len = 10;
+    tab = new Int32Array(len);
+    for(i = 0; i < len; i++)
+        tab[i] = i;
+    sum = 0;
+    for(j = 0; j < n; j++) {
+        sum += tab[0];
+        sum += tab[1];
+        sum += tab[2];
+        sum += tab[3];
+        sum += tab[4];
+        sum += tab[5];
+        sum += tab[6];
+        sum += tab[7];
+        sum += tab[8];
+        sum += tab[9];
+    }
+    global_res = sum;
+    return len * n;
+}
+
+function typed_array_write(n)
+{
+    var tab, len, i, j;
+    len = 10;
+    tab = new Int32Array(len);
+    for(i = 0; i < len; i++)
+        tab[i] = i;
+    for(j = 0; j < n; j++) {
+        tab[0] = j;
+        tab[1] = j;
+        tab[2] = j;
+        tab[3] = j;
+        tab[4] = j;
+        tab[5] = j;
+        tab[6] = j;
+        tab[7] = j;
+        tab[8] = j;
+        tab[9] = j;
+    }
+    return len * n;
+}
+
+var global_var0;
+
+function global_read(n)
+{
+    var sum, j;
+    global_var0 = 0;
+    sum = 0;
+    for(j = 0; j < n; j++) {
+        sum += global_var0;
+        sum += global_var0;
+        sum += global_var0;
+        sum += global_var0;
+    }
+    global_res = sum;
+    return n * 4;
+}
+
+var global_write =
+    (1, eval)(`(function global_write(n)
+           {
+               var j;
+               for(j = 0; j < n; j++) {
+                   global_var0 = j;
+                   global_var0 = j;
+                   global_var0 = j;
+                   global_var0 = j;
+               }
+               return n * 4;
+           })`);
+
+function global_write_strict(n)
+{
+    var j;
+    for(j = 0; j < n; j++) {
+        global_var0 = j;
+        global_var0 = j;
+        global_var0 = j;
+        global_var0 = j;
+    }
+    return n * 4;
+}
+
+function local_destruct(n)
+{
+    var j, v1, v2, v3, v4;
+    var array = [ 1, 2, 3, 4, 5];
+    var o = { a:1, b:2, c:3, d:4 };
+    var a, b, c, d;
+    for(j = 0; j < n; j++) {
+        [ v1, v2,, v3, ...v4] = array;
+        ({ a, b, c, d } = o);
+        ({ a: a, b: b, c: c, d: d } = o);
+    }
+    return n * 12;
+}
+
+var global_v1, global_v2, global_v3, global_v4;
+var global_a, global_b, global_c, global_d;
+
+var global_destruct =
+    (1, eval)(`(function global_destruct(n)
+           {
+               var j, v1, v2, v3, v4;
+               var array = [ 1, 2, 3, 4, 5 ];
+               var o = { a:1, b:2, c:3, d:4 };
+               var a, b, c, d;
+               for(j = 0; j < n; j++) {
+                   [ global_v1, global_v2,, global_v3, ...global_v4] = array;
+                   ({ a: global_a, b: global_b, c: global_c, d: global_d } = o);
+               }
+               return n * 8;
+          })`);
+
+function global_destruct_strict(n)
+{
+    var j, v1, v2, v3, v4;
+    var array = [ 1, 2, 3, 4, 5 ];
+    var o = { a:1, b:2, c:3, d:4 };
+    var a, b, c, d;
+    for(j = 0; j < n; j++) {
+        [ global_v1, global_v2,, global_v3, ...global_v4] = array;
+        ({ a: global_a, b: global_b, c: global_c, d: global_d } = o);
+    }
+    return n * 8;
+}
+
+function func_call(n)
+{
+    function f(a)
+    {
+        return 1;
+    }
+
+    var j, sum;
+    sum = 0;
+    for(j = 0; j < n; j++) {
+        sum += f(j);
+        sum += f(j);
+        sum += f(j);
+        sum += f(j);
+    }
+    global_res = sum;
+    return n * 4;
+}
+
+function closure_var(n)
+{
+    function f(a)
+    {
+        sum++;
+    }
+
+    var j, sum;
+    sum = 0;
+    for(j = 0; j < n; j++) {
+        f(j);
+        f(j);
+        f(j);
+        f(j);
+    }
+    global_res = sum;
+    return n * 4;
+}
+
+function int_arith(n)
+{
+    var i, j, sum;
+    global_res = 0;
+    for(j = 0; j < n; j++) {
+        sum = 0;
+        for(i = 0; i < 1000; i++) {
+            sum += i * i;
+        }
+        global_res += sum;
+    }
+    return n * 1000;
+}
+
+function float_arith(n)
+{
+    var i, j, sum, a, incr, a0;
+    global_res = 0;
+    a0 = 0.1;
+    incr = 1.1;
+    for(j = 0; j < n; j++) {
+        sum = 0;
+        a = a0;
+        for(i = 0; i < 1000; i++) {
+            sum += a * a;
+            a += incr;
+        }
+        global_res += sum;
+    }
+    return n * 1000;
+}
+
+function bigfloat_arith(n)
+{
+    var i, j, sum, a, incr, a0;
+    global_res = 0;
+    a0 = BigFloat("0.1");
+    incr = BigFloat("1.1");
+    for(j = 0; j < n; j++) {
+        sum = 0;
+        a = a0;
+        for(i = 0; i < 1000; i++) {
+            sum += a * a;
+            a += incr;
+        }
+        global_res += sum;
+    }
+    return n * 1000;
+}
+
+function float256_arith(n)
+{
+    return BigFloatEnv.setPrec(bigfloat_arith.bind(null, n), 237, 19);
+}
+
+function bigint_arith(n, bits)
+{
+    var i, j, sum, a, incr, a0, sum0;
+    sum0 = global_res = BigInt(0);
+    a0 = BigInt(1) << BigInt(Math.floor((bits - 10) * 0.5));
+    incr = BigInt(1);
+    for(j = 0; j < n; j++) {
+        sum = sum0;
+        a = a0;
+        for(i = 0; i < 1000; i++) {
+            sum += a * a;
+            a += incr;
+        }
+        global_res += sum;
+    }
+    return n * 1000;
+}
+
+function bigint64_arith(n)
+{
+    return bigint_arith(n, 64);
+}
+
+function bigint256_arith(n)
+{
+    return bigint_arith(n, 256);
+}
+
+function set_collection_add(n)
+{
+    var s, i, j, len = 100;
+    s = new Set();
+    for(j = 0; j < n; j++) {
+        for(i = 0; i < len; i++) {
+            s.add(String(i), i);
+        }
+        for(i = 0; i < len; i++) {
+            if (!s.has(String(i)))
+                throw Error("bug in Set");
+        }
+    }
+    return n * len;
+}
+
+function array_for(n)
+{
+    var r, i, j, sum;
+    r = [];
+    for(i = 0; i < 100; i++)
+        r[i] = i;
+    for(j = 0; j < n; j++) {
+        sum = 0;
+        for(i = 0; i < 100; i++) {
+            sum += r[i];
+        }
+        global_res = sum;
+    }
+    return n * 100;
+}
+
+function array_for_in(n)
+{
+    var r, i, j, sum;
+    r = [];
+    for(i = 0; i < 100; i++)
+        r[i] = i;
+    for(j = 0; j < n; j++) {
+        sum = 0;
+        for(i in r) {
+            sum += r[i];
+        }
+        global_res = sum;
+    }
+    return n * 100;
+}
+
+function array_for_of(n)
+{
+    var r, i, j, sum;
+    r = [];
+    for(i = 0; i < 100; i++)
+        r[i] = i;
+    for(j = 0; j < n; j++) {
+        sum = 0;
+        for(i of r) {
+            sum += i;
+        }
+        global_res = sum;
+    }
+    return n * 100;
+}
+
+function math_min(n)
+{
+    var i, j, r;
+    r = 0;
+    for(j = 0; j < n; j++) {
+        for(i = 0; i < 1000; i++)
+            r = Math.min(i, 500);
+        global_res = r;
+    }
+    return n * 1000;
+}
+
+/* incremental string contruction as local var */
+function string_build1(n)
+{
+    var i, j, r;
+    r = "";
+    for(j = 0; j < n; j++) {
+        for(i = 0; i < 100; i++)
+            r += "x";
+        global_res = r;
+    }
+    return n * 100;
+}
+
+/* incremental string contruction as arg */
+function string_build2(n, r)
+{
+    var i, j;
+    r = "";
+    for(j = 0; j < n; j++) {
+        for(i = 0; i < 100; i++)
+            r += "x";
+        global_res = r;
+    }
+    return n * 100;
+}
+
+/* incremental string contruction by prepending */
+function string_build3(n, r)
+{
+    var i, j;
+    r = "";
+    for(j = 0; j < n; j++) {
+        for(i = 0; i < 100; i++)
+            r = "x" + r;
+        global_res = r;
+    }
+    return n * 100;
+}
+
+/* incremental string contruction with multiple reference */
+function string_build4(n)
+{
+    var i, j, r, s;
+    r = "";
+    for(j = 0; j < n; j++) {
+        for(i = 0; i < 100; i++) {
+            s = r;
+            r += "x";
+        }
+        global_res = r;
+    }
+    return n * 100;
+}
+
+/* sort bench */
+
+function sort_bench(text) {
+    function random(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[(Math.random() * n) >> 0];
+    }
+    function random8(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[(Math.random() * 256) >> 0];
+    }
+    function random1(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[(Math.random() * 2) >> 0];
+    }
+    function hill(arr, n, def) {
+        var mid = n >> 1;
+        for (var i = 0; i < mid; i++)
+            arr[i] = def[i];
+        for (var i = mid; i < n; i++)
+            arr[i] = def[n - i];
+    }
+    function comb(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[(i & 1) * i];
+    }
+    function crisscross(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[(i & 1) ? n - i : i];
+    }
+    function zero(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[0];
+    }
+    function increasing(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[i];
+    }
+    function decreasing(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[n - 1 - i];
+    }
+    function alternate(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[i ^ 1];
+    }
+    function jigsaw(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[i % (n >> 4)];
+    }
+    function incbutone(arr, n, def) {
+        for (var i = 0; i < n; i++)
+            arr[i] = def[i];
+        if (n > 0)
+            arr[n >> 2] = def[n];
+    }
+    function incbutfirst(arr, n, def) {
+        if (n > 0)
+            arr[0] = def[n];
+        for (var i = 1; i < n; i++)
+            arr[i] = def[i];
+    }
+    function incbutlast(arr, n, def) {
+        for (var i = 0; i < n - 1; i++)
+            arr[i] = def[i + 1];
+        if (n > 0)
+            arr[n - 1] = def[0];
+    }
+
+    var sort_cases = [ random, random8, random1, jigsaw, hill, comb,
+                      crisscross, zero, increasing, decreasing, alternate,
+                      incbutone, incbutlast, incbutfirst ];
+
+    var n = sort_bench.array_size || 10000;
+    var array_type = sort_bench.array_type || Array;
+    var def, arr;
+    var i, j, x, y;
+    var total = 0;
+
+    var save_total_score = total_score;
+    var save_total_scale = total_scale;
+
+    // initialize default sorted array (n + 1 elements)
+    def = new array_type(n + 1);
+    if (array_type == Array) {
+        for (i = 0; i <= n; i++) {
+            def[i] = i + "";
+        }
+    } else {
+        for (i = 0; i <= n; i++) {
+            def[i] = i;
+        }
+    }
+    def.sort();
+    for (var f of sort_cases) {
+        var ti = 0, tx = 0;
+        for (j = 0; j < 100; j++) {
+            arr = new array_type(n);
+            f(arr, n, def);
+            var t1 = __date_clock();
+            arr.sort();
+            t1 = __date_clock() - t1;
+            tx += t1;
+            if (!ti || ti > t1)
+                ti = t1;
+            if (tx >= clocks_per_sec)
+                break;
+        }
+        total += ti;
+
+        i = 0;
+        x = arr[0];
+        if (x !== void 0) {
+            for (i = 1; i < n; i++) {
+                y = arr[i];
+                if (y === void 0)
+                    break;
+                if (x > y)
+                    break;
+                x = y;
+            }
+        }
+        while (i < n && arr[i] === void 0)
+            i++;
+        if (i < n) {
+            console.log("sort_bench: out of order error for " + f.name +
+                        " at offset " + (i - 1) +
+                        ": " + arr[i - 1] + " > " + arr[i]);
+        }
+        if (sort_bench.verbose)
+            log_one("sort_" + f.name, n, ti, n * 100);
+    }
+    total_score = save_total_score;
+    total_scale = save_total_scale;
+    return total / n / 1000;
+}
+sort_bench.bench = true;
+sort_bench.verbose = false;
+
+function load_result(filename)
+{
+    var f, str, res;
+    if (typeof std === "undefined")
+        return null;
+    try {
+        f = std.open(filename, "r");
+    } catch(e) {
+        return null;
+    }
+    str = f.readAsString();
+    res = JSON.parse(str);
+    f.close();
+    return res;
+}
+
+function save_result(filename, obj)
+{
+    var f;
+    if (typeof std === "undefined")
+        return;
+    f = std.open(filename, "w");
+    f.puts(JSON.stringify(obj, null, 2));
+    f.puts("\n");
+    f.close();
+}
+
+function main(argc, argv, g)
+{
+    var test_list = [
+        empty_loop,
+        date_now,
+        prop_read,
+        prop_write,
+        prop_create,
+        array_read,
+        array_write,
+        array_prop_create,
+        array_length_decr,
+        array_hole_length_decr,
+        array_push,
+        array_pop,
+        typed_array_read,
+        typed_array_write,
+        global_read,
+        global_write,
+        global_write_strict,
+        local_destruct,
+        global_destruct,
+        global_destruct_strict,
+        func_call,
+        closure_var,
+        int_arith,
+        float_arith,
+        set_collection_add,
+        array_for,
+        array_for_in,
+        array_for_of,
+        math_min,
+        string_build1,
+        string_build2,
+        //string_build3,
+        //string_build4,
+        sort_bench,
+    ];
+    var tests = [];
+    var i, j, n, f, name;
+    
+    if (typeof BigInt == "function") {
+        /* BigInt test */
+        test_list.push(bigint64_arith);
+        test_list.push(bigint256_arith);
+    }
+    if (typeof BigFloat == "function") {
+        /* BigFloat test */
+        test_list.push(float256_arith);
+    }
+    
+    for (i = 1; i < argc;) {
+        name = argv[i++];
+        if (name == "-a") {
+            sort_bench.verbose = true;
+            continue;
+        }
+        if (name == "-t") {
+            name = argv[i++];
+            sort_bench.array_type = g[name];
+            if (typeof sort_bench.array_type != "function") {
+                console.log("unknown array type: " + name);
+                return 1;
+            }
+            continue;
+        }
+        if (name == "-n") {
+            sort_bench.array_size = +argv[i++];
+            continue;
+        }
+        for (j = 0; j < test_list.length; j++) {
+            f = test_list[j];
+            if (name === f.name) {
+                tests.push(f);
+                break;
+            }
+        }
+        if (j == test_list.length) {
+            console.log("unknown benchmark: " + name);
+            return 1;
+        }
+    }
+    if (tests.length == 0)
+        tests = test_list;
+
+    ref_data = load_result("microbench.txt");
+    log_data = {};
+    log_line.apply(null, heads);
+    n = 0;
+
+    for(i = 0; i < tests.length; i++) {
+        f = tests[i];
+        bench(f, f.name, ref_data, log_data);
+        if (ref_data && ref_data[f.name])
+            n++;
+    }
+    if (ref_data)
+        log_line("total", "", total[2], total[3], total_score * 100 / total_scale);
+    else
+        log_line("total", "", total[2]);
+        
+    if (tests == test_list)
+        save_result("microbench-new.txt", log_data);
+}
+
+if (!scriptArgs)
+    scriptArgs = [];
+main(scriptArgs.length, scriptArgs, this);
diff --git a/tests/test262.patch b/tests/test262.patch
new file mode 100644
index 0000000..59f08ec
--- /dev/null
+++ b/tests/test262.patch
@@ -0,0 +1,71 @@
+diff --git a/harness/atomicsHelper.js b/harness/atomicsHelper.js
+index 135c16e..b19f2ef 100644
+--- a/harness/atomicsHelper.js
++++ b/harness/atomicsHelper.js
+@@ -222,10 +222,14 @@ $262.agent.waitUntil = function(typedArray, index, expected) {
+  *   }
+  */
+ $262.agent.timeouts = {
+-  yield: 100,
+-  small: 200,
+-  long: 1000,
+-  huge: 10000,
++//  yield: 100,
++//  small: 200,
++//  long: 1000,
++//  huge: 10000,
++  yield: 20,
++  small: 20,
++  long: 100,
++  huge: 1000,
+ };
+ 
+ /**
+diff --git a/harness/regExpUtils.js b/harness/regExpUtils.js
+index 2abfee3..e7c07b1 100644
+--- a/harness/regExpUtils.js
++++ b/harness/regExpUtils.js
+@@ -5,24 +5,27 @@ description: |
+     Collection of functions used to assert the correctness of RegExp objects.
+ ---*/
+ 
++if ($262 && typeof $262.codePointRange === "function") {
++    /* use C function to build the codePointRange (much faster with
++       slow JS engines) */
++    codePointRange = $262.codePointRange;
++} else {
++    codePointRange = function codePointRange(start, end) {
++        const codePoints = [];
++        let length = 0;
++        for (codePoint = start; codePoint < end; codePoint++) {
++            codePoints[length++] = codePoint;
++        }
++        return String.fromCodePoint.apply(null, codePoints);
++    }
++}
++
+ function buildString({ loneCodePoints, ranges }) {
+-  const CHUNK_SIZE = 10000;
+-  let result = Reflect.apply(String.fromCodePoint, null, loneCodePoints);
+-  for (let i = 0; i < ranges.length; i++) {
+-    const range = ranges[i];
+-    const start = range[0];
+-    const end = range[1];
+-    const codePoints = [];
+-    for (let length = 0, codePoint = start; codePoint <= end; codePoint++) {
+-      codePoints[length++] = codePoint;
+-      if (length === CHUNK_SIZE) {
+-        result += Reflect.apply(String.fromCodePoint, null, codePoints);
+-        codePoints.length = length = 0;
+-      }
++    let result = String.fromCodePoint.apply(null, loneCodePoints);
++    for (const [start, end] of ranges) {
++        result += codePointRange(start, end + 1);
+     }
+-    result += Reflect.apply(String.fromCodePoint, null, codePoints);
+-  }
+-  return result;
++    return result;
+ }
+ 
+ function testPropertyEscapes(regex, string, expression) {
diff --git a/tests/test_bignum.js b/tests/test_bignum.js
new file mode 100644
index 0000000..344aaf9
--- /dev/null
+++ b/tests/test_bignum.js
@@ -0,0 +1,269 @@
+"use math";
+"use strict";
+
+function assert(actual, expected, message) {
+    if (arguments.length == 1)
+        expected = true;
+
+    if (actual === expected)
+        return;
+
+    if (actual !== null && expected !== null
+    &&  typeof actual == 'object' && typeof expected == 'object'
+    &&  actual.toString() === expected.toString())
+        return;
+
+    throw Error("assertion failed: got |" + actual + "|" +
+                ", expected |" + expected + "|" +
+                (message ? " (" + message + ")" : ""));
+}
+
+// load more elaborate version of assert if available
+try { __loadScript("test_assert.js"); } catch(e) {}
+
+/*----------------*/
+
+function pow(a, n)
+{
+    var r, i;
+    r = 1;
+    for(i = 0; i < n; i++)
+        r *= a;
+    return r;
+}
+
+function test_integer()
+{
+    var a, r;
+    a = pow(3, 100);
+    assert((a - 1) != a);
+    assert(a == 515377520732011331036461129765621272702107522001);
+    assert(a == 0x5a4653ca673768565b41f775d6947d55cf3813d1);
+    assert(Integer.isInteger(1) === true);
+    assert(Integer.isInteger(1.0) === false);
+
+    assert(Integer.floorLog2(0) === -1);
+    assert(Integer.floorLog2(7) === 2);
+
+    r = 1 << 31;
+    assert(r, 2147483648, "1 << 31 === 2147483648");
+    
+    r = 1 << 32;
+    assert(r, 4294967296, "1 << 32 === 4294967296");
+    
+    r = (1 << 31) < 0;
+    assert(r, false, "(1 << 31) < 0 === false");
+}
+
+function test_divrem(div1, a, b, q)
+{
+    var div, divrem, t;
+    div = Integer[div1];
+    divrem = Integer[div1 + "rem"];
+    assert(div(a, b) == q);
+    t = divrem(a, b);
+    assert(t[0] == q);
+    assert(a == b * q + t[1]);
+}
+
+function test_idiv1(div, a, b, r)
+{
+    test_divrem(div, a, b, r[0]);
+    test_divrem(div, -a, b, r[1]);
+    test_divrem(div, a, -b, r[2]);
+    test_divrem(div, -a, -b, r[3]);
+}
+
+function test_idiv()
+{
+    test_idiv1("tdiv", 3, 2, [1, -1, -1, 1]);
+    test_idiv1("fdiv", 3, 2, [1, -2, -2, 1]);
+    test_idiv1("cdiv", 3, 2, [2, -1, -1, 2]);
+    test_idiv1("ediv", 3, 2, [1, -2, -1, 2]);
+}
+
+function test_float()
+{
+    var e, a, b, sqrt2;
+    
+    assert(typeof 1 === "bigint");
+    assert(typeof 1.0 === "bigfloat");
+    assert(1 == 1.0);
+    assert(1 !== 1.0);
+    
+    e = new BigFloatEnv(128);
+    assert(e.prec == 128);
+    a = BigFloat.sqrt(2, e);
+    assert(a == BigFloat.parseFloat("0x1.6a09e667f3bcc908b2fb1366ea957d3e", 0, e));
+    assert(e.inexact === true);
+    assert(BigFloat.fpRound(a) == 0x1.6a09e667f3bcd);
+    
+    b = BigFloatEnv.setPrec(BigFloat.sqrt.bind(null, 2), 128);
+    assert(a == b);
+}
+
+/* jscalc tests */
+
+function test_modulo()
+{
+    var i, p, a, b;
+
+    /* Euclidian modulo operator */
+    assert((-3) % 2 == 1);
+    assert(3 % (-2) == 1);
+
+    p = 101;
+    for(i = 1; i < p; i++) {
+        a = Integer.invmod(i, p);
+        assert(a >= 0 && a < p);
+        assert((i * a) % p == 1);
+    }
+
+    assert(Integer.isPrime(2^107-1));
+    assert(!Integer.isPrime((2^107-1) * (2^89-1)));
+    a = Integer.factor((2^89-1)*2^3*11*13^2*1009);
+    assert(a == [ 2,2,2,11,13,13,1009,618970019642690137449562111 ]);
+}
+
+function test_mod()
+{
+    var a, b, p;
+    
+    a = Mod(3, 101);
+    b = Mod(-1, 101);
+    assert((a + b) == Mod(2, 101));
+    assert(a ^ 100 == Mod(1, 101));
+
+    p = 2 ^ 607 - 1; /* mersenne prime */
+    a = Mod(3, p) ^ (p - 1);
+    assert(a == Mod(1, p));
+}
+
+function test_polynomial()
+{
+    var a, b, q, r, t, i;
+    a = (1 + X) ^ 4;
+    assert(a == X^4+4*X^3+6*X^2+4*X+1);
+    
+    r = (1 + X);
+    q = (1+X+X^2);
+    b = (1 - X^2);
+    a = q * b + r;
+    t = Polynomial.divrem(a, b);
+    assert(t[0] == q);
+    assert(t[1] == r);
+
+    a = 1 + 2*X + 3*X^2;
+    assert(a.apply(0.1) == 1.23);
+
+    a = 1-2*X^2+2*X^3;
+    assert(deriv(a) == (6*X^2-4*X));
+    assert(deriv(integ(a)) == a);
+
+    a = (X-1)*(X-2)*(X-3)*(X-4)*(X-0.1);
+    r = polroots(a);
+    for(i = 0; i < r.length; i++) {
+        b = abs(a.apply(r[i]));
+        assert(b <= 1e-13);
+    }
+}
+
+function test_poly_mod()
+{
+    var a, p;
+
+    /* modulo using polynomials */
+    p = X^2 + X + 1;
+    a = PolyMod(3+X, p) ^ 10;
+    assert(a == PolyMod(-3725*X-18357, p));
+
+    a = PolyMod(1/X, 1+X^2);
+    assert(a == PolyMod(-X, X^2+1));
+}
+
+function test_rfunc()
+{
+    var a;
+    a = (X+1)/((X+1)*(X-1));
+    assert(a == 1/(X-1));
+    a = (X + 2) / (X - 2);
+    assert(a.apply(1/3) == -7/5);
+
+    assert(deriv((X^2-X+1)/(X-1)) == (X^2-2*X)/(X^2-2*X+1));
+}
+
+function test_series()
+{
+    var a, b;
+    a = 1+X+O(X^5);
+    b = a.inverse();
+    assert(b == 1-X+X^2-X^3+X^4+O(X^5));
+    assert(deriv(b) == -1+2*X-3*X^2+4*X^3+O(X^4));
+    assert(deriv(integ(b)) == b);
+
+    a = Series(1/(1-X), 5);
+    assert(a == 1+X+X^2+X^3+X^4+O(X^5));
+    b = a.apply(0.1);
+    assert(b == 1.1111);
+
+    assert(exp(3*X^2+O(X^10)) == 1+3*X^2+9/2*X^4+9/2*X^6+27/8*X^8+O(X^10));
+    assert(sin(X+O(X^6)) == X-1/6*X^3+1/120*X^5+O(X^6));
+    assert(cos(X+O(X^6)) == 1-1/2*X^2+1/24*X^4+O(X^6));
+    assert(tan(X+O(X^8)) == X+1/3*X^3+2/15*X^5+17/315*X^7+O(X^8));
+    assert((1+X+O(X^6))^(2+X) == 1+2*X+2*X^2+3/2*X^3+5/6*X^4+5/12*X^5+O(X^6));
+}
+
+function test_matrix()
+{
+    var a, b, r;
+    a = [[1, 2],[3, 4]];
+    b = [3, 4];
+    r = a * b;
+    assert(r == [11, 25]);
+    r = (a^-1) * 2;
+    assert(r == [[-4, 2],[3, -1]]);
+
+    assert(norm2([1,2,3]) == 14);
+
+    assert(diag([1,2,3]) == [ [ 1, 0, 0 ], [ 0, 2, 0 ], [ 0, 0, 3 ] ]);
+    assert(trans(a) == [ [ 1, 3 ], [ 2, 4 ] ]);
+    assert(trans([1,2,3]) == [[1,2,3]]);
+    assert(trace(a) == 5);
+
+    assert(charpoly(Matrix.hilbert(4)) == X^4-176/105*X^3+3341/12600*X^2-41/23625*X+1/6048000);
+    assert(det(Matrix.hilbert(4)) == 1/6048000);
+
+    a = [[1,2,1],[-2,-3,1],[3,5,0]];
+    assert(rank(a) == 2);
+    assert(ker(a) == [ [ 5 ], [ -3 ], [ 1 ] ]);
+
+    assert(dp([1, 2, 3], [3, -4, -7]) === -26);
+    assert(cp([1, 2, 3], [3, -4, -7]) == [ -2, 16, -10 ]);
+}
+
+function assert_eq(a, ref)
+{
+    assert(abs(a / ref - 1.0) <= 1e-15);
+}
+
+function test_trig()
+{
+    assert_eq(sin(1/2), 0.479425538604203);
+    assert_eq(sin(2+3*I), 9.154499146911428-4.168906959966565*I);
+    assert_eq(cos(2+3*I), -4.189625690968807-9.109227893755337*I);
+    assert_eq((2+0.5*I)^(1.1-0.5*I), 2.494363021357619-0.23076804554558092*I);
+    assert_eq(sqrt(2*I), 1 + I);
+}
+
+test_integer();
+test_idiv();
+test_float();
+
+test_modulo();
+test_mod();
+test_polynomial();
+test_poly_mod();
+test_rfunc();
+test_series();
+test_matrix();
+test_trig();
diff --git a/tests/test_bjson.js b/tests/test_bjson.js
new file mode 100644
index 0000000..bd37120
--- /dev/null
+++ b/tests/test_bjson.js
@@ -0,0 +1,119 @@
+import * as bjson from "../bjson.so";
+
+function assert(b, str)
+{
+    if (b) {
+        return;
+    } else {
+        throw Error("assertion failed: " + str);
+    }
+}
+
+function toHex(a)
+{
+    var i, s = "", tab, v;
+    tab = new Uint8Array(a);
+    for(i = 0; i < tab.length; i++) {
+        v = tab[i].toString(16);
+        if (v.length < 2)
+            v = "0" + v;
+        if (i !== 0)
+            s += " ";
+        s += v;
+    }
+    return s;
+}
+
+function toStr(a)
+{
+    var s, i, props, prop;
+
+    switch(typeof(a)) {
+    case "object":
+        if (a === null)
+            return "null";
+        if (Array.isArray(a)) {
+            s = "[";
+            for(i = 0; i < a.length; i++) {
+                if (i != 0)
+                    s += ",";
+                s += toStr(a[i]);
+            }
+            s += "]";
+        } else {
+            props = Object.keys(a);
+            s = "{";
+            for(i = 0; i < props.length; i++) {
+                if (i != 0)
+                    s += ",";
+                prop = props[i];
+                s += prop + ":" + toStr(a[prop]);
+            }
+            s += "}";
+        }
+        return s;
+    case "undefined":
+        return "undefined";
+    case "string":
+        return a.__quote();
+    case "number":
+    case "bigfloat":
+        if (a == 0 && 1 / a < 0)
+            return "-0";
+        else
+            return a.toString();
+        break;
+    default:
+        return a.toString();
+    }
+}
+
+function bjson_test(a)
+{
+    var buf, r, a_str, r_str;
+    a_str = toStr(a);
+    buf = bjson.write(a);
+    if (0) {
+        print(a_str, "->", toHex(buf));
+    }
+    r = bjson.read(buf, 0, buf.byteLength);
+    r_str = toStr(r);
+    if (a_str != r_str) {
+        print(a_str);
+        print(r_str);
+        assert(false);
+    }
+}
+
+function bjson_test_all()
+{
+    var obj;
+    
+    bjson_test({x:1, y:2, if:3});
+    bjson_test([1, 2, 3]);
+    bjson_test([1.0, "aa", true, false, undefined, null, NaN, -Infinity, -0.0]);
+    if (typeof BigInt !== "undefined") {
+        bjson_test([BigInt("1"), -BigInt("0x123456789"),
+               BigInt("0x123456789abcdef123456789abcdef")]);
+    }
+    if (typeof BigFloat !== "undefined") {
+        BigFloatEnv.setPrec(function () {
+            bjson_test([BigFloat("0.1"), BigFloat("-1e30"), BigFloat("0"),
+                   BigFloat("-0"), BigFloat("Infinity"), BigFloat("-Infinity"),
+                   0.0 / BigFloat("0"), BigFloat.MAX_VALUE,
+                   BigFloat.MIN_VALUE]);
+        }, 113, 15);
+    }
+
+    /* tested with a circular reference */
+    obj = {};
+    obj.x = obj;
+    try {
+        bjson.write(obj);
+        assert(false);
+    } catch(e) {
+        assert(e instanceof TypeError);
+    }
+}
+
+bjson_test_all();
diff --git a/tests/test_builtin.js b/tests/test_builtin.js
new file mode 100644
index 0000000..2b444df
--- /dev/null
+++ b/tests/test_builtin.js
@@ -0,0 +1,644 @@
+"use strict";
+
+function assert(actual, expected, message) {
+    if (arguments.length == 1)
+        expected = true;
+
+    if (actual === expected)
+        return;
+
+    if (actual !== null && expected !== null
+    &&  typeof actual == 'object' && typeof expected == 'object'
+    &&  actual.toString() === expected.toString())
+        return;
+
+    throw Error("assertion failed: got |" + actual + "|" +
+                ", expected |" + expected + "|" +
+                (message ? " (" + message + ")" : ""));
+}
+
+// load more elaborate version of assert if available
+try { __loadScript("test_assert.js"); } catch(e) {}
+
+/*----------------*/
+
+function my_func(a, b)
+{
+    return a + b;
+}
+
+function test_function()
+{
+    function f(a, b) {
+        var i, tab = [];
+        tab.push(this);
+        for(i = 0; i < arguments.length; i++)
+            tab.push(arguments[i]);
+        return tab;
+    }
+    function constructor1(a) {
+        this.x = a;
+    }
+    
+    var r, g;
+    
+    r = my_func.call(null, 1, 2);
+    assert(r, 3, "call");
+
+    r = my_func.apply(null, [1, 2]);
+    assert(r, 3, "apply");
+
+    r = new Function("a", "b", "return a + b;");
+    assert(r(2,3), 5, "function");
+    
+    g = f.bind(1, 2);
+    assert(g.length, 1);
+    assert(g.name, "bound f");
+    assert(g(3), [1,2,3]);
+
+    g = constructor1.bind(null, 1);
+    r = new g();
+    assert(r.x, 1);
+}
+
+function test()
+{
+    var r, a, b, c, err;
+
+    r = Error("hello");
+    assert(r.message, "hello", "Error");
+
+    a = new Object();
+    a.x = 1;
+    assert(a.x, 1, "Object");
+
+    assert(Object.getPrototypeOf(a), Object.prototype, "getPrototypeOf");
+    Object.defineProperty(a, "y", { value: 3, writable: true, configurable: true, enumerable: true });
+    assert(a.y, 3, "defineProperty");
+
+    Object.defineProperty(a, "z", { get: function () { return 4; }, set: function(val) { this.z_val = val; }, configurable: true, enumerable: true });
+    assert(a.z, 4, "get");
+    a.z = 5;
+    assert(a.z_val, 5, "set");
+    
+    a = { get z() { return 4; }, set z(val) { this.z_val = val; } };
+    assert(a.z, 4, "get");
+    a.z = 5;
+    assert(a.z_val, 5, "set");
+
+    b = Object.create(a);
+    assert(Object.getPrototypeOf(b), a, "create");
+    c = {u:2};
+    /* XXX: refcount bug in 'b' instead of 'a' */
+    Object.setPrototypeOf(a, c);
+    assert(Object.getPrototypeOf(a), c, "setPrototypeOf");
+
+    a = {};
+    assert(a.toString(), "[object Object]", "toString");
+
+    a = {x:1};
+    assert(Object.isExtensible(a), true, "extensible");
+    Object.preventExtensions(a);
+
+    err = false;
+    try {
+        a.y = 2;
+    } catch(e) {
+        err = true;
+    }
+    assert(Object.isExtensible(a), false, "extensible");
+    assert(typeof a.y, "undefined", "extensible");
+    assert(err, true, "extensible");
+}
+
+function test_enum()
+{
+    var a, tab;
+    a = {x:1,
+         "18014398509481984": 1,
+         "9007199254740992": 1,
+         "9007199254740991": 1,
+         "4294967296": 1,
+         "4294967295": 1,
+         y:1,
+         "4294967294": 1,
+         "1": 2};
+    tab = Object.keys(a);
+//    console.log("tab=" + tab.toString());
+    assert(tab, ["1","4294967294","x","18014398509481984","9007199254740992","9007199254740991","4294967296","4294967295","y"], "keys");
+}
+
+function test_array()
+{
+    var a, err;
+
+    a = [1, 2, 3];
+    assert(a.length, 3, "array");
+    assert(a[2], 3, "array1");
+
+    a = new Array(10);
+    assert(a.length, 10, "array2");
+
+    a = new Array(1, 2);
+    assert(a.length === 2 && a[0] === 1 && a[1] === 2, true, "array3");
+
+    a = [1, 2, 3];
+    a.length = 2;
+    assert(a.length === 2 && a[0] === 1 && a[1] === 2, true, "array4");
+
+    a = [];
+    a[1] = 10;
+    a[4] = 3;
+    assert(a.length, 5);
+
+    a = [1,2];
+    a.length = 5;
+    a[4] = 1;
+    a.length = 4;
+    assert(a[4] !== 1, true, "array5");
+
+    a = [1,2];
+    a.push(3,4);
+    assert(a.join(), "1,2,3,4", "join");
+
+    a = [1,2,3,4,5];
+    Object.defineProperty(a, "3", { configurable: false });
+    err = false;
+    try {
+        a.length = 2;
+    } catch(e) {
+        err = true;
+    }
+    assert(err && a.toString() === "1,2,3,4");
+}
+
+function test_string()
+{
+    var a;
+    a = String("abc");
+    assert(a.length, 3, "string");
+    assert(a[1], "b", "string");
+    assert(a.charCodeAt(1), 0x62, "string");
+    assert(String.fromCharCode(65), "A", "string");
+    assert(String.fromCharCode.apply(null, [65, 66, 67]), "ABC", "string");
+    assert(a.charAt(1), "b");
+    assert(a.charAt(-1), "");
+    assert(a.charAt(3), "");
+    
+    a = "abcd";
+    assert(a.substring(1, 3), "bc", "substring");
+    a = String.fromCharCode(0x20ac);
+    assert(a.charCodeAt(0), 0x20ac, "unicode");
+    assert(a, "€", "unicode");
+    assert(a, "\u20ac", "unicode");
+    assert(a, "\u{20ac}", "unicode");
+    assert("a", "\x61", "unicode");
+        
+    a = "\u{10ffff}";
+    assert(a.length, 2, "unicode");
+    assert(a, "\u{dbff}\u{dfff}", "unicode");
+    assert(a.codePointAt(0), 0x10ffff);
+    assert(String.fromCodePoint(0x10ffff), a);
+
+    assert("a".concat("b", "c"), "abc");
+
+    assert("abcabc".indexOf("cab"), 2);
+    assert("abcabc".indexOf("cab2"), -1);
+    assert("abc".indexOf("c"), 2);
+
+    assert("aaa".indexOf("a"), 0);
+    assert("aaa".indexOf("a", NaN), 0);
+    assert("aaa".indexOf("a", -Infinity), 0);
+    assert("aaa".indexOf("a", -1), 0);
+    assert("aaa".indexOf("a", -0), 0);
+    assert("aaa".indexOf("a", 0), 0);
+    assert("aaa".indexOf("a", 1), 1);
+    assert("aaa".indexOf("a", 2), 2);
+    assert("aaa".indexOf("a", 3), -1);
+    assert("aaa".indexOf("a", 4), -1);
+    assert("aaa".indexOf("a", Infinity), -1);
+
+    assert("aaa".indexOf(""), 0);
+    assert("aaa".indexOf("", NaN), 0);
+    assert("aaa".indexOf("", -Infinity), 0);
+    assert("aaa".indexOf("", -1), 0);
+    assert("aaa".indexOf("", -0), 0);
+    assert("aaa".indexOf("", 0), 0);
+    assert("aaa".indexOf("", 1), 1);
+    assert("aaa".indexOf("", 2), 2);
+    assert("aaa".indexOf("", 3), 3);
+    assert("aaa".indexOf("", 4), 3);
+    assert("aaa".indexOf("", Infinity), 3);
+
+    assert("aaa".lastIndexOf("a"), 2);
+    assert("aaa".lastIndexOf("a", NaN), 2);
+    assert("aaa".lastIndexOf("a", -Infinity), 0);
+    assert("aaa".lastIndexOf("a", -1), 0);
+    assert("aaa".lastIndexOf("a", -0), 0);
+    assert("aaa".lastIndexOf("a", 0), 0);
+    assert("aaa".lastIndexOf("a", 1), 1);
+    assert("aaa".lastIndexOf("a", 2), 2);
+    assert("aaa".lastIndexOf("a", 3), 2);
+    assert("aaa".lastIndexOf("a", 4), 2);
+    assert("aaa".lastIndexOf("a", Infinity), 2);
+
+    assert("aaa".lastIndexOf(""), 3);
+    assert("aaa".lastIndexOf("", NaN), 3);
+    assert("aaa".lastIndexOf("", -Infinity), 0);
+    assert("aaa".lastIndexOf("", -1), 0);
+    assert("aaa".lastIndexOf("", -0), 0);
+    assert("aaa".lastIndexOf("", 0), 0);
+    assert("aaa".lastIndexOf("", 1), 1);
+    assert("aaa".lastIndexOf("", 2), 2);
+    assert("aaa".lastIndexOf("", 3), 3);
+    assert("aaa".lastIndexOf("", 4), 3);
+    assert("aaa".lastIndexOf("", Infinity), 3);
+
+    assert("a,b,c".split(","), ["a","b","c"]);
+    assert(",b,c".split(","), ["","b","c"]);
+    assert("a,b,".split(","), ["a","b",""]);
+
+    assert("aaaa".split(), [ "aaaa" ]);
+    assert("aaaa".split(undefined, 0), [ ]);
+    assert("aaaa".split(""), [ "a", "a", "a", "a" ]);
+    assert("aaaa".split("", 0), [ ]);
+    assert("aaaa".split("", 1), [ "a" ]);
+    assert("aaaa".split("", 2), [ "a", "a" ]);
+    assert("aaaa".split("a"), [ "", "", "", "", "" ]);
+    assert("aaaa".split("a", 2), [ "", "" ]);
+    assert("aaaa".split("aa"), [ "", "", "" ]);
+    assert("aaaa".split("aa", 0), [ ]);
+    assert("aaaa".split("aa", 1), [ "" ]);
+    assert("aaaa".split("aa", 2), [ "", "" ]);
+    assert("aaaa".split("aaa"), [ "", "a" ]);
+    assert("aaaa".split("aaaa"), [ "", "" ]);
+    assert("aaaa".split("aaaaa"), [ "aaaa" ]);
+    assert("aaaa".split("aaaaa", 0), [  ]);
+    assert("aaaa".split("aaaaa", 1), [ "aaaa" ]);
+
+    assert(eval('"\0"'), "\0");
+}
+
+function test_math()
+{
+    var a;
+    a = 1.4;
+    assert(Math.floor(a), 1);
+    assert(Math.ceil(a), 2);
+    assert(Math.imul(0x12345678, 123), -1088058456);
+    assert(Math.fround(0.1), 0.10000000149011612);
+}
+
+function test_number()
+{
+    assert(parseInt("123"), 123);
+    assert(parseInt("  123r"), 123);
+    assert(parseInt("0x123"), 0x123);
+    assert(parseInt("0o123"), 0);
+    assert(+"  123   ", 123);
+    assert(+"0b111", 7);
+    assert(+"0o123", 83);
+    assert(parseFloat("0x1234"), 0);
+    assert(parseFloat("Infinity"), Infinity);
+    assert(parseFloat("-Infinity"), -Infinity);
+    assert(parseFloat("123.2"), 123.2);
+    assert(parseFloat("123.2e3"), 123200);
+
+    assert((25).toExponential(0), "3e+1");
+    assert((-25).toExponential(0), "-3e+1");
+    assert((2.5).toPrecision(1), "3");
+    assert((-2.5).toPrecision(1), "-3");
+    assert((1.125).toFixed(2), "1.13");
+    assert((-1.125).toFixed(2), "-1.13");
+}
+
+function test_eval2()
+{
+    var g_call_count = 0;
+    /* force non strict mode for f1 and f2 */
+    var f1 = new Function("eval", "eval(1, 2)");
+    var f2 = new Function("eval", "eval(...[1, 2])");
+    function g(a, b) {
+        assert(a, 1);
+        assert(b, 2);
+        g_call_count++;
+    }
+    f1(g);
+    f2(g);
+    assert(g_call_count, 2);
+}
+
+function test_eval()
+{
+    function f(b) {
+        var x = 1;
+        return eval(b);
+    }
+    var r, a;
+
+    r = eval("1+1;");
+    assert(r, 2, "eval");
+
+    r = eval("var my_var=2; my_var;");
+    assert(r, 2, "eval");
+    assert(typeof my_var, "undefined");
+
+    assert(eval("if (1) 2; else 3;"), 2);
+    assert(eval("if (0) 2; else 3;"), 3);
+
+    assert(f.call(1, "this"), 1);
+    
+    a = 2;
+    assert(eval("a"), 2);
+
+    eval("a = 3");
+    assert(a, 3);
+
+    assert(f("arguments.length", 1), 2);
+    assert(f("arguments[1]", 1), 1);
+
+    a = 4;
+    assert(f("a"), 4);
+    f("a=3");
+    assert(a, 3);
+
+    test_eval2();
+}
+
+function test_typed_array()
+{
+    var buffer, a, i;
+
+    a = new Uint8Array(4);
+    assert(a.length, 4);
+    for(i = 0; i < a.length; i++)
+        a[i] = i;
+    assert(a.join(","), "0,1,2,3");
+    a[0] = -1;
+    assert(a[0], 255);
+
+    a = new Int8Array(3);
+    a[0] = 255;
+    assert(a[0], -1);
+
+    a = new Int32Array(3);
+    a[0] = Math.pow(2, 32) - 1;
+    assert(a[0], -1);
+    assert(a.BYTES_PER_ELEMENT, 4);
+
+    a = new Uint8ClampedArray(4);
+    a[0] = -100;
+    a[1] = 1.5;
+    a[2] = 0.5;
+    a[3] = 1233.5;
+    assert(a.toString(), "0,2,0,255");
+    
+    buffer = new ArrayBuffer(16);
+    assert(buffer.byteLength, 16);
+    a = new Uint32Array(buffer, 12, 1);
+    assert(a.length, 1);
+    a[0] = -1;
+
+    a = new Uint16Array(buffer, 2);
+    a[0] = -1;
+
+    a = new Float32Array(buffer, 8, 1);
+    a[0] = 1;
+    
+    a = new Uint8Array(buffer);
+    
+    assert(a.toString(), "0,0,255,255,0,0,0,0,0,0,128,63,255,255,255,255");
+
+    assert(a.buffer, buffer);
+
+    a = new Uint8Array([1, 2, 3, 4]);
+    assert(a.toString(), "1,2,3,4");
+    a.set([10, 11], 2);
+    assert(a.toString(), "1,2,10,11");
+}
+
+function test_json()
+{
+    var a, s;
+    s = '{"x":1,"y":true,"z":null,"a":[1,2,3],"s":"str"}';
+    a = JSON.parse(s);
+    assert(a.x, 1);
+    assert(a.y, true);
+    assert(a.z, null);
+    assert(JSON.stringify(a), s);
+
+    /* indentation test */
+    assert(JSON.stringify([[{x:1,y:{},z:[]},2,3]],undefined,1),
+`[
+ [
+  {
+   "x": 1,
+   "y": {},
+   "z": []
+  },
+  2,
+  3
+ ]
+]`);
+}
+
+function test_date()
+{
+    var d = new Date(1506098258091), a, s;
+    assert(d.toISOString(), "2017-09-22T16:37:38.091Z");
+    d.setUTCHours(18, 10, 11);
+    assert(d.toISOString(), "2017-09-22T18:10:11.091Z");
+    a = Date.parse(d.toISOString());
+    assert((new Date(a)).toISOString(), d.toISOString());
+    s = new Date("2020-01-01T01:01:01.1Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:01.100Z");
+    s = new Date("2020-01-01T01:01:01.12Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:01.120Z");
+    s = new Date("2020-01-01T01:01:01.123Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:01.123Z");
+    s = new Date("2020-01-01T01:01:01.1234Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:01.123Z");
+    s = new Date("2020-01-01T01:01:01.12345Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:01.123Z");
+    s = new Date("2020-01-01T01:01:01.1235Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:01.124Z");
+    s = new Date("2020-01-01T01:01:01.9999Z").toISOString();
+    assert(s ==  "2020-01-01T01:01:02.000Z");
+}
+
+function test_regexp()
+{
+    var a, str;
+    str = "abbbbbc";
+    a = /(b+)c/.exec(str);
+    assert(a[0], "bbbbbc");
+    assert(a[1], "bbbbb");
+    assert(a.index, 1);
+    assert(a.input, str);
+    a = /(b+)c/.test(str);
+    assert(a, true);
+    assert(/\x61/.exec("a")[0], "a");
+    assert(/\u0061/.exec("a")[0], "a");
+    assert(/\ca/.exec("\x01")[0], "\x01");
+    assert(/\\a/.exec("\\a")[0], "\\a");
+    assert(/\c0/.exec("\\c0")[0], "\\c0");
+
+    a = /(\.(?=com|org)|\/)/.exec("ah.com");
+    assert(a.index === 2 && a[0] === ".");
+
+    a = /(\.(?!com|org)|\/)/.exec("ah.com");
+    assert(a, null);
+    
+    a = /(?=(a+))/.exec("baaabac");
+    assert(a.index === 1 && a[0] === "" && a[1] === "aaa");
+
+    a = /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac");
+    assert(a, ["zaacbbbcac","z","ac","a",,"c"]);
+
+    a = eval("/\0a/");
+    assert(a.toString(), "/\0a/");
+    assert(a.exec("\0a")[0], "\0a");
+}
+
+function test_symbol()
+{
+    var a, b, obj, c;
+    a = Symbol("abc");
+    obj = {};
+    obj[a] = 2;
+    assert(obj[a], 2);
+    assert(typeof obj["abc"], "undefined");
+    assert(String(a), "Symbol(abc)");
+    b = Symbol("abc");
+    assert(a == a);
+    assert(a === a);
+    assert(a != b);
+    assert(a !== b);
+
+    b = Symbol.for("abc");
+    c = Symbol.for("abc");
+    assert(b === c);
+    assert(b !== a);
+
+    assert(Symbol.keyFor(b), "abc");
+    assert(Symbol.keyFor(a), undefined);
+
+    a = Symbol("aaa");
+    assert(a.valueOf(), a);
+    assert(a.toString(), "Symbol(aaa)");
+
+    b = Object(a);
+    assert(b.valueOf(), a);
+    assert(b.toString(), "Symbol(aaa)");
+}
+
+function test_map()
+{
+    var a, i, n, tab, o, v;
+    n = 1000;
+    a = new Map();
+    tab = [];
+    for(i = 0; i < n; i++) {
+        v = { };
+        o = { id: i };
+        tab[i] = [o, v];
+        a.set(o, v);
+    }
+
+    assert(a.size, n);
+    for(i = 0; i < n; i++) {
+        assert(a.get(tab[i][0]), tab[i][1]);
+    }
+
+    i = 0;
+    a.forEach(function (v, o) { 
+        assert(o, tab[i++][0]);
+        assert(a.has(o));
+        assert(a.delete(o));
+        assert(!a.has(o));
+    });
+
+    assert(a.size, 0);
+}
+
+function test_weak_map()
+{
+    var a, i, n, tab, o, v, n2;
+    a = new WeakMap();
+    n = 10;
+    tab = [];
+    for(i = 0; i < n; i++) {
+        v = { };
+        o = { id: i };
+        tab[i] = [o, v];
+        a.set(o, v);
+    }
+    o = null;
+    
+    n2 = n >> 1;
+    for(i = 0; i < n2; i++) {
+        a.delete(tab[i][0]);
+    }
+    for(i = n2; i < n; i++) {
+        tab[i][0] = null; /* should remove the object from the WeakMap too */
+    }
+    /* the WeakMap should be empty here */
+}
+
+function test_generator()
+{
+    function *f() {
+        var ret;
+        yield 1;
+        ret = yield 2;
+        assert(ret, "next_arg");
+        return 3;
+    }
+    function *f2() {
+        yield 1;
+        yield 2;
+        return "ret_val";
+    }
+    function *f1() {
+        var ret = yield *f2();
+        assert(ret, "ret_val");
+        return 3;
+    }
+    var g, v;
+    g = f();
+    v = g.next();
+    assert(v.value === 1 && v.done === false);
+    v = g.next();
+    assert(v.value === 2 && v.done === false);
+    v = g.next("next_arg");
+    assert(v.value === 3 && v.done === true);
+    v = g.next();
+    assert(v.value === undefined && v.done === true);
+
+    g = f1();
+    v = g.next();
+    assert(v.value === 1 && v.done === false);
+    v = g.next();
+    assert(v.value === 2 && v.done === false);
+    v = g.next();
+    assert(v.value === 3 && v.done === true);
+    v = g.next();
+    assert(v.value === undefined && v.done === true);
+}
+
+test();
+test_function();
+test_enum();
+test_array();
+test_string();
+test_math();
+test_number();
+test_eval();
+test_typed_array();
+test_json();
+test_date();
+test_regexp();
+test_symbol();
+test_map();
+test_weak_map();
+test_generator();
diff --git a/tests/test_closure.js b/tests/test_closure.js
new file mode 100644
index 0000000..aa1d17e
--- /dev/null
+++ b/tests/test_closure.js
@@ -0,0 +1,221 @@
+function assert(actual, expected, message) {
+    if (arguments.length == 1)
+        expected = true;
+
+    if (actual === expected)
+        return;
+
+    if (actual !== null && expected !== null
+    &&  typeof actual == 'object' && typeof expected == 'object'
+    &&  actual.toString() === expected.toString())
+        return;
+
+    throw Error("assertion failed: got |" + actual + "|" +
+                ", expected |" + expected + "|" +
+                (message ? " (" + message + ")" : ""));
+}
+
+// load more elaborate version of assert if available
+try { __loadScript("test_assert.js"); } catch(e) {}
+
+/*----------------*/
+
+var log_str = "";
+
+function log(str)
+{
+    log_str += str + ",";
+}
+
+function f(a, b, c)
+{
+    var x = 10;
+    log("a="+a);
+    function g(d) {
+        function h() {
+            log("d=" + d);
+            log("x=" + x);
+        }
+        log("b=" + b);
+        log("c=" + c);
+        h();
+    }
+    g(4);
+    return g;
+}
+
+var g1 = f(1, 2, 3);
+g1(5);
+
+assert(log_str, "a=1,b=2,c=3,d=4,x=10,b=2,c=3,d=5,x=10,", "closure1");
+
+function test_closure1()
+{
+    function f2()
+    {
+        var val = 1;
+        
+        function set(a) {
+            val = a;
+        }
+        function get(a) {
+            return val;
+        }
+        return { "set": set, "get": get };
+    }
+    
+    var obj = f2();
+    obj.set(10);
+    var r;
+    r = obj.get();
+    assert(r, 10, "closure2");
+}
+
+function test_closure2()
+{
+    var expr_func = function myfunc1(n) {
+        function myfunc2(n) {
+            return myfunc1(n - 1);
+        }
+        if (n == 0)
+            return 0;
+        else
+            return myfunc2(n);
+    };
+    var r;
+    r = expr_func(1);
+    assert(r, 0, "expr_func");
+}
+
+function test_closure3()
+{
+    function fib(n)
+    {
+        if (n <= 0)
+            return 0;
+        else if (n == 1)
+            return 1;
+        else
+            return fib(n - 1) + fib(n - 2);
+    }
+
+    var fib_func = function fib1(n)
+    {
+        if (n <= 0)
+            return 0;
+        else if (n == 1)
+            return 1;
+        else
+            return fib1(n - 1) + fib1(n - 2);
+    };
+
+    assert(fib(6), 8, "fib");
+    assert(fib_func(6), 8, "fib_func");
+}
+
+function test_arrow_function()
+{
+    "use strict";
+
+    function f1() {
+        return (() => arguments)();
+    }
+    function f2() {
+        return (() => this)();
+    }
+    function f3() {
+        return (() => eval("this"))();
+    }
+    function f4() {
+        return (() => eval("new.target"))();
+    }
+    var a;
+
+    a = f1(1, 2);
+    assert(a.length, 2);
+    assert(a[0] === 1 && a[1] === 2);
+
+    assert(f2.call("this_val") === "this_val");
+    assert(f3.call("this_val") === "this_val");
+    assert(new f4() === f4);
+
+    var o1 = { f() { return this; } };
+    var o2 = { f() {
+        return (() => eval("super.f()"))();
+    } };
+    o2.__proto__ = o1;
+
+    assert(o2.f() === o2);
+}
+
+function test_with()
+{
+    var o1 = { x: "o1", y: "o1" };
+    var x = "local";
+    eval('var z="var_obj";');
+    assert(z === "var_obj");
+    with (o1) {
+        assert(x === "o1");
+        assert(eval("x") === "o1");
+        var f = function () {
+            o2 = { x: "o2" };
+            with (o2) {
+                assert(x === "o2");
+                assert(y === "o1");
+                assert(z === "var_obj");
+                assert(eval("x") === "o2");
+                assert(eval("y") === "o1");
+                assert(eval("z") === "var_obj");
+                assert(eval('eval("x")') === "o2");
+            }
+        };
+        f();
+    }
+}
+
+function test_eval_closure()
+{
+    var tab;
+
+    tab = [];
+    for(let i = 0; i < 3; i++) {
+        eval("tab.push(function g1() { return i; })");
+    }
+    for(let i = 0; i < 3; i++) {
+        assert(tab[i]() === i);
+    }
+
+    tab = [];
+    for(let i = 0; i < 3; i++) {
+        let f = function f() {
+            eval("tab.push(function g2() { return i; })");
+        };
+        f();
+    }
+    for(let i = 0; i < 3; i++) {
+        assert(tab[i]() === i);
+    }
+}
+
+function test_eval_const()
+{
+    const a = 1;
+    var success = false;
+    var f = function () {
+        eval("a = 1");
+    };
+    try {
+        f();
+    } catch(e) {
+        success = (e instanceof TypeError);
+    }
+    assert(success);
+}
+
+test_closure1();
+test_closure2();
+test_closure3();
+test_arrow_function();
+test_with();
+test_eval_closure();
+test_eval_const();
diff --git a/tests/test_loop.js b/tests/test_loop.js
new file mode 100644
index 0000000..5fda9d8
--- /dev/null
+++ b/tests/test_loop.js
@@ -0,0 +1,368 @@
+function assert(actual, expected, message) {
+    if (arguments.length == 1)
+        expected = true;
+
+    if (actual === expected)
+        return;
+
+    if (actual !== null && expected !== null
+    &&  typeof actual == 'object' && typeof expected == 'object'
+    &&  actual.toString() === expected.toString())
+        return;
+
+    throw Error("assertion failed: got |" + actual + "|" +
+                ", expected |" + expected + "|" +
+                (message ? " (" + message + ")" : ""));
+}
+
+// load more elaborate version of assert if available
+try { __loadScript("test_assert.js"); } catch(e) {}
+
+/*----------------*/
+
+function test_while()
+{
+    var i, c;
+    i = 0;
+    c = 0;
+    while (i < 3) {
+        c++;
+        i++;
+    }
+    assert(c === 3);
+}
+
+function test_while_break()
+{
+    var i, c;
+    i = 0;
+    c = 0;
+    while (i < 3) {
+        c++;
+        if (i == 1)
+            break;
+        i++;
+    }
+    assert(c === 2 && i === 1);
+}
+
+function test_do_while()
+{
+    var i, c;
+    i = 0;
+    c = 0;
+    do {
+        c++;
+        i++;
+    } while (i < 3);
+    assert(c === 3 && i === 3);
+}
+
+function test_for()
+{
+    var i, c;
+    c = 0;
+    for(i = 0; i < 3; i++) {
+        c++;
+    }
+    assert(c === 3 && i === 3);
+
+    c = 0;
+    for(var j = 0; j < 3; j++) {
+        c++;
+    }
+    assert(c === 3 && j === 3);
+}
+
+function test_for_in()
+{
+    var i, tab, a, b;
+
+    tab = [];
+    for(i in {x:1, y: 2}) {
+        tab.push(i);
+    }
+    assert(tab.toString(), "x,y", "for_in");
+
+    /* prototype chain test */
+    a = {x:2, y: 2, "1": 3};
+    b = {"4" : 3 };
+    Object.setPrototypeOf(a, b);
+    tab = [];
+    for(i in a) {
+        tab.push(i);
+    }
+    assert(tab.toString(), "1,x,y,4", "for_in");
+
+    /* non enumerable properties hide enumerables ones in the
+       prototype chain */
+    a = {y: 2, "1": 3};
+    Object.defineProperty(a, "x", { value: 1 });
+    b = {"x" : 3 };
+    Object.setPrototypeOf(a, b);
+    tab = [];
+    for(i in a) {
+        tab.push(i);
+    }
+    assert(tab.toString(), "1,y", "for_in");
+
+    /* array optimization */
+    a = [];
+    for(i = 0; i < 10; i++)
+        a.push(i);
+    tab = [];
+    for(i in a) {
+        tab.push(i);
+    }
+    assert(tab.toString(), "0,1,2,3,4,5,6,7,8,9", "for_in");
+
+    /* iterate with a field */
+    a={x:0};
+    tab = [];
+    for(a.x in {x:1, y: 2}) {
+        tab.push(a.x);
+    }
+    assert(tab.toString(), "x,y", "for_in");
+
+    /* iterate with a variable field */
+    a=[0];
+    tab = [];
+    for(a[0] in {x:1, y: 2}) {
+        tab.push(a[0]);
+    }
+    assert(tab.toString(), "x,y", "for_in");
+
+    /* variable definition in the for in */
+    tab = [];
+    for(var j in {x:1, y: 2}) {
+        tab.push(j);
+    }
+    assert(tab.toString(), "x,y", "for_in");
+
+    /* variable assigment in the for in */
+    tab = [];
+    for(var k = 2 in {x:1, y: 2}) {
+        tab.push(k);
+    }
+    assert(tab.toString(), "x,y", "for_in");
+}
+
+function test_for_in2()
+{
+    var i;
+    tab = [];
+    for(i in {x:1, y: 2, z:3}) {
+        if (i === "y")
+            continue;
+        tab.push(i);
+    }
+    assert(tab.toString() == "x,z");
+
+    tab = [];
+    for(i in {x:1, y: 2, z:3}) {
+        if (i === "z")
+            break;
+        tab.push(i);
+    }
+    assert(tab.toString() == "x,y");
+}
+
+function test_for_break()
+{
+    var i, c;
+    c = 0;
+    L1: for(i = 0; i < 3; i++) {
+        c++;
+        if (i == 0)
+            continue;
+        while (1) {
+            break L1;
+        }
+    }
+    assert(c === 2 && i === 1);
+}
+
+function test_switch1()
+{
+    var i, a, s;
+    s = "";
+    for(i = 0; i < 3; i++) {
+        a = "?";
+        switch(i) {
+        case 0:
+            a = "a";
+            break;
+        case 1:
+            a = "b";
+            break;
+        default:
+            a = "c";
+            break;
+        }
+        s += a;
+    }
+    assert(s === "abc" && i === 3);
+}
+
+function test_switch2()
+{
+    var i, a, s;
+    s = "";
+    for(i = 0; i < 4; i++) {
+        a = "?";
+        switch(i) {
+        case 0:
+            a = "a";
+            break;
+        case 1:
+            a = "b";
+            break;
+        case 2:
+            continue;
+        default:
+            a = "" + i;
+            break;
+        }
+        s += a;
+    }
+    assert(s === "ab3" && i === 4);
+}
+
+function test_try_catch1()
+{
+    try {
+        throw "hello";
+    } catch (e) {
+        assert(e, "hello", "catch");
+        return;
+    }
+    assert(false, "catch");
+}
+
+function test_try_catch2()
+{
+    var a;
+    try {
+        a = 1;
+    } catch (e) {
+        a = 2;
+    }
+    assert(a, 1, "catch");
+}
+
+function test_try_catch3()
+{
+    var s;
+    s = "";
+    try {
+        s += "t";
+    } catch (e) {
+        s += "c";
+    } finally {
+        s += "f";
+    }
+    assert(s, "tf", "catch");
+}
+
+function test_try_catch4()
+{
+    var s;
+    s = "";
+    try {
+        s += "t";
+        throw "c";
+    } catch (e) {
+        s += e;
+    } finally {
+        s += "f";
+    }
+    assert(s, "tcf", "catch");
+}
+
+function test_try_catch5()
+{
+    var s;
+    s = "";
+    for(;;) {
+        try {
+            s += "t";
+            break;
+            s += "b";
+        } finally {
+            s += "f";
+        }
+    }
+    assert(s, "tf", "catch");
+}
+
+function test_try_catch6()
+{
+    function f() {
+        try {
+            s += 't';
+            return 1;
+        } finally {
+            s += "f";
+        }
+    }
+    var s = "";
+    assert(f() === 1);
+    assert(s, "tf", "catch6");
+}
+
+function test_try_catch7()
+{
+    var s;
+    s = "";
+
+    try {
+        try {
+            s += "t";
+            throw "a";
+        } finally {
+            s += "f";
+        }
+    } catch(e) {
+        s += e;
+    } finally {
+        s += "g";
+    }
+    assert(s, "tfag", "catch");
+}
+
+function test_try_catch8()
+{
+    var i, s;
+    
+    s = "";
+    for(var i in {x:1, y:2}) {
+        try {
+            s += i;
+            throw "a";
+        } catch (e) {
+            s += e;
+        } finally {
+            s += "f";
+        }
+    }
+    assert(s === "xafyaf");
+}
+
+test_while();
+test_while_break();
+test_do_while();
+test_for();
+test_for_break();
+test_switch1();
+test_switch2();
+test_for_in();
+test_for_in2();
+
+test_try_catch1();
+test_try_catch2();
+test_try_catch3();
+test_try_catch4();
+test_try_catch5();
+test_try_catch6();
+test_try_catch7();
+test_try_catch8();
diff --git a/tests/test_op.js b/tests/test_op.js
new file mode 100644
index 0000000..0ce820a
--- /dev/null
+++ b/tests/test_op.js
@@ -0,0 +1,352 @@
+function assert(actual, expected, message) {
+    if (arguments.length == 1)
+        expected = true;
+
+    if (actual === expected)
+        return;
+
+    if (actual !== null && expected !== null
+    &&  typeof actual == 'object' && typeof expected == 'object'
+    &&  actual.toString() === expected.toString())
+        return;
+
+    throw Error("assertion failed: got |" + actual + "|" +
+                ", expected |" + expected + "|" +
+                (message ? " (" + message + ")" : ""));
+}
+
+// load more elaborate version of assert if available
+try { __loadScript("test_assert.js"); } catch(e) {}
+
+/*----------------*/
+
+function test_op1()
+{
+    var r, a;
+    r = 1 + 2;
+    assert(r, 3, "1 + 2 === 3");
+
+    r = 1 - 2;
+    assert(r, -1, "1 - 2 === -1");
+
+    r = -1;
+    assert(r, -1, "-1 === -1");
+
+    r = +2;
+    assert(r, 2, "+2 === 2");
+
+    r = 2 * 3;
+    assert(r, 6, "2 * 3 === 6");
+
+    r = 4 / 2;
+    assert(r, 2, "4 / 2 === 2");
+
+    r = 4 % 3;
+    assert(r, 1, "4 % 3 === 3");
+
+    r = 4 << 2;
+    assert(r, 16, "4 << 2 === 16");
+
+    r = 1 << 0;
+    assert(r, 1, "1 << 0 === 1");
+
+    r = 1 << 31;
+    assert(r, -2147483648, "1 << 31 === -2147483648");
+    
+    r = 1 << 32;
+    assert(r, 1, "1 << 32 === 1");
+    
+    r = (1 << 31) < 0;
+    assert(r, true, "(1 << 31) < 0 === true");
+
+    r = -4 >> 1;
+    assert(r, -2, "-4 >> 1 === -2");
+
+    r = -4 >>> 1;
+    assert(r, 0x7ffffffe, "-4 >>> 1 === 0x7ffffffe");
+
+    r = 1 & 1;
+    assert(r, 1, "1 & 1 === 1");
+
+    r = 0 | 1;
+    assert(r, 1, "0 | 1 === 1");
+
+    r = 1 ^ 1;
+    assert(r, 0, "1 ^ 1 === 0");
+
+    r = ~1;
+    assert(r, -2, "~1 === -2");
+
+    r = !1;
+    assert(r, false, "!1 === false");
+
+    assert((1 < 2), true, "(1 < 2) === true");
+
+    assert((2 > 1), true, "(2 > 1) === true");
+
+    assert(('b' > 'a'), true, "('b' > 'a') === true");
+
+    assert(2 ** 8, 256, "2 ** 8 === 256");
+}
+
+function test_cvt()
+{
+    assert((NaN | 0) === 0);
+    assert((Infinity | 0) === 0);
+    assert(((-Infinity) | 0) === 0);
+    assert(("12345" | 0) === 12345);
+    assert(("0x12345" | 0) === 0x12345);
+    assert(((4294967296 * 3 - 4) | 0) === -4);
+    
+    assert(("12345" >>> 0) === 12345);
+    assert(("0x12345" >>> 0) === 0x12345);
+    assert((NaN >>> 0) === 0);
+    assert((Infinity >>> 0) === 0);
+    assert(((-Infinity) >>> 0) === 0);
+    assert(((4294967296 * 3 - 4) >>> 0) === (4294967296 - 4));
+}
+
+function test_eq()
+{
+    assert(null == undefined);
+    assert(undefined == null);
+    assert(true == 1);
+    assert(0 == false);
+    assert("" == 0);
+    assert("123" == 123);
+    assert("122" != 123);
+    assert((new Number(1)) == 1);
+    assert(2 == (new Number(2)));
+    assert((new String("abc")) == "abc");
+    assert({} != "abc");
+}
+
+function test_inc_dec()
+{
+    var a, r;
+    
+    a = 1;
+    r = a++;
+    assert(r === 1 && a === 2, true, "++");
+
+    a = 1;
+    r = ++a;
+    assert(r === 2 && a === 2, true, "++");
+
+    a = 1;
+    r = a--;
+    assert(r === 1 && a === 0, true, "--");
+
+    a = 1;
+    r = --a;
+    assert(r === 0 && a === 0, true, "--");
+
+    a = {x:true};
+    a.x++;
+    assert(a.x, 2, "++");
+
+    a = {x:true};
+    a.x--;
+    assert(a.x, 0, "--");
+
+    a = [true];
+    a[0]++;
+    assert(a[0], 2, "++");
+    
+    a = {x:true};
+    r = a.x++;
+    assert(r === 1 && a.x === 2, true, "++");
+    
+    a = {x:true};
+    r = a.x--;
+    assert(r === 1 && a.x === 0, true, "--");
+    
+    a = [true];
+    r = a[0]++;
+    assert(r === 1 && a[0] === 2, true, "++");
+    
+    a = [true];
+    r = a[0]--;
+    assert(r === 1 && a[0] === 0, true, "--");
+}
+
+function F(x)
+{
+    this.x = x;
+}
+
+function test_op2()
+{
+    var a, b;
+    a = new Object;
+    a.x = 1;
+    assert(a.x, 1, "new");
+    b = new F(2);
+    assert(b.x, 2, "new");
+
+    a = {x : 2};
+    assert(("x" in a), true, "in");
+    assert(("y" in a), false, "in");
+
+    a = {};
+    assert((a instanceof Object), true, "instanceof");
+    assert((a instanceof String), false, "instanceof");
+
+    assert((typeof 1), "number", "typeof");
+    assert((typeof Object), "function", "typeof");
+    assert((typeof null), "object", "typeof");
+    assert((typeof unknown_var), "undefined", "typeof");
+    
+    a = {x: 1, if: 2, async: 3};
+    assert(a.if === 2);
+    assert(a.async === 3);
+}
+
+function test_delete()
+{
+    var a, err;
+
+    a = {x: 1, y: 1};
+    assert((delete a.x), true, "delete");
+    assert(("x" in a), false, "delete");
+    
+    /* the following are not tested by test262 */
+    assert(delete "abc"[100], true);
+
+    err = false;
+    try {
+        delete null.a;
+    } catch(e) {
+        err = (e instanceof TypeError);
+    }
+    assert(err, true, "delete");
+
+    err = false;
+    try {
+        a = { f() { delete super.a; } };
+        a.f();
+    } catch(e) {
+        err = (e instanceof ReferenceError);
+    }
+    assert(err, true, "delete");
+}
+
+function test_prototype()
+{
+    function f() { }
+    assert(f.prototype.constructor, f, "prototype");
+}
+
+function test_arguments()
+{
+    function f2() {
+        assert(arguments.length, 2, "arguments");
+        assert(arguments[0], 1, "arguments");
+        assert(arguments[1], 3, "arguments");
+    }
+    f2(1, 3);
+}
+
+function test_class()
+{
+    var o;
+    class C {
+        constructor() {
+            this.x = 10;
+        }
+        f() {
+            return 1;
+        }
+        static F() {
+            return -1;
+        }
+        get y() {
+            return 12;
+        }
+    };
+    class D extends C {
+        constructor() {
+            super();
+            this.z = 20;
+        }
+        g() {
+            return 2;
+        }
+        static G() {
+            return -2;
+        }
+        h() {
+            return super.f();
+        }
+        static H() {
+            return super["F"]();
+        }
+    }
+
+    assert(C.F() === -1);
+    assert(Object.getOwnPropertyDescriptor(C.prototype, "y").get.name === "get y");
+
+    o = new C();
+    assert(o.f() === 1);
+    assert(o.x === 10);
+    
+    assert(D.F() === -1);
+    assert(D.G() === -2);
+    assert(D.H() === -1);
+
+    o = new D();
+    assert(o.f() === 1);
+    assert(o.g() === 2);
+    assert(o.x === 10);
+    assert(o.z === 20);
+    assert(o.h() === 1);
+
+    /* test class name scope */
+    var E1 = class E { static F() { return E; } };
+    assert(E1 === E1.F());
+};
+
+function test_template()
+{
+    var a, b;
+    b = 123;
+    a = `abc${b}d`;
+    assert(a === "abc123d");
+
+    a = String.raw `abc${b}d`;
+    assert(a === "abc123d");
+}
+
+function test_object_literal()
+{
+    var x = 0, get = 1, set = 2; async = 3;
+    a = { get: 2, set: 3, async: 4 };
+    assert(JSON.stringify(a), '{"get":2,"set":3,"async":4}');
+
+    a = { x, get, set, async };
+    assert(JSON.stringify(a), '{"x":0,"get":1,"set":2,"async":3}');
+}
+
+function test_regexp_skip()
+{
+    var a, b;
+    [a, b = /abc\(/] = [1];
+    assert(a === 1);
+    
+    [a, b =/abc\(/] = [2];
+    assert(a === 2);
+}
+
+test_op1();
+test_cvt();
+test_eq();
+test_inc_dec();
+test_op2();
+test_delete();
+test_prototype();
+test_arguments();
+test_class();
+test_template();
+test_object_literal();
+test_regexp_skip();
+
diff --git a/tests/test_std.js b/tests/test_std.js
new file mode 100644
index 0000000..b5b1bd0
--- /dev/null
+++ b/tests/test_std.js
@@ -0,0 +1,247 @@
+import * as std from "std";
+import * as os from "os";
+
+function assert(actual, expected, message) {
+    if (arguments.length == 1)
+        expected = true;
+
+    if (actual === expected)
+        return;
+
+    if (actual !== null && expected !== null
+    &&  typeof actual == 'object' && typeof expected == 'object'
+    &&  actual.toString() === expected.toString())
+        return;
+
+    throw Error("assertion failed: got |" + actual + "|" +
+                ", expected |" + expected + "|" +
+                (message ? " (" + message + ")" : ""));
+}
+
+// load more elaborate version of assert if available
+try { std.loadScript("test_assert.js"); } catch(e) {}
+
+/*----------------*/
+
+function test_printf()
+{
+    assert(std.sprintf("a=%d s=%s", 123, "abc"), "a=123 s=abc");
+}
+
+function test_file1()
+{
+    var f, len, str, size, buf, ret, i, str1;
+
+    f = std.tmpfile();
+    str = "hello world\n";
+    f.puts(str);
+
+    f.seek(0, std.SEEK_SET);
+    str1 = f.readAsString();
+    assert(str1 === str);
+    
+    f.seek(0, std.SEEK_END);
+    size = f.tell();
+    assert(size === str.length);
+
+    f.seek(0, std.SEEK_SET);
+
+    buf = new Uint8Array(size);
+    ret = f.read(buf.buffer, 0, size);
+    assert(ret === size);
+    for(i = 0; i < size; i++)
+        assert(buf[i] === str.charCodeAt(i));
+
+    f.close();
+}
+
+function test_file2()
+{
+    var f, str, i, size;
+    f = std.tmpfile();
+    str = "hello world\n";
+    size = str.length;
+    for(i = 0; i < size; i++)
+        f.putByte(str.charCodeAt(i));
+    f.seek(0, std.SEEK_SET);
+    for(i = 0; i < size; i++) {
+        assert(str.charCodeAt(i) === f.getByte());
+    }
+    assert(f.getByte() === -1);
+    f.close();
+}
+
+function test_getline()
+{
+    var f, line, line_count, lines, i;
+    
+    lines = ["hello world", "line 1", "line 2" ];
+    f = std.tmpfile();
+    for(i = 0; i < lines.length; i++) {
+        f.puts(lines[i], "\n");
+    }
+
+    f.seek(0, std.SEEK_SET);
+    assert(!f.eof());
+    line_count = 0;
+    for(;;) {
+        line = f.getline();
+        if (line === null)
+            break;
+        assert(line == lines[line_count]);
+        line_count++;
+    }
+    assert(f.eof());
+    assert(line_count === lines.length);
+
+    f.close();
+}
+ 
+function test_popen()
+{
+    var str, f, fname = "tmp_file.txt";
+    var content = "hello world";
+
+    f = std.open(fname, "w");
+    f.puts(content);
+    f.close();
+
+    /* execute the 'cat' shell command */
+    f = std.popen("cat " + fname, "r");
+    str = f.readAsString();
+    f.close();
+
+    assert(str, content);
+
+    os.remove(fname);
+}
+
+function test_os()
+{
+    var fd, fpath, fname, fdir, buf, buf2, i, files, err, fdate, st, link_path;
+
+    assert(os.isatty(0));
+
+    fdir = "test_tmp_dir";
+    fname = "tmp_file.txt";
+    fpath = fdir + "/" + fname;
+    link_path = fdir + "/test_link";
+    
+    os.remove(link_path);
+    os.remove(fpath);
+    os.remove(fdir);
+
+    err = os.mkdir(fdir, 0o755);
+    assert(err === 0);
+    
+    fd = os.open(fpath, os.O_RDWR | os.O_CREAT | os.O_TRUNC);
+    assert(fd >= 0);
+    
+    buf = new Uint8Array(10);
+    for(i = 0; i < buf.length; i++)
+        buf[i] = i;
+    assert(os.write(fd, buf.buffer, 0, buf.length) === buf.length);
+
+    assert(os.seek(fd, 0, os.SEEK_SET) === 0);
+    buf2 = new Uint8Array(buf.length);
+    assert(os.read(fd, buf2.buffer, 0, buf2.length) === buf2.length);
+    
+    for(i = 0; i < buf.length; i++)
+        assert(buf[i] == buf2[i]);
+    
+    assert(os.close(fd) === 0);
+
+    [files, err] = os.readdir(fdir);
+    assert(err, 0);
+    assert(files.indexOf(fname) >= 0);
+
+    fdate = 10000;
+
+    err = os.utimes(fpath, fdate, fdate);
+    assert(err, 0);
+    
+    [st, err] = os.stat(fpath);
+    assert(err, 0);
+    assert(st.mode & os.S_IFMT, os.S_IFREG);
+    assert(st.mtime, fdate);
+
+    err = os.symlink(fname, link_path);
+    assert(err === 0);
+    
+    [st, err] = os.lstat(link_path);
+    assert(err, 0);
+    assert(st.mode & os.S_IFMT, os.S_IFLNK);
+
+    [buf, err] = os.readlink(link_path);
+    assert(err, 0);
+    assert(buf, fname);
+    
+    assert(os.remove(link_path) === 0);
+
+    [buf, err] = os.getcwd();
+    assert(err, 0);
+
+    [buf2, err] = os.realpath(".");
+    assert(err, 0);
+
+    assert(buf, buf2);
+    
+    assert(os.remove(fpath) === 0);
+
+    fd = os.open(fpath, os.O_RDONLY);
+    assert(fd < 0);
+
+    assert(os.remove(fdir) === 0);
+}
+
+function test_os_exec()
+{
+    var ret, fds, pid, f, status;
+
+    ret = os.exec(["true"]);
+    assert(ret, 0);
+
+    ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false });
+    assert(ret, 1);
+    
+    fds = os.pipe();
+    pid = os.exec(["echo", "hello"], { stdout: fds[1], block: false } );
+    assert(pid >= 0);
+    os.close(fds[1]); /* close the write end (as it is only in the child)  */
+    f = std.fdopen(fds[0], "r");
+    assert(f.getline(), "hello");
+    assert(f.getline(), null);
+    f.close();
+    [ret, status] = os.waitpid(pid, 0);
+    assert(ret, pid);
+    assert(status & 0x7f, 0); /* exited */
+    assert(status >> 8, 0); /* exit code */
+
+    pid = os.exec(["cat"], { block: false } );
+    assert(pid >= 0);
+    os.kill(pid, os.SIGQUIT);
+    [ret, status] = os.waitpid(pid, 0);
+    assert(ret, pid);
+    assert(status & 0x7f, os.SIGQUIT);
+}
+
+function test_timer()
+{
+    var th, i;
+
+    /* just test that a timer can be inserted and removed */
+    th = [];
+    for(i = 0; i < 3; i++)
+        th[i] = os.setTimeout(function () { }, 1000);
+    for(i = 0; i < 3; i++)
+        os.clearTimeout(th[i]);
+}
+
+test_printf();
+test_file1();
+test_file2();
+test_getline();
+test_popen();
+test_os();
+test_os_exec();
+test_timer();
diff --git a/unicode_download.sh b/unicode_download.sh
new file mode 100755
index 0000000..a72d9ae
--- /dev/null
+++ b/unicode_download.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+set -e
+
+url="ftp://ftp.unicode.org/Public/12.1.0/ucd"
+emoji_url="ftp://ftp.unicode.org/Public/emoji/12.0/emoji-data.txt"
+
+files="CaseFolding.txt DerivedNormalizationProps.txt PropList.txt \
+SpecialCasing.txt CompositionExclusions.txt ScriptExtensions.txt \
+UnicodeData.txt DerivedCoreProperties.txt NormalizationTest.txt Scripts.txt \
+PropertyValueAliases.txt"
+
+mkdir -p unicode
+
+for f in $files; do
+    g="${url}/${f}"
+    wget $g -O unicode/$f
+done
+    
+wget $emoji_url -O unicode/emoji-data.txt
diff --git a/unicode_gen.c b/unicode_gen.c
new file mode 100644
index 0000000..f99908e
--- /dev/null
+++ b/unicode_gen.c
@@ -0,0 +1,3056 @@
+/*
+ * Generation of Unicode tables
+ * 
+ * Copyright (c) 2017-2018 Fabrice Bellard
+ * Copyright (c) 2017-2018 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "cutils.h"
+
+/* define it to be able to test unicode.c */
+//#define USE_TEST
+/* profile tests */
+//#define PROFILE
+
+//#define DUMP_CASE_CONV_TABLE
+//#define DUMP_TABLE_SIZE
+//#define DUMP_CC_TABLE
+//#define DUMP_DECOMP_TABLE
+
+/* Ideas:
+   - Generalize run length encoding + index for all tables
+   - remove redundant tables for ID_start, ID_continue, Case_Ignorable, Cased
+
+   Case conversion:
+   - use a single entry for consecutive U/LF runs
+   - allow EXT runs of length > 1
+
+   Decomposition:
+   - Greek lower case (+1f10/1f10) ?
+   - allow holes in B runs
+   - suppress more upper / lower case redundancy
+*/
+
+#ifdef USE_TEST
+#include "libunicode.c"
+#endif
+
+#define CHARCODE_MAX 0x10ffff
+#define CC_LEN_MAX 3
+
+void *mallocz(size_t size)
+{
+    void *ptr;
+    ptr = malloc(size);
+    memset(ptr, 0, size);
+    return ptr;
+}
+
+const char *get_field(const char *p, int n)
+{
+    int i;
+    for(i = 0; i < n; i++) {
+        while (*p != ';' && *p != '\0')
+            p++;
+        if (*p == '\0')
+            return NULL;
+        p++;
+    }
+    return p;
+}
+
+const char *get_field_buf(char *buf, size_t buf_size, const char *p, int n)
+{
+    char *q;
+    p = get_field(p, n);
+    q = buf;
+    while (*p != ';' && *p != '\0') {
+        if ((q - buf) < buf_size - 1)
+            *q++ = *p;
+        p++;
+    }
+    *q = '\0';
+    return buf;
+}
+
+void add_char(int **pbuf, int *psize, int *plen, int c)
+{
+    int len, size, *buf;
+    buf = *pbuf;
+    size = *psize;
+    len = *plen;
+    if (len >= size) {
+        size = *psize;
+        size = max_int(len + 1, size * 3 / 2);
+        buf = realloc(buf, sizeof(buf[0]) * size);
+        *pbuf = buf;
+        *psize = size;
+    }
+    buf[len++] = c;
+    *plen = len;
+}
+
+int *get_field_str(int *plen, const char *str, int n)
+{
+    const char *p;
+    int *buf, len, size;
+    p = get_field(str, n);
+    if (!p) {
+        *plen = 0;
+        return NULL;
+    }
+    len = 0;
+    size = 0;
+    buf = NULL;
+    for(;;) {
+        while (isspace(*p))
+            p++;
+        if (!isxdigit(*p))
+            break;
+        add_char(&buf, &size, &len, strtoul(p, (char **)&p, 16));
+    }
+    *plen = len;
+    return buf;
+}
+
+char *get_line(char *buf, int buf_size, FILE *f)
+{
+    int len;
+    if (!fgets(buf, buf_size, f))
+        return NULL;
+    len = strlen(buf);
+    if (len > 0 && buf[len - 1] == '\n')
+        buf[len - 1] = '\0';
+    return buf;
+}
+
+#define UNICODE_GENERAL_CATEGORY
+
+typedef enum {
+#define DEF(id, str) GCAT_ ## id,
+#include "unicode_gen_def.h"
+#undef DEF
+    GCAT_COUNT,
+} UnicodeGCEnum1;
+
+static const char *unicode_gc_name[] = {
+#define DEF(id, str) #id,
+#include "unicode_gen_def.h"
+#undef DEF
+};
+
+static const char *unicode_gc_short_name[] = {
+#define DEF(id, str) str,
+#include "unicode_gen_def.h"
+#undef DEF
+};
+
+#undef UNICODE_GENERAL_CATEGORY
+
+#define UNICODE_SCRIPT
+
+typedef enum {
+#define DEF(id, str) SCRIPT_ ## id,
+#include "unicode_gen_def.h"
+#undef DEF
+    SCRIPT_COUNT,
+} UnicodeScriptEnum1;
+
+static const char *unicode_script_name[] = {
+#define DEF(id, str) #id,
+#include "unicode_gen_def.h"
+#undef DEF
+};
+
+const char *unicode_script_short_name[] = {
+#define DEF(id, str) str,
+#include "unicode_gen_def.h"
+#undef DEF
+};
+
+#undef UNICODE_SCRIPT
+
+#define UNICODE_PROP_LIST
+
+typedef enum {
+#define DEF(id, str) PROP_ ## id,
+#include "unicode_gen_def.h"
+#undef DEF
+    PROP_COUNT,
+} UnicodePropEnum1;
+
+static const char *unicode_prop_name[] = {
+#define DEF(id, str) #id,
+#include "unicode_gen_def.h"
+#undef DEF
+};
+
+static const char *unicode_prop_short_name[] = {
+#define DEF(id, str) str,
+#include "unicode_gen_def.h"
+#undef DEF
+};
+
+#undef UNICODE_SPROP_LIST
+
+typedef struct {
+    /* case conv */
+    uint8_t u_len;
+    uint8_t l_len;
+    int u_data[CC_LEN_MAX];
+    int l_data[CC_LEN_MAX];
+    int f_code;
+
+    uint8_t combining_class;
+    uint8_t is_compat:1;
+    uint8_t is_excluded:1;
+    uint8_t general_category;
+    uint8_t script;
+    uint8_t script_ext_len;
+    uint8_t *script_ext;
+    uint32_t prop_bitmap_tab[3];
+    /* decomposition */
+    int decomp_len;
+    int *decomp_data;
+} CCInfo;
+
+CCInfo *unicode_db;
+
+int find_name(const char **tab, int tab_len, const char *name)
+{
+    int i, len, name_len;
+    const char *p, *r;
+
+    name_len = strlen(name);
+    for(i = 0; i < tab_len; i++) {
+        p = tab[i];
+        for(;;) {
+            r = strchr(p, ',');
+            if (!r)
+                len = strlen(p);
+            else
+                len = r - p;
+            if (len == name_len && memcmp(p, name, len) == 0)
+                return i;
+            if (!r)
+                break;
+            p = r + 1;
+        }
+    }
+    return -1;
+}
+
+static int get_prop(uint32_t c, int prop_idx)
+{
+    return (unicode_db[c].prop_bitmap_tab[prop_idx >> 5] >> (prop_idx & 0x1f)) & 1;
+}
+
+static void set_prop(uint32_t c, int prop_idx, int val)
+{
+    uint32_t mask;
+    mask = 1U << (prop_idx & 0x1f);
+    if (val)
+        unicode_db[c].prop_bitmap_tab[prop_idx >> 5] |= mask;
+    else
+        unicode_db[c].prop_bitmap_tab[prop_idx >> 5]  &= ~mask;
+}
+
+void parse_unicode_data(const char *filename)
+{
+    FILE *f;
+    char line[1024];
+    char buf1[256];
+    const char *p;
+    int code, lc, uc, last_code;
+    CCInfo *ci, *tab = unicode_db;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#')
+            continue;
+
+        p = get_field(line, 0);
+        if (!p)
+            continue;
+        code = strtoul(p, NULL, 16);
+        lc = 0;
+        uc = 0;
+        
+        p = get_field(line, 12);
+        if (p && *p != ';') {
+            uc = strtoul(p, NULL, 16);
+        }
+
+        p = get_field(line, 13);
+        if (p && *p != ';') {
+            lc = strtoul(p, NULL, 16);
+        }
+        ci = &tab[code];
+        if (uc > 0 || lc > 0) {
+            assert(code <= CHARCODE_MAX);
+            if (uc > 0) {
+                assert(ci->u_len == 0);
+                ci->u_len = 1;
+                ci->u_data[0] = uc;
+            }
+            if (lc > 0) {
+                assert(ci->l_len == 0);
+                ci->l_len = 1;
+                ci->l_data[0] = lc;
+            }
+        }
+
+        {
+            int i;
+            get_field_buf(buf1, sizeof(buf1), line, 2);
+            i = find_name(unicode_gc_name, countof(unicode_gc_name), buf1);
+            if (i < 0) {
+                fprintf(stderr, "General category '%s' not found\n",
+                        buf1);
+                exit(1);
+            }
+            ci->general_category = i;
+        }
+        
+        p = get_field(line, 3);
+        if (p && *p != ';' && *p != '\0') {
+            int cc;
+            cc = strtoul(p, NULL, 0);
+            if (cc != 0) {
+                assert(code <= CHARCODE_MAX);
+                ci->combining_class = cc;
+                //                printf("%05x: %d\n", code, ci->combining_class);
+            }
+        }
+
+        p = get_field(line, 5);
+        if (p && *p != ';' && *p != '\0') {
+            int size;
+            assert(code <= CHARCODE_MAX);
+            ci->is_compat = 0;
+            if (*p == '<') {
+                while (*p != '\0' && *p != '>')
+                    p++;
+                if (*p == '>')
+                    p++;
+                ci->is_compat = 1;
+            }
+            size = 0;
+            for(;;) {
+                while (isspace(*p))
+                    p++;
+                if (!isxdigit(*p))
+                    break;
+                add_char(&ci->decomp_data, &size, &ci->decomp_len, strtoul(p, (char **)&p, 16));
+            }
+#if 0
+            {
+                int i;
+                static int count, d_count;
+
+                printf("%05x: %c", code, ci->is_compat ? 'C': ' ');
+                for(i = 0; i < ci->decomp_len; i++)
+                    printf(" %05x", ci->decomp_data[i]);
+                printf("\n");
+                count++;
+                d_count += ci->decomp_len;
+                //                printf("%d %d\n", count, d_count);
+            }
+#endif
+        }
+
+        p = get_field(line, 9);
+        if (p && *p == 'Y') {
+            set_prop(code, PROP_Bidi_Mirrored, 1);
+        }
+        
+        /* handle ranges */
+        get_field_buf(buf1, sizeof(buf1), line, 1);
+        if (strstr(buf1, " Last>")) {
+            int i;
+            //            printf("range: 0x%x-%0x\n", last_code, code);
+            assert(ci->decomp_len == 0);
+            assert(ci->script_ext_len == 0);
+            for(i = last_code + 1; i < code; i++) {
+                unicode_db[i] = *ci;
+            }
+        }
+        last_code = code;
+    }
+        
+    fclose(f);
+}
+
+void parse_special_casing(CCInfo *tab, const char *filename)
+{
+    FILE *f;
+    char line[1024];
+    const char *p;
+    int code;
+    CCInfo *ci;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#')
+            continue;
+
+        p = get_field(line, 0);
+        if (!p)
+            continue;
+        code = strtoul(p, NULL, 16);
+        assert(code <= CHARCODE_MAX);
+        ci = &tab[code];
+
+        p = get_field(line, 4);
+        if (p) {
+            /* locale dependent casing */
+            while (isspace(*p))
+                p++;
+            if (*p != '#' && *p != '\0')
+                continue;
+        }
+            
+        
+        p = get_field(line, 1);
+        if (p && *p != ';') {
+            ci->l_len = 0;
+            for(;;) {
+                while (isspace(*p))
+                    p++;
+                if (*p == ';')
+                    break;
+                assert(ci->l_len < CC_LEN_MAX);
+                ci->l_data[ci->l_len++] = strtoul(p, (char **)&p, 16);
+            }
+
+            if (ci->l_len == 1 && ci->l_data[0] == code)
+                ci->l_len = 0;
+        }
+
+        p = get_field(line, 3);
+        if (p && *p != ';') {
+            ci->u_len = 0;
+            for(;;) {
+                while (isspace(*p))
+                    p++;
+                if (*p == ';')
+                    break;
+                assert(ci->u_len < CC_LEN_MAX);
+                ci->u_data[ci->u_len++] = strtoul(p, (char **)&p, 16);
+            }
+
+            if (ci->u_len == 1 && ci->u_data[0] == code)
+                ci->u_len = 0;
+        }
+    }
+        
+    fclose(f);
+}
+
+void parse_case_folding(CCInfo *tab, const char *filename)
+{
+    FILE *f;
+    char line[1024];
+    const char *p;
+    int code;
+    CCInfo *ci;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#')
+            continue;
+
+        p = get_field(line, 0);
+        if (!p)
+            continue;
+        code = strtoul(p, NULL, 16);
+        assert(code <= CHARCODE_MAX);
+        ci = &tab[code];
+
+        p = get_field(line, 1);
+        if (!p)
+            continue;
+        /* locale dependent casing */
+        while (isspace(*p))
+            p++;
+        if (*p != 'C' && *p != 'S')
+            continue;
+        
+        p = get_field(line, 2);
+        assert(p != 0);
+        assert(ci->f_code == 0);
+        ci->f_code = strtoul(p, NULL, 16);
+        assert(ci->f_code != 0 && ci->f_code != code);
+    }
+        
+    fclose(f);
+}
+
+void parse_composition_exclusions(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p;
+    uint32_t c0;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@' || *p == '\0')
+            continue;
+        c0 = strtoul(p, (char **)&p, 16);
+        assert(c0 > 0 && c0 <= CHARCODE_MAX);
+        unicode_db[c0].is_excluded = TRUE;
+    }
+    fclose(f);
+}
+
+void parse_derived_core_properties(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p, buf[256], *q;
+    uint32_t c0, c1, c;
+    int i;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@' || *p == '\0')
+            continue;
+        c0 = strtoul(p, (char **)&p, 16);
+        if (*p == '.' && p[1] == '.') {
+            p += 2;
+            c1 = strtoul(p, (char **)&p, 16);
+        } else {
+            c1 = c0;
+        }
+        assert(c1 <= CHARCODE_MAX);
+        p += strspn(p, " \t");
+        if (*p == ';') {
+            p++;
+            p += strspn(p, " \t");
+            q = buf;
+            while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') {
+                if ((q - buf) < sizeof(buf) - 1)
+                    *q++ = *p;
+                p++;
+            }
+            *q = '\0';
+            i = find_name(unicode_prop_name,
+                          countof(unicode_prop_name), buf);
+            if (i < 0) {
+                if (!strcmp(buf, "Grapheme_Link"))
+                    goto next;
+                fprintf(stderr, "Property not found: %s\n", buf);
+                exit(1);
+            }
+            for(c = c0; c <= c1; c++) {
+                set_prop(c, i, 1);
+            }
+next: ;
+        }
+    }
+    fclose(f);
+}
+
+void parse_derived_norm_properties(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p, buf[256], *q;
+    uint32_t c0, c1, c;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@' || *p == '\0')
+            continue;
+        c0 = strtoul(p, (char **)&p, 16);
+        if (*p == '.' && p[1] == '.') {
+            p += 2;
+            c1 = strtoul(p, (char **)&p, 16);
+        } else {
+            c1 = c0;
+        }
+        assert(c1 <= CHARCODE_MAX);
+        p += strspn(p, " \t");
+        if (*p == ';') {
+            p++;
+            p += strspn(p, " \t");
+            q = buf;
+            while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') {
+                if ((q - buf) < sizeof(buf) - 1)
+                    *q++ = *p;
+                p++;
+            }
+            *q = '\0';
+            if (!strcmp(buf, "Changes_When_NFKC_Casefolded")) {
+                for(c = c0; c <= c1; c++) {
+                    set_prop(c, PROP_Changes_When_NFKC_Casefolded, 1);
+                }
+            }
+        }
+    }
+    fclose(f);
+}
+
+void parse_prop_list(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p, buf[256], *q;
+    uint32_t c0, c1, c;
+    int i;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@' || *p == '\0')
+            continue;
+        c0 = strtoul(p, (char **)&p, 16);
+        if (*p == '.' && p[1] == '.') {
+            p += 2;
+            c1 = strtoul(p, (char **)&p, 16);
+        } else {
+            c1 = c0;
+        }
+        assert(c1 <= CHARCODE_MAX);
+        p += strspn(p, " \t");
+        if (*p == ';') {
+            p++;
+            p += strspn(p, " \t");
+            q = buf;
+            while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') {
+                if ((q - buf) < sizeof(buf) - 1)
+                    *q++ = *p;
+                p++;
+            }
+            *q = '\0';
+            i = find_name(unicode_prop_name,
+                          countof(unicode_prop_name), buf);
+            if (i < 0) {
+                fprintf(stderr, "Property not found: %s\n", buf);
+                exit(1);
+            }
+            for(c = c0; c <= c1; c++) {
+                set_prop(c, i, 1);
+            }
+        }
+    }
+    fclose(f);
+}
+
+void parse_scripts(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p, buf[256], *q;
+    uint32_t c0, c1, c;
+    int i;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@' || *p == '\0')
+            continue;
+        c0 = strtoul(p, (char **)&p, 16);
+        if (*p == '.' && p[1] == '.') {
+            p += 2;
+            c1 = strtoul(p, (char **)&p, 16);
+        } else {
+            c1 = c0;
+        }
+        assert(c1 <= CHARCODE_MAX);
+        p += strspn(p, " \t");
+        if (*p == ';') {
+            p++;
+            p += strspn(p, " \t");
+            q = buf;
+            while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') {
+                if ((q - buf) < sizeof(buf) - 1)
+                    *q++ = *p;
+                p++;
+            }
+            *q = '\0';
+            i = find_name(unicode_script_name,
+                          countof(unicode_script_name), buf);
+            if (i < 0) {
+                fprintf(stderr, "Unknown script: '%s'\n", buf);
+                exit(1);
+            }
+            for(c = c0; c <= c1; c++)
+                unicode_db[c].script = i;
+        }
+    }
+    fclose(f);
+}
+
+void parse_script_extensions(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p, buf[256], *q;
+    uint32_t c0, c1, c;
+    int i;
+    uint8_t script_ext[255];
+    int script_ext_len;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@' || *p == '\0')
+            continue;
+        c0 = strtoul(p, (char **)&p, 16);
+        if (*p == '.' && p[1] == '.') {
+            p += 2;
+            c1 = strtoul(p, (char **)&p, 16);
+        } else {
+            c1 = c0;
+        }
+        assert(c1 <= CHARCODE_MAX);
+        p += strspn(p, " \t");
+        script_ext_len = 0;
+        if (*p == ';') {
+            p++;
+            for(;;) {
+                p += strspn(p, " \t");
+                q = buf;
+                while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') {
+                    if ((q - buf) < sizeof(buf) - 1)
+                        *q++ = *p;
+                    p++;
+                }
+                *q = '\0';
+                if (buf[0] == '\0')
+                    break;
+                i = find_name(unicode_script_short_name,
+                              countof(unicode_script_short_name), buf);
+                if (i < 0) {
+                    fprintf(stderr, "Script not found: %s\n", buf);
+                    exit(1);
+                }
+                assert(script_ext_len < sizeof(script_ext));
+                script_ext[script_ext_len++] = i;
+            }
+            for(c = c0; c <= c1; c++) {
+                CCInfo *ci = &unicode_db[c];
+                ci->script_ext_len = script_ext_len;
+                ci->script_ext = malloc(sizeof(ci->script_ext[0]) * script_ext_len);
+                for(i = 0; i < script_ext_len; i++)
+                    ci->script_ext[i] = script_ext[i];
+            }
+        }
+    }
+    fclose(f);
+}
+
+void dump_cc_info(CCInfo *ci, int i)
+{
+    int j;
+    printf("%05x:", i);
+    if (ci->u_len != 0) {
+        printf(" U:");
+        for(j = 0; j < ci->u_len; j++)
+            printf(" %05x", ci->u_data[j]);
+    }
+    if (ci->l_len != 0) {
+        printf(" L:");
+        for(j = 0; j < ci->l_len; j++)
+            printf(" %05x", ci->l_data[j]);
+    }
+    if (ci->f_code != 0) {
+        printf(" F: %05x", ci->f_code);
+    }
+    printf("\n");
+}
+
+void dump_data(CCInfo *tab)
+{
+    int i;
+    CCInfo *ci;
+    for(i = 0; i <= CHARCODE_MAX; i++) {
+        ci = &tab[i];
+        if (ci->u_len != 0 || ci->l_len != 0 || ci->f_code != 0) {
+            dump_cc_info(ci, i);
+        }
+    }
+}
+
+BOOL is_complicated_case(const CCInfo *ci)
+{
+    return (ci->u_len > 1 || ci->l_len > 1 ||
+            (ci->u_len > 0 && ci->l_len > 0) ||
+            (ci->f_code != 0) != ci->l_len ||
+            (ci->f_code != 0 && ci->l_data[0] != ci->f_code));
+}
+
+#ifndef USE_TEST
+enum {
+    RUN_TYPE_U,
+    RUN_TYPE_L,
+    RUN_TYPE_UF,
+    RUN_TYPE_LF,
+    RUN_TYPE_UL,
+    RUN_TYPE_LSU,
+    RUN_TYPE_U2L_399_EXT2,
+    RUN_TYPE_UF_D20,
+    RUN_TYPE_UF_D1_EXT,
+    RUN_TYPE_U_EXT,
+    RUN_TYPE_LF_EXT,
+    RUN_TYPE_U_EXT2,
+    RUN_TYPE_L_EXT2,
+    RUN_TYPE_U_EXT3,
+};
+#endif
+
+const char *run_type_str[] = {
+    "U",
+    "L",
+    "UF",
+    "LF",
+    "UL",
+    "LSU",
+    "U2L_399_EXT2",
+    "UF_D20",
+    "UF_D1_EXT",
+    "U_EXT",
+    "LF_EXT",
+    "U_EXT2",
+    "L_EXT2",
+    "U_EXT3",
+};
+
+typedef struct {
+    int code;
+    int len;
+    int type;
+    int data;
+    int ext_len;
+    int ext_data[3];
+    int data_index; /* 'data' coming from the table */
+} TableEntry;
+
+/* code (17), len (7), type (4) */
+
+void find_run_type(TableEntry *te, CCInfo *tab, int code)
+{
+    int is_lower, len;
+    CCInfo *ci, *ci1, *ci2;
+
+    ci = &tab[code];
+    ci1 = &tab[code + 1];
+    ci2 = &tab[code + 2];
+    te->code = code;
+    
+    if (ci->l_len == 1 && ci->l_data[0] == code + 2 &&
+        ci->f_code == ci->l_data[0] &&
+        ci->u_len == 0 &&
+
+        ci1->l_len == 1 && ci1->l_data[0] == code + 2 &&
+        ci1->f_code == ci1->l_data[0] &&
+        ci1->u_len == 1 && ci1->u_data[0] == code &&
+
+        ci2->l_len == 0 &&
+        ci2->f_code == 0 &&
+        ci2->u_len == 1 && ci2->u_data[0] == code) {
+        te->len = 3;
+        te->data = 0;
+        te->type = RUN_TYPE_LSU;
+        return;
+    }
+
+    if (is_complicated_case(ci)) {
+        len = 1;
+        while (code + len <= CHARCODE_MAX) {
+            ci1 = &tab[code + len];
+            if (ci1->u_len != 1 ||
+                ci1->u_data[0] != ci->u_data[0] + len ||
+                ci1->l_len != 0 ||
+                ci1->f_code != ci1->u_data[0])
+                break;
+            len++;
+        }
+        if (len > 1) {
+            te->len = len;
+            te->type = RUN_TYPE_UF;
+            te->data = ci->u_data[0];
+            return;
+        }
+
+        if (ci->u_len == 2 && ci->u_data[1] == 0x399 &&
+            ci->f_code == 0 && ci->l_len == 0) {
+            len = 1;
+            while (code + len <= CHARCODE_MAX) {
+                ci1 = &tab[code + len];
+                if (!(ci1->u_len == 2 &&
+                    ci1->u_data[1] == 0x399 &&
+                      ci1->u_data[0] == ci->u_data[0] + len &&
+                      ci1->f_code == 0 &&
+                      ci1->l_len == 0))
+                    break;
+                len++;
+            }
+            te->len = len;
+            te->type = RUN_TYPE_U_EXT2;
+            te->ext_data[0] = ci->u_data[0];
+            te->ext_data[1] = ci->u_data[1];
+            te->ext_len = 2;
+            return;
+        }
+
+        if (ci->u_len == 2 && ci->u_data[1] == 0x399 &&
+            ci->l_len == 1 && ci->f_code == ci->l_data[0]) {
+            len = 1;
+            while (code + len <= CHARCODE_MAX) {
+                ci1 = &tab[code + len];
+                if (!(ci1->u_len == 2 &&
+                      ci1->u_data[1] == 0x399 &&
+                      ci1->u_data[0] == ci->u_data[0] + len &&
+                      ci1->l_len == 1 &&
+                      ci1->l_data[0] == ci->l_data[0] + len &&
+                      ci1->f_code == ci1->l_data[0]))
+                    break;
+                len++;
+            }
+            te->len = len;
+            te->type = RUN_TYPE_U2L_399_EXT2;
+            te->ext_data[0] = ci->u_data[0];
+            te->ext_data[1] = ci->l_data[0];
+            te->ext_len = 2;
+            return;
+        }
+
+        if (ci->l_len == 1 && ci->u_len == 0 && ci->f_code == 0) {
+            len = 1;
+            while (code + len <= CHARCODE_MAX) {
+                ci1 = &tab[code + len];
+                if (!(ci1->l_len == 1 &&
+                      ci1->l_data[0] == ci->l_data[0] + len &&
+                      ci1->u_len == 0 && ci1->f_code == 0))
+                    break;
+                len++;
+            }
+            te->len = len;
+            te->type = RUN_TYPE_L;
+            te->data = ci->l_data[0];
+            return;
+        }
+
+        if (ci->l_len == 0 &&
+            ci->u_len == 1 &&
+            ci->u_data[0] < 0x1000 &&
+            ci->f_code == ci->u_data[0] + 0x20) {
+            te->len = 1;
+            te->type = RUN_TYPE_UF_D20;
+            te->data = ci->u_data[0];
+        } else if (ci->l_len == 0 &&
+            ci->u_len == 1 &&
+            ci->f_code == ci->u_data[0] + 1) {
+            te->len = 1;
+            te->type = RUN_TYPE_UF_D1_EXT;
+            te->ext_data[0] = ci->u_data[0];
+            te->ext_len = 1;
+        } else if (ci->l_len == 2 && ci->u_len == 0 && ci->f_code == 0) {
+            te->len = 1;
+            te->type = RUN_TYPE_L_EXT2;
+            te->ext_data[0] = ci->l_data[0];
+            te->ext_data[1] = ci->l_data[1];
+            te->ext_len = 2;
+        } else if (ci->u_len == 2 && ci->l_len == 0 && ci->f_code == 0) {
+            te->len = 1;
+            te->type = RUN_TYPE_U_EXT2;
+            te->ext_data[0] = ci->u_data[0];
+            te->ext_data[1] = ci->u_data[1];
+            te->ext_len = 2;
+        } else if (ci->u_len == 3 && ci->l_len == 0 && ci->f_code == 0) {
+            te->len = 1;
+            te->type = RUN_TYPE_U_EXT3;
+            te->ext_data[0] = ci->u_data[0];
+            te->ext_data[1] = ci->u_data[1];
+            te->ext_data[2] = ci->u_data[2];
+            te->ext_len = 3;
+        } else {
+            printf("unsupported encoding case:\n");
+            dump_cc_info(ci, code);
+            abort();
+        }
+    } else {
+        /* look for a run of identical conversions */
+        len = 0;
+        for(;;) {
+            if (code >= CHARCODE_MAX || len >= 126)
+                break;
+            ci = &tab[code + len];
+            ci1 = &tab[code + len + 1];
+            if (is_complicated_case(ci) || is_complicated_case(ci1)) {
+                break;
+            }
+            if (ci->l_len != 1 || ci->l_data[0] != code + len + 1)
+                break;
+            if (ci1->u_len != 1 || ci1->u_data[0] != code + len)
+                break;
+            len += 2;
+        }
+        if (len > 0) {
+            te->len = len;
+            te->type = RUN_TYPE_UL;
+            te->data = 0;
+            return;
+        }
+        
+        ci = &tab[code];
+        is_lower = ci->l_len > 0;
+        len = 1;
+        while (code + len <= CHARCODE_MAX) {
+            ci1 = &tab[code + len];
+            if (is_complicated_case(ci1))
+                break;
+            if (is_lower) {
+                if (ci1->l_len != 1 ||
+                    ci1->l_data[0] != ci->l_data[0] + len)
+                    break;
+            } else {
+                if (ci1->u_len != 1 ||
+                    ci1->u_data[0] != ci->u_data[0] + len)
+                    break;
+            }
+            len++;
+        }
+        te->len = len;
+        if (is_lower) {
+            te->type = RUN_TYPE_LF;
+            te->data = ci->l_data[0];
+        } else {
+            te->type = RUN_TYPE_U;
+            te->data = ci->u_data[0];
+        }
+    }
+}
+
+TableEntry conv_table[1000];
+int conv_table_len;
+int ext_data[1000];
+int ext_data_len;
+
+void dump_case_conv_table1(void)
+{
+    int i, j;
+    const TableEntry *te;
+
+    for(i = 0; i < conv_table_len; i++) {
+        te = &conv_table[i];
+        printf("%05x %02x %-10s %05x",
+               te->code, te->len, run_type_str[te->type], te->data);
+        for(j = 0; j < te->ext_len; j++) {
+            printf(" %05x", te->ext_data[j]);
+        }
+        printf("\n");
+    }
+    printf("table_len=%d ext_len=%d\n", conv_table_len, ext_data_len);
+}
+
+int find_data_index(const TableEntry *conv_table, int len, int data)
+{
+    int i;
+    const TableEntry *te;
+    for(i = 0; i < len; i++) {
+        te = &conv_table[i];
+        if (te->code == data)
+            return i;
+    }
+    return -1;
+}
+
+int find_ext_data_index(int data)
+{
+    int i;
+    for(i = 0; i < ext_data_len; i++) {
+        if (ext_data[i] == data)
+            return i;
+    }
+    assert(ext_data_len < countof(ext_data));
+    ext_data[ext_data_len++] = data;
+    return ext_data_len - 1;
+}
+
+void build_conv_table(CCInfo *tab)
+{
+    int code, i, j;
+    CCInfo *ci;
+    TableEntry *te;
+                          
+    te = conv_table;
+    for(code = 0; code <= CHARCODE_MAX; code++) {
+        ci = &tab[code];
+        if (ci->u_len == 0 && ci->l_len == 0 && ci->f_code == 0)
+            continue;
+        assert(te - conv_table < countof(conv_table));
+        find_run_type(te, tab, code);
+#if 0
+        if (te->type == RUN_TYPE_TODO) {
+            printf("TODO: ");
+            dump_cc_info(ci, code);
+        }
+#endif
+        assert(te->len <= 127);
+        code += te->len - 1;
+        te++;
+    }
+    conv_table_len = te - conv_table;
+
+    /* find the data index */
+    for(i = 0; i < conv_table_len; i++) {
+        int data_index;
+        te = &conv_table[i];
+        
+        switch(te->type) {
+        case RUN_TYPE_U:
+        case RUN_TYPE_L:
+        case RUN_TYPE_UF:
+        case RUN_TYPE_LF:
+            data_index = find_data_index(conv_table, conv_table_len, te->data);
+            if (data_index < 0) {
+                switch(te->type) {
+                case RUN_TYPE_U:
+                    te->type = RUN_TYPE_U_EXT;
+                    te->ext_len = 1;
+                    te->ext_data[0] = te->data;
+                    break;
+                case RUN_TYPE_LF:
+                    te->type = RUN_TYPE_LF_EXT;
+                    te->ext_len = 1;
+                    te->ext_data[0] = te->data;
+                    break;
+                default:
+                    printf("%05x: index not found\n", te->code);
+                    exit(1);
+                }
+            } else {
+                te->data_index = data_index;
+            }
+            break;
+        case RUN_TYPE_UF_D20:
+            te->data_index = te->data;
+            break;
+        }
+    }
+
+    /* find the data index for ext_data */
+    for(i = 0; i < conv_table_len; i++) {
+        te = &conv_table[i];
+        if (te->type == RUN_TYPE_U_EXT3) {
+            int p, v;
+            v = 0;
+            for(j = 0; j < 3; j++) {
+                p = find_ext_data_index(te->ext_data[j]);
+                assert(p < 16);
+                v = (v << 4) | p;
+            }
+            te->data_index = v;
+        }
+    }
+
+    for(i = 0; i < conv_table_len; i++) {
+        te = &conv_table[i];
+        if (te->type == RUN_TYPE_L_EXT2 ||
+            te->type == RUN_TYPE_U_EXT2 ||
+            te->type == RUN_TYPE_U2L_399_EXT2) {
+            int p, v;
+            v = 0;
+            for(j = 0; j < 2; j++) {
+                p = find_ext_data_index(te->ext_data[j]);
+                assert(p < 64);
+                v = (v << 6) | p;
+            }
+            te->data_index = v;
+        }
+    }
+
+    for(i = 0; i < conv_table_len; i++) {
+        te = &conv_table[i];
+        if (te->type == RUN_TYPE_UF_D1_EXT ||
+            te->type == RUN_TYPE_U_EXT ||
+            te->type == RUN_TYPE_LF_EXT) {
+            te->data_index = find_ext_data_index(te->ext_data[0]);
+        }
+    }
+#ifdef DUMP_CASE_CONV_TABLE
+    dump_case_conv_table1();
+#endif
+}
+
+void dump_case_conv_table(FILE *f)
+{
+    int i;
+    uint32_t v;
+    const TableEntry *te;
+
+    fprintf(f, "static const uint32_t case_conv_table1[%u] = {", conv_table_len);
+    for(i = 0; i < conv_table_len; i++) {
+        if (i % 4 == 0)
+            fprintf(f, "\n   ");
+        te = &conv_table[i];
+        v = te->code << (32 - 17);
+        v |= te->len << (32 - 17 - 7);
+        v |= te->type << (32 - 17 - 7 - 4);
+        v |= te->data_index >> 8;
+        fprintf(f, " 0x%08x,", v);
+    }
+    fprintf(f, "\n};\n\n");
+
+    fprintf(f, "static const uint8_t case_conv_table2[%u] = {", conv_table_len);
+    for(i = 0; i < conv_table_len; i++) {
+        if (i % 8 == 0)
+            fprintf(f, "\n   ");
+        te = &conv_table[i];
+        fprintf(f, " 0x%02x,", te->data_index & 0xff);
+    }
+    fprintf(f, "\n};\n\n");
+
+    fprintf(f, "static const uint16_t case_conv_ext[%u] = {", ext_data_len);
+    for(i = 0; i < ext_data_len; i++) {
+        if (i % 8 == 0)
+            fprintf(f, "\n   ");
+        fprintf(f, " 0x%04x,", ext_data[i]);
+    }
+    fprintf(f, "\n};\n\n");
+}
+
+int tabcmp(const int *tab1, const int *tab2, int n)
+{
+    int i;
+    for(i = 0; i < n; i++) {
+        if (tab1[i] != tab2[i])
+            return -1;
+    }
+    return 0;
+}
+
+void dump_str(const char *str, const int *buf, int len)
+{
+    int i;
+    printf("%s=", str);
+    for(i = 0; i < len; i++)
+        printf(" %05x", buf[i]);
+    printf("\n");
+}
+
+void compute_internal_props(void)
+{
+    int i;
+    BOOL has_ul;
+
+    for(i = 0; i <= CHARCODE_MAX; i++) {
+        CCInfo *ci = &unicode_db[i];
+        has_ul = (ci->u_len != 0 || ci->l_len != 0 || ci->f_code != 0);
+        if (has_ul) {
+            assert(get_prop(i, PROP_Cased));
+        } else {
+            set_prop(i, PROP_Cased1, get_prop(i, PROP_Cased));
+        }
+        set_prop(i, PROP_ID_Continue1,
+                 get_prop(i, PROP_ID_Continue) & (get_prop(i, PROP_ID_Start) ^ 1));
+        set_prop(i, PROP_XID_Start1,
+                 get_prop(i, PROP_ID_Start) ^ get_prop(i, PROP_XID_Start));
+        set_prop(i, PROP_XID_Continue1,
+                 get_prop(i, PROP_ID_Continue) ^ get_prop(i, PROP_XID_Continue));
+        set_prop(i, PROP_Changes_When_Titlecased1,
+                 get_prop(i, PROP_Changes_When_Titlecased) ^ (ci->u_len != 0));
+        set_prop(i, PROP_Changes_When_Casefolded1,
+                 get_prop(i, PROP_Changes_When_Casefolded) ^ (ci->f_code != 0));
+        /* XXX: reduce table size (438 bytes) */
+        set_prop(i, PROP_Changes_When_NFKC_Casefolded1,
+                 get_prop(i, PROP_Changes_When_NFKC_Casefolded) ^ (ci->f_code != 0));
+#if 0
+        /* TEST */
+#define M(x) (1U << GCAT_ ## x)
+        {
+            int b;
+            b = ((M(Mn) | M(Cf) | M(Lm) | M(Sk)) >>
+                 unicode_db[i].general_category) & 1;
+            set_prop(i, PROP_Cased1,
+                     get_prop(i, PROP_Case_Ignorable) ^ b);
+        }
+#undef M
+#endif
+    }
+}
+
+void dump_byte_table(FILE *f, const char *cname, const uint8_t *tab, int len)
+{
+    int i;
+    fprintf(f, "static const uint8_t %s[%d] = {", cname, len);
+    for(i = 0; i < len; i++) {
+        if (i % 8 == 0)
+            fprintf(f, "\n   ");
+        fprintf(f, " 0x%02x,", tab[i]);
+    }
+    fprintf(f, "\n};\n\n");
+}
+
+#define PROP_BLOCK_LEN 32
+
+void build_prop_table(FILE *f, int prop_index, BOOL add_index)
+{
+    int i, j, n, v, offset, code;
+    DynBuf dbuf_s, *dbuf = &dbuf_s;
+    DynBuf dbuf1_s, *dbuf1 = &dbuf1_s;
+    DynBuf dbuf2_s, *dbuf2 = &dbuf2_s;
+    const uint32_t *buf;
+    int buf_len, block_end_pos, bit;
+    char cname[128];
+    
+    dbuf_init(dbuf1);
+
+    for(i = 0; i <= CHARCODE_MAX;) {
+        v = get_prop(i, prop_index);
+        j = i + 1;
+        while (j <= CHARCODE_MAX && get_prop(j, prop_index) == v) {
+            j++;
+        }
+        n = j - i;
+        if (j == (CHARCODE_MAX + 1) && v == 0)
+            break; /* no need to encode last zero run */
+        //printf("%05x: %d %d\n", i, n, v);
+        dbuf_put_u32(dbuf1, n - 1);
+        i += n;
+    }
+    
+    dbuf_init(dbuf);
+    dbuf_init(dbuf2);
+    buf = (uint32_t *)dbuf1->buf;
+    buf_len = dbuf1->size / sizeof(buf[0]);
+    
+    /* the first value is assumed to be 0 */
+    assert(get_prop(0, prop_index) == 0);
+    
+    block_end_pos = PROP_BLOCK_LEN;
+    i = 0;
+    code = 0;
+    bit = 0;
+    while (i < buf_len) {
+        if (add_index && dbuf->size >= block_end_pos && bit == 0) {
+            offset = (dbuf->size - block_end_pos);
+            /* XXX: offset could be larger in case of runs of small
+               lengths. Could add code to change the encoding to
+               prevent it at the expense of one byte loss */
+            assert(offset <= 7);
+            v = code | (offset << 21);
+            dbuf_putc(dbuf2, v);
+            dbuf_putc(dbuf2, v >> 8);
+            dbuf_putc(dbuf2, v >> 16);
+            block_end_pos += PROP_BLOCK_LEN;
+        }
+
+        v = buf[i];
+        code += v + 1;
+        bit ^= 1;
+        if (v < 8 && (i + 1) < buf_len && buf[i + 1] < 8) {
+            code += buf[i + 1] + 1;
+            bit ^= 1;
+            dbuf_putc(dbuf, (v << 3) | buf[i + 1]);
+            i += 2;
+        } else if (v < 128) {
+            dbuf_putc(dbuf, 0x80 + v);
+            i++;
+        } else if (v < (1 << 13)) {
+            dbuf_putc(dbuf, 0x40 + (v >> 8));
+            dbuf_putc(dbuf, v);
+            i++;
+        } else {
+            assert(v < (1 << 21));
+            dbuf_putc(dbuf, 0x60 + (v >> 16));
+            dbuf_putc(dbuf, v >> 8);
+            dbuf_putc(dbuf, v);
+            i++;
+        }
+    }
+
+    if (add_index) {
+        /* last index entry */
+        v = code;
+        dbuf_putc(dbuf2, v);
+        dbuf_putc(dbuf2, v >> 8);
+        dbuf_putc(dbuf2, v >> 16);
+    }
+
+#ifdef DUMP_TABLE_SIZE
+    printf("prop %s: length=%d bytes\n", unicode_prop_name[prop_index],
+           (int)(dbuf->size + dbuf2->size));
+#endif
+    snprintf(cname, sizeof(cname), "unicode_prop_%s_table", unicode_prop_name[prop_index]);
+    dump_byte_table(f, cname, dbuf->buf, dbuf->size);
+    if (add_index) {
+        snprintf(cname, sizeof(cname), "unicode_prop_%s_index", unicode_prop_name[prop_index]);
+        dump_byte_table(f, cname, dbuf2->buf, dbuf2->size);
+    }
+    
+    dbuf_free(dbuf);
+    dbuf_free(dbuf1);
+    dbuf_free(dbuf2);
+}
+
+void build_flags_tables(FILE *f)
+{
+    build_prop_table(f, PROP_Cased1, TRUE);
+    build_prop_table(f, PROP_Case_Ignorable, TRUE);
+    build_prop_table(f, PROP_ID_Start, TRUE);
+    build_prop_table(f, PROP_ID_Continue1, TRUE);
+}
+
+void dump_name_table(FILE *f, const char *cname, const char **tab_name, int len,
+                     const char **tab_short_name)
+{
+    int i, w, maxw;
+
+    maxw = 0;
+    for(i = 0; i < len; i++) {
+        w = strlen(tab_name[i]);
+        if (tab_short_name[i][0] != '\0') {
+            w += 1 + strlen(tab_short_name[i]);
+        }
+        if (maxw < w)
+            maxw = w;
+    }
+
+    /* generate a sequence of strings terminated by an empty string */
+    fprintf(f, "static const char %s[] =\n", cname);
+    for(i = 0; i < len; i++) {
+        fprintf(f, "    \"");
+        w = fprintf(f, "%s", tab_name[i]);
+        if (tab_short_name[i][0] != '\0') {
+            w += fprintf(f, ",%s", tab_short_name[i]);
+        }
+        fprintf(f, "\"%*s\"\\0\"\n", 1 + maxw - w, "");
+    }
+    fprintf(f, ";\n\n");
+}
+
+void build_general_category_table(FILE *f)
+{
+    int i, v, j, n, n1;
+    DynBuf dbuf_s, *dbuf = &dbuf_s;
+    int cw_count, cw_len_count[4], cw_start;
+
+    fprintf(f, "typedef enum {\n");
+    for(i = 0; i < GCAT_COUNT; i++)
+        fprintf(f, "    UNICODE_GC_%s,\n", unicode_gc_name[i]);
+    fprintf(f, "    UNICODE_GC_COUNT,\n");
+    fprintf(f, "} UnicodeGCEnum;\n\n");
+
+    dump_name_table(f, "unicode_gc_name_table",
+                    unicode_gc_name, GCAT_COUNT,
+                    unicode_gc_short_name);
+
+
+    dbuf_init(dbuf);
+    cw_count = 0;
+    for(i = 0; i < 4; i++)
+        cw_len_count[i] = 0;
+    for(i = 0; i <= CHARCODE_MAX;) {
+        v = unicode_db[i].general_category;
+        j = i + 1;
+        while (j <= CHARCODE_MAX && unicode_db[j].general_category == v)
+            j++;
+        n = j - i;
+        /* compress Lu/Ll runs */
+        if (v == GCAT_Lu) {
+            n1 = 1;
+            while ((i + n1) <= CHARCODE_MAX && unicode_db[i + n1].general_category == (v + (n1 & 1))) {
+                n1++;
+            }
+            if (n1 > n) {
+                v = 31;
+                n = n1;
+            }
+        }
+        //        printf("%05x %05x %d\n", i, n, v);
+        cw_count++;
+        n--;
+        cw_start = dbuf->size;
+        if (n < 7) {
+            dbuf_putc(dbuf, (n << 5) | v);
+        } else if (n < 7 + 128) {
+            n1 = n - 7;
+            assert(n1 < 128);
+            dbuf_putc(dbuf, (0xf << 5) | v);
+            dbuf_putc(dbuf, n1);
+        } else if (n < 7 + 128 + (1 << 14)) {
+            n1 = n - (7 + 128);
+            assert(n1 < (1 << 14));
+            dbuf_putc(dbuf, (0xf << 5) | v);
+            dbuf_putc(dbuf, (n1 >> 8) + 128);
+            dbuf_putc(dbuf, n1);
+        } else {
+            n1 = n - (7 + 128 + (1 << 14));
+            assert(n1 < (1 << 22));
+            dbuf_putc(dbuf, (0xf << 5) | v);
+            dbuf_putc(dbuf, (n1 >> 16) + 128 + 64);
+            dbuf_putc(dbuf, n1 >> 8);
+            dbuf_putc(dbuf, n1);
+        }
+        cw_len_count[dbuf->size - cw_start - 1]++;
+        i += n + 1;
+    }
+#ifdef DUMP_TABLE_SIZE
+    printf("general category: %d entries [",
+           cw_count);
+    for(i = 0; i < 4; i++)
+        printf(" %d", cw_len_count[i]);
+    printf(" ], length=%d bytes\n", (int)dbuf->size);
+#endif
+    
+    dump_byte_table(f, "unicode_gc_table", dbuf->buf, dbuf->size);
+
+    dbuf_free(dbuf);
+}
+
+void build_script_table(FILE *f)
+{
+    int i, v, j, n, n1, type;
+    DynBuf dbuf_s, *dbuf = &dbuf_s;
+    int cw_count, cw_len_count[4], cw_start;
+
+    fprintf(f, "typedef enum {\n");
+    for(i = 0; i < SCRIPT_COUNT; i++)
+        fprintf(f, "    UNICODE_SCRIPT_%s,\n", unicode_script_name[i]);
+    fprintf(f, "    UNICODE_SCRIPT_COUNT,\n");
+    fprintf(f, "} UnicodeScriptEnum;\n\n");
+
+    i = 1;
+    dump_name_table(f, "unicode_script_name_table",
+                    unicode_script_name + i, SCRIPT_COUNT - i,
+                    unicode_script_short_name + i);
+
+    dbuf_init(dbuf);
+    cw_count = 0;
+    for(i = 0; i < 4; i++)
+        cw_len_count[i] = 0;
+    for(i = 0; i <= CHARCODE_MAX;) {
+        v = unicode_db[i].script;
+        j = i + 1;
+        while (j <= CHARCODE_MAX && unicode_db[j].script == v)
+            j++;
+        n = j - i;
+        if (v == 0 && j == (CHARCODE_MAX + 1))
+            break;
+        //        printf("%05x %05x %d\n", i, n, v);
+        cw_count++;
+        n--;
+        cw_start = dbuf->size;
+        if (v == 0)
+            type = 0;
+        else
+            type = 1;
+        if (n < 96) {
+            dbuf_putc(dbuf, n | (type << 7));
+        } else if (n < 96 + (1 << 12)) {
+            n1 = n - 96;
+            assert(n1 < (1 << 12));
+            dbuf_putc(dbuf, ((n1 >> 8) + 96) | (type << 7));
+            dbuf_putc(dbuf, n1);
+        } else {
+            n1 = n - (96 + (1 << 12));
+            assert(n1 < (1 << 20));
+            dbuf_putc(dbuf, ((n1 >> 16) + 112) | (type << 7));
+            dbuf_putc(dbuf, n1 >> 8);
+            dbuf_putc(dbuf, n1);
+        }
+        if (type != 0)
+            dbuf_putc(dbuf, v);
+
+        cw_len_count[dbuf->size - cw_start - 1]++;
+        i += n + 1;
+    }
+#if defined(DUMP_TABLE_SIZE)
+    printf("script: %d entries [",
+           cw_count);
+    for(i = 0; i < 4; i++)
+        printf(" %d", cw_len_count[i]);
+    printf(" ], length=%d bytes\n", (int)dbuf->size);
+#endif
+    
+    dump_byte_table(f, "unicode_script_table", dbuf->buf, dbuf->size);
+
+    dbuf_free(dbuf);
+}
+
+void build_script_ext_table(FILE *f)
+{
+    int i, j, n, n1, script_ext_len;
+    DynBuf dbuf_s, *dbuf = &dbuf_s;
+    int cw_count;
+
+    dbuf_init(dbuf);
+    cw_count = 0;
+    for(i = 0; i <= CHARCODE_MAX;) {
+        script_ext_len = unicode_db[i].script_ext_len;
+        j = i + 1;
+        while (j <= CHARCODE_MAX &&
+               unicode_db[j].script_ext_len == script_ext_len &&
+               !memcmp(unicode_db[j].script_ext, unicode_db[i].script_ext,
+                       script_ext_len)) {
+            j++;
+        }
+        n = j - i;
+        cw_count++;
+        n--;
+        if (n < 128) {
+            dbuf_putc(dbuf, n);
+        } else if (n < 128 + (1 << 14)) {
+            n1 = n - 128;
+            assert(n1 < (1 << 14));
+            dbuf_putc(dbuf, (n1 >> 8) + 128);
+            dbuf_putc(dbuf, n1);
+        } else {
+            n1 = n - (128 + (1 << 14));
+            assert(n1 < (1 << 22));
+            dbuf_putc(dbuf, (n1 >> 16) + 128 + 64);
+            dbuf_putc(dbuf, n1 >> 8);
+            dbuf_putc(dbuf, n1);
+        }
+        dbuf_putc(dbuf, script_ext_len);
+        for(j = 0; j < script_ext_len; j++)
+            dbuf_putc(dbuf, unicode_db[i].script_ext[j]);
+        i += n + 1;
+    }
+#ifdef DUMP_TABLE_SIZE
+    printf("script_ext: %d entries",
+           cw_count);
+    printf(", length=%d bytes\n", (int)dbuf->size);
+#endif
+    
+    dump_byte_table(f, "unicode_script_ext_table", dbuf->buf, dbuf->size);
+
+    dbuf_free(dbuf);
+}
+
+/* the following properties are synthetized so no table is necessary */
+#define PROP_TABLE_COUNT PROP_ASCII
+
+void build_prop_list_table(FILE *f)
+{
+    int i;
+    
+    for(i = 0; i < PROP_TABLE_COUNT; i++) {
+        if (i == PROP_ID_Start ||
+            i == PROP_Case_Ignorable ||
+            i == PROP_ID_Continue1) {
+            /* already generated */
+        } else {
+            build_prop_table(f, i, FALSE);
+        }
+    }
+    
+    fprintf(f, "typedef enum {\n");
+    for(i = 0; i < PROP_COUNT; i++)
+        fprintf(f, "    UNICODE_PROP_%s,\n", unicode_prop_name[i]);
+    fprintf(f, "    UNICODE_PROP_COUNT,\n");
+    fprintf(f, "} UnicodePropertyEnum;\n\n");
+
+    i = PROP_ASCII_Hex_Digit;
+    dump_name_table(f, "unicode_prop_name_table",
+                    unicode_prop_name + i, PROP_XID_Start - i + 1,
+                    unicode_prop_short_name + i);
+
+    fprintf(f, "static const uint8_t * const unicode_prop_table[] = {\n");
+    for(i = 0; i < PROP_TABLE_COUNT; i++) {
+        fprintf(f, "    unicode_prop_%s_table,\n", unicode_prop_name[i]);
+    }
+    fprintf(f, "};\n\n");
+
+    fprintf(f, "static const uint16_t unicode_prop_len_table[] = {\n");
+    for(i = 0; i < PROP_TABLE_COUNT; i++) {
+        fprintf(f, "    countof(unicode_prop_%s_table),\n", unicode_prop_name[i]);
+    }
+    fprintf(f, "};\n\n");
+}
+
+#ifdef USE_TEST
+int check_conv(uint32_t *res, uint32_t c, int conv_type)
+{
+    return lre_case_conv(res, c, conv_type);
+}
+
+void check_case_conv(void)
+{
+    CCInfo *tab = unicode_db;
+    uint32_t res[3];
+    int l, error;
+    CCInfo ci_s, *ci1, *ci = &ci_s;
+    int code;
+    
+    for(code = 0; code <= CHARCODE_MAX; code++) {
+        ci1 = &tab[code];
+        *ci = *ci1;
+        if (ci->l_len == 0) {
+            ci->l_len = 1;
+            ci->l_data[0] = code;
+        }
+        if (ci->u_len == 0) {
+            ci->u_len = 1;
+            ci->u_data[0] = code;
+        }
+        if (ci->f_code == 0)
+            ci->f_code = code;
+
+        error = 0;
+        l = check_conv(res, code, 0);
+        if (l != ci->u_len || tabcmp((int *)res, ci->u_data, l)) {
+            printf("ERROR: L\n");
+            error++;
+        }
+        l = check_conv(res, code, 1);
+        if (l != ci->l_len || tabcmp((int *)res, ci->l_data, l)) {
+            printf("ERROR: U\n");
+            error++;
+        }
+        l = check_conv(res, code, 2);
+        if (l != 1 || res[0] != ci->f_code) {
+            printf("ERROR: F\n");
+            error++;
+        }
+        if (error) {
+            dump_cc_info(ci, code);
+            exit(1);
+        }
+    }
+}
+
+#ifdef PROFILE
+static int64_t get_time_ns(void)
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+#endif
+
+
+void check_flags(void)
+{
+    int c;
+    BOOL flag_ref, flag;
+    for(c = 0; c <= CHARCODE_MAX; c++) {
+        flag_ref = get_prop(c, PROP_Cased);
+        flag = lre_is_cased(c);
+        if (flag != flag_ref) {
+            printf("ERROR: c=%05x cased=%d ref=%d\n",
+                   c, flag, flag_ref);
+            exit(1);
+        }
+
+        flag_ref = get_prop(c, PROP_Case_Ignorable);
+        flag = lre_is_case_ignorable(c);
+        if (flag != flag_ref) {
+            printf("ERROR: c=%05x case_ignorable=%d ref=%d\n",
+                   c, flag, flag_ref);
+            exit(1);
+        }
+
+        flag_ref = get_prop(c, PROP_ID_Start);
+        flag = lre_is_id_start(c);
+        if (flag != flag_ref) {
+            printf("ERROR: c=%05x id_start=%d ref=%d\n",
+                   c, flag, flag_ref);
+            exit(1);
+        }
+
+        flag_ref = get_prop(c, PROP_ID_Continue);
+        flag = lre_is_id_continue(c);
+        if (flag != flag_ref) {
+            printf("ERROR: c=%05x id_cont=%d ref=%d\n",
+                   c, flag, flag_ref);
+            exit(1);
+        }
+    }
+#ifdef PROFILE
+    {
+        int64_t ti, count;
+        ti = get_time_ns();
+        count = 0;
+        for(c = 0x20; c <= 0xffff; c++) {
+            flag_ref = get_prop(c, PROP_ID_Start);
+            flag = lre_is_id_start(c);
+            assert(flag == flag_ref);
+            count++;
+        }
+        ti = get_time_ns() - ti;
+        printf("flags time=%0.1f ns/char\n",
+               (double)ti / count);
+    }
+#endif
+}
+
+#endif
+
+#define CC_BLOCK_LEN 32
+
+void build_cc_table(FILE *f)
+{
+    int i, cc, n, cc_table_len, type, n1;
+    DynBuf dbuf_s, *dbuf = &dbuf_s;
+    DynBuf dbuf1_s, *dbuf1 = &dbuf1_s;
+    int cw_len_tab[3], cw_start, block_end_pos;
+    uint32_t v;
+    
+    dbuf_init(dbuf);
+    dbuf_init(dbuf1);
+    cc_table_len = 0;
+    for(i = 0; i < countof(cw_len_tab); i++)
+        cw_len_tab[i] = 0;
+    block_end_pos = CC_BLOCK_LEN;
+    for(i = 0; i <= CHARCODE_MAX;) {
+        cc = unicode_db[i].combining_class;
+        assert(cc <= 255);
+        /* check increasing values */
+        n = 1;
+        while ((i + n) <= CHARCODE_MAX &&
+               unicode_db[i + n].combining_class == (cc + n))
+            n++;
+        if (n >= 2) {
+            type = 1;
+        } else {
+            type = 0;
+            n = 1;
+            while ((i + n) <= CHARCODE_MAX &&
+                   unicode_db[i + n].combining_class == cc)
+                n++;
+        }
+        /* no need to encode the last run */
+        if (cc == 0 && (i + n - 1) == CHARCODE_MAX)
+            break;
+#ifdef DUMP_CC_TABLE
+        printf("%05x %6d %d %d\n", i, n, type, cc);
+#endif
+        if (type == 0) {
+            if (cc == 0)
+                type = 2;
+            else if (cc == 230)
+                type = 3;
+        }
+        n1 = n - 1;
+
+        /* add an entry to the index if necessary */
+        if (dbuf->size >= block_end_pos) {
+            v = i | ((dbuf->size - block_end_pos) << 21);
+            dbuf_putc(dbuf1, v);
+            dbuf_putc(dbuf1, v >> 8);
+            dbuf_putc(dbuf1, v >> 16);
+            block_end_pos += CC_BLOCK_LEN;
+        }
+        cw_start = dbuf->size;
+        if (n1 < 48) {
+            dbuf_putc(dbuf, n1 | (type << 6));
+        } else if (n1 < 48 + (1 << 11)) {
+            n1 -= 48;
+            dbuf_putc(dbuf, ((n1 >> 8) + 48) | (type << 6));
+            dbuf_putc(dbuf, n1);
+        } else {
+            n1 -= 48 + (1 << 11);
+            assert(n1 < (1 << 20));
+            dbuf_putc(dbuf, ((n1 >> 16) + 56) | (type << 6));
+            dbuf_putc(dbuf, n1 >> 8);
+            dbuf_putc(dbuf, n1);
+        }
+        cw_len_tab[dbuf->size - cw_start - 1]++;
+        if (type == 0 || type == 1)
+            dbuf_putc(dbuf, cc);
+        cc_table_len++;
+        i += n;
+    }
+
+    /* last index entry */
+    v = i;
+    dbuf_putc(dbuf1, v);
+    dbuf_putc(dbuf1, v >> 8);
+    dbuf_putc(dbuf1, v >> 16);
+    
+    dump_byte_table(f, "unicode_cc_table", dbuf->buf, dbuf->size);
+    dump_byte_table(f, "unicode_cc_index", dbuf1->buf, dbuf1->size);
+
+#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE)
+    printf("CC table: size=%d (%d entries) [",
+           (int)(dbuf->size + dbuf1->size),
+           cc_table_len);
+    for(i = 0; i < countof(cw_len_tab); i++)
+        printf(" %d", cw_len_tab[i]);
+    printf(" ]\n");
+#endif
+    dbuf_free(dbuf);
+    dbuf_free(dbuf1);
+}
+
+/* maximum length of decomposition: 18 chars (1), then 8 */
+#ifndef USE_TEST
+typedef enum {
+    DECOMP_TYPE_C1, /* 16 bit char */
+    DECOMP_TYPE_L1, /* 16 bit char table */
+    DECOMP_TYPE_L2,
+    DECOMP_TYPE_L3,
+    DECOMP_TYPE_L4,
+    DECOMP_TYPE_L5, /* XXX: not used */
+    DECOMP_TYPE_L6, /* XXX: could remove */
+    DECOMP_TYPE_L7, /* XXX: could remove */
+    DECOMP_TYPE_LL1, /* 18 bit char table */
+    DECOMP_TYPE_LL2,
+    DECOMP_TYPE_S1, /* 8 bit char table */
+    DECOMP_TYPE_S2,
+    DECOMP_TYPE_S3,
+    DECOMP_TYPE_S4,
+    DECOMP_TYPE_S5,
+    DECOMP_TYPE_I1, /* increment 16 bit char value */
+    DECOMP_TYPE_I2_0,
+    DECOMP_TYPE_I2_1,
+    DECOMP_TYPE_I3_1,
+    DECOMP_TYPE_I3_2,
+    DECOMP_TYPE_I4_1,
+    DECOMP_TYPE_I4_2,
+    DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */
+    DECOMP_TYPE_B2,
+    DECOMP_TYPE_B3,
+    DECOMP_TYPE_B4,
+    DECOMP_TYPE_B5,
+    DECOMP_TYPE_B6,
+    DECOMP_TYPE_B7,
+    DECOMP_TYPE_B8,
+    DECOMP_TYPE_B18,
+    DECOMP_TYPE_LS2,
+    DECOMP_TYPE_PAT3,
+    DECOMP_TYPE_S2_UL,
+    DECOMP_TYPE_LS2_UL,
+} DecompTypeEnum;
+#endif
+
+const char *decomp_type_str[] = {
+    "C1",
+    "L1",
+    "L2",
+    "L3",
+    "L4",
+    "L5",
+    "L6",
+    "L7",
+    "LL1",
+    "LL2",
+    "S1",
+    "S2",
+    "S3",
+    "S4",
+    "S5",
+    "I1",
+    "I2_0",
+    "I2_1",
+    "I3_1",
+    "I3_2",
+    "I4_1",
+    "I4_2",
+    "B1",
+    "B2",
+    "B3",
+    "B4",
+    "B5",
+    "B6",
+    "B7",
+    "B8",
+    "B18",
+    "LS2",
+    "PAT3",
+    "S2_UL",
+    "LS2_UL",
+};
+
+const int decomp_incr_tab[4][4] = {
+    { DECOMP_TYPE_I1, 0, -1 },
+    { DECOMP_TYPE_I2_0, 0, 1, -1 },
+    { DECOMP_TYPE_I3_1, 1, 2, -1 },
+    { DECOMP_TYPE_I4_1, 1, 2, -1 },
+};
+
+/*
+  entry size:
+  type   bits
+  code   18 
+  len    7
+  compat 1
+  type   5
+  index  16
+  total  47
+*/
+
+typedef struct {
+    int code;
+    uint8_t len;
+    uint8_t type;
+    uint8_t c_len;
+    uint16_t c_min;
+    uint16_t data_index;
+    int cost; /* size in bytes from this entry to the end */
+} DecompEntry;
+
+int get_decomp_run_size(const DecompEntry *de)
+{
+    int s;
+    s = 6;
+    if (de->type <= DECOMP_TYPE_C1) {
+        /* nothing more */
+    } else if (de->type <= DECOMP_TYPE_L7) {
+        s += de->len * de->c_len * 2;
+    } else if (de->type <= DECOMP_TYPE_LL2) {
+        /* 18 bits per char */
+        s += (de->len * de->c_len * 18 + 7) / 8;
+    } else if (de->type <= DECOMP_TYPE_S5) {
+        s += de->len * de->c_len;
+    } else if (de->type <= DECOMP_TYPE_I4_2) {
+        s += de->c_len * 2;
+    } else if (de->type <= DECOMP_TYPE_B18) {
+        s += 2 + de->len * de->c_len;
+    } else if (de->type <= DECOMP_TYPE_LS2) {
+        s += de->len * 3;
+    } else if (de->type <= DECOMP_TYPE_PAT3) {
+        s += 4 + de->len * 2;
+    } else if (de->type <= DECOMP_TYPE_S2_UL) {
+        s += de->len;
+    } else if (de->type <= DECOMP_TYPE_LS2_UL) {
+        s += (de->len / 2) * 3;
+    } else {
+        abort();
+    }
+    return s;
+}
+
+static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 };
+
+/* return -1 if not found */
+int get_short_code(int c)
+{
+    int i;
+    if (c < 0x80) {
+        return c;
+    } else if (c >= 0x300 && c < 0x350) {
+        return c - 0x300 + 0x80;
+    } else {
+        for(i = 0; i < countof(unicode_short_table); i++) {
+            if (c == unicode_short_table[i])
+                return i + 0x80 + 0x50;
+        }
+        return -1;
+    }
+}
+
+static BOOL is_short(int code)
+{
+    return get_short_code(code) >= 0;
+}
+
+static BOOL is_short_tab(const int *tab, int len)
+{
+    int i;
+    for(i = 0; i < len; i++) {
+        if (!is_short(tab[i]))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static BOOL is_16bit(const int *tab, int len)
+{
+    int i;
+    for(i = 0; i < len; i++) {
+        if (tab[i] > 0xffff)
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static uint32_t to_lower_simple(uint32_t c)
+{
+    /* Latin1 and Cyrillic */
+    if (c < 0x100 || (c >= 0x410 && c <= 0x42f))
+        c += 0x20;
+    else
+        c++;
+    return c;
+}
+
+/* select best encoding with dynamic programming */
+void find_decomp_run(DecompEntry *tab_de, int i)
+{
+    DecompEntry de_s, *de = &de_s;
+    CCInfo *ci, *ci1, *ci2;
+    int l, j, n, len_max;
+    
+    ci = &unicode_db[i];
+    l = ci->decomp_len;
+    if (l == 0) {
+        tab_de[i].cost = tab_de[i + 1].cost;
+        return;
+    }
+
+    /* the offset for the compose table has only 6 bits, so we must
+       limit if it can be used by the compose table */
+    if (!ci->is_compat && !ci->is_excluded && l == 2)
+        len_max = 64; 
+    else
+        len_max = 127;
+    
+    tab_de[i].cost = 0x7fffffff;
+    
+    if (!is_16bit(ci->decomp_data, l)) {
+        assert(l <= 2);
+
+        n = 1;
+        for(;;) {
+            de->code = i;
+            de->len = n;
+            de->type = DECOMP_TYPE_LL1 + l - 1;
+            de->c_len = l;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+            if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            /* Note: we accept a hole */
+            if (!(ci1->decomp_len == 0 ||
+                  (ci1->decomp_len == l &&
+                   ci1->is_compat == ci->is_compat)))
+                break;
+            n++;
+        }
+        return;
+    }
+
+    if (l <= 7) {
+        n = 1;
+        for(;;) {
+            de->code = i;
+            de->len = n;
+            if (l == 1 && n == 1) {
+                de->type = DECOMP_TYPE_C1;
+            } else {
+                assert(l <= 8);
+                de->type = DECOMP_TYPE_L1 + l - 1;
+            }
+            de->c_len = l;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+            
+            if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            /* Note: we accept a hole */
+            if (!(ci1->decomp_len == 0 ||
+                  (ci1->decomp_len == l &&
+                   ci1->is_compat == ci->is_compat &&
+                   is_16bit(ci1->decomp_data, l))))
+                break;
+            n++;
+        }
+    }
+    
+    if (l <= 8 || l == 18) {
+        int c_min, c_max, c;
+        c_min = c_max = -1;
+        n = 1;
+        for(;;) {
+            ci1 = &unicode_db[i + n - 1];
+            for(j = 0; j < l; j++) {
+                c = ci1->decomp_data[j];
+                if (c == 0x20) {
+                    /* we accept space for Arabic */
+                } else if (c_min == -1) {
+                    c_min = c_max = c;
+                } else {
+                    c_min = min_int(c_min, c);
+                    c_max = max_int(c_max, c);
+                }
+            }
+            if ((c_max - c_min) > 254)
+                break;
+            de->code = i;
+            de->len = n;
+            if (l == 18)
+                de->type = DECOMP_TYPE_B18;
+            else
+                de->type = DECOMP_TYPE_B1 + l - 1;
+            de->c_len = l;
+            de->c_min = c_min;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+            if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            if (!(ci1->decomp_len == l &&
+                  ci1->is_compat == ci->is_compat))
+                break;
+            n++;
+        }
+    }
+
+    /* find an ascii run */
+    if (l <= 5 && is_short_tab(ci->decomp_data, l)) {
+        n = 1;
+        for(;;) {
+            de->code = i;
+            de->len = n;
+            de->type = DECOMP_TYPE_S1 + l - 1;
+            de->c_len = l;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+
+            if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            /* Note: we accept a hole */
+            if (!(ci1->decomp_len == 0 ||
+                  (ci1->decomp_len == l &&
+                   ci1->is_compat == ci->is_compat &&
+                   is_short_tab(ci1->decomp_data, l))))
+                break;
+            n++;
+        }
+    }
+
+    /* check if a single char is increasing */
+    if (l <= 4) {
+        int idx1, idx;
+        
+        for(idx1 = 1; (idx = decomp_incr_tab[l - 1][idx1]) >= 0; idx1++) {
+            n = 1;
+            for(;;) {
+                de->code = i;
+                de->len = n;
+                de->type = decomp_incr_tab[l - 1][0] + idx1 - 1;
+                de->c_len = l;
+                de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+                if (de->cost < tab_de[i].cost) {
+                    tab_de[i] = *de;
+                }
+
+                if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                    break;
+                ci1 = &unicode_db[i + n];
+                if (!(ci1->decomp_len == l &&
+                      ci1->is_compat == ci->is_compat))
+                    goto next1;
+                for(j = 0; j < l; j++) {
+                    if (j == idx) {
+                        if (ci1->decomp_data[j] != ci->decomp_data[j] + n)
+                            goto next1;
+                    } else {
+                        if (ci1->decomp_data[j] != ci->decomp_data[j])
+                            goto next1;
+                    }
+                }
+                n++;
+            }
+        next1: ;
+        }
+    }
+
+    if (l == 3) {
+        n = 1;
+        for(;;) {
+            de->code = i;
+            de->len = n;
+            de->type = DECOMP_TYPE_PAT3;
+            de->c_len = l;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+            if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            if (!(ci1->decomp_len == l &&
+                  ci1->is_compat == ci->is_compat &&
+                  ci1->decomp_data[1] <= 0xffff &&
+                  ci1->decomp_data[0] == ci->decomp_data[0] &&
+                  ci1->decomp_data[l - 1] == ci->decomp_data[l - 1]))
+                break;
+            n++;
+        }
+    }
+
+    if (l == 2 && is_short(ci->decomp_data[1])) {
+        n = 1;
+        for(;;) {
+            de->code = i;
+            de->len = n;
+            de->type = DECOMP_TYPE_LS2;
+            de->c_len = l;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+            if (!((i + n) <= CHARCODE_MAX && n < len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            if (!(ci1->decomp_len == 0 ||
+                  (ci1->decomp_len == l &&
+                   ci1->is_compat == ci->is_compat &&
+                   ci1->decomp_data[0] <= 0xffff &&
+                   is_short(ci1->decomp_data[1]))))
+                break;
+            n++;
+        }
+    }
+
+    if (l == 2) {
+        BOOL is_16bit;
+        
+        n = 0;
+        is_16bit = FALSE;
+        for(;;) {
+            if (!((i + n + 1) <= CHARCODE_MAX && n + 2 <= len_max))
+                break;
+            ci1 = &unicode_db[i + n];
+            if (!(ci1->decomp_len == l &&
+                  ci1->is_compat == ci->is_compat &&
+                  is_short(ci1->decomp_data[1])))
+                break;
+            if (!is_16bit && !is_short(ci1->decomp_data[0]))
+                is_16bit = TRUE;
+            ci2 = &unicode_db[i + n + 1];
+            if (!(ci2->decomp_len == l &&
+                  ci2->is_compat == ci->is_compat &&
+                  ci2->decomp_data[0] == to_lower_simple(ci1->decomp_data[0])  &&
+                  ci2->decomp_data[1] == ci1->decomp_data[1]))
+                break;
+            n += 2;
+            de->code = i;
+            de->len = n;
+            de->type = DECOMP_TYPE_S2_UL + is_16bit;
+            de->c_len = l;
+            de->cost = get_decomp_run_size(de) + tab_de[i + n].cost;
+            if (de->cost < tab_de[i].cost) {
+                tab_de[i] = *de;
+            }
+        }
+    }
+}
+
+void put16(uint8_t *data_buf, int *pidx, uint16_t c)
+{
+    int idx;
+    idx = *pidx;
+    data_buf[idx++] = c;
+    data_buf[idx++] = c >> 8;
+    *pidx = idx;
+}
+
+void add_decomp_data(uint8_t *data_buf, int *pidx, DecompEntry *de)
+{
+    int i, j, idx, c;
+    CCInfo *ci;
+    
+    idx = *pidx;
+    de->data_index = idx;
+    if (de->type <= DECOMP_TYPE_C1) {
+        ci = &unicode_db[de->code];
+        assert(ci->decomp_len == 1);
+        de->data_index = ci->decomp_data[0];
+    } else if (de->type <= DECOMP_TYPE_L7) {
+        for(i = 0; i < de->len; i++) {
+            ci = &unicode_db[de->code + i];
+            for(j = 0; j < de->c_len; j++) {
+                if (ci->decomp_len == 0)
+                    c = 0;
+                else
+                    c = ci->decomp_data[j];
+                put16(data_buf, &idx,  c);
+            }
+        }
+    } else if (de->type <= DECOMP_TYPE_LL2) {
+        int n, p, k;
+        n = (de->len * de->c_len * 18 + 7) / 8;
+        p = de->len * de->c_len * 2;
+        memset(data_buf + idx, 0, n);
+        k = 0;
+        for(i = 0; i < de->len; i++) {
+            ci = &unicode_db[de->code + i];
+            for(j = 0; j < de->c_len; j++) {
+                if (ci->decomp_len == 0)
+                    c = 0;
+                else
+                    c = ci->decomp_data[j];
+                data_buf[idx + k * 2] = c;
+                data_buf[idx + k * 2 + 1] = c >> 8;
+                data_buf[idx + p + (k / 4)] |= (c >> 16) << ((k % 4) * 2);
+                k++;
+            }
+        }
+        idx += n;
+    } else if (de->type <= DECOMP_TYPE_S5) {
+        for(i = 0; i < de->len; i++) {
+            ci = &unicode_db[de->code + i];
+            for(j = 0; j < de->c_len; j++) {
+                if (ci->decomp_len == 0)
+                    c = 0;
+                else
+                    c = ci->decomp_data[j];
+                c = get_short_code(c);
+                assert(c >= 0);
+                data_buf[idx++] = c;
+            }
+        }
+    } else if (de->type <= DECOMP_TYPE_I4_2) {
+        ci = &unicode_db[de->code];
+        assert(ci->decomp_len == de->c_len);
+        for(j = 0; j < de->c_len; j++)
+            put16(data_buf, &idx, ci->decomp_data[j]);
+    } else if (de->type <= DECOMP_TYPE_B18) {
+        c = de->c_min;
+        data_buf[idx++] = c;
+        data_buf[idx++] = c >> 8;
+        for(i = 0; i < de->len; i++) {
+            ci = &unicode_db[de->code + i];
+            for(j = 0; j < de->c_len; j++) {
+                assert(ci->decomp_len == de->c_len);
+                c = ci->decomp_data[j];
+                if (c == 0x20) {
+                    c = 0xff;
+                } else {
+                    c -= de->c_min;
+                    assert((uint32_t)c <= 254);
+                }
+                data_buf[idx++] = c;
+            }
+        }
+    } else if (de->type <= DECOMP_TYPE_LS2) {
+        assert(de->c_len == 2);
+        for(i = 0; i < de->len; i++) {
+            ci = &unicode_db[de->code + i];
+            if (ci->decomp_len == 0)
+                c = 0;
+            else
+                c = ci->decomp_data[0];
+            put16(data_buf, &idx,  c);
+
+            if (ci->decomp_len == 0)
+                c = 0;
+            else
+                c = ci->decomp_data[1];
+            c = get_short_code(c);
+            assert(c >= 0);
+            data_buf[idx++] = c;
+        }
+    } else if (de->type <= DECOMP_TYPE_PAT3) {
+        ci = &unicode_db[de->code];
+        assert(ci->decomp_len == 3);
+        put16(data_buf, &idx,  ci->decomp_data[0]);
+        put16(data_buf, &idx,  ci->decomp_data[2]);
+        for(i = 0; i < de->len; i++) {
+            ci = &unicode_db[de->code + i];
+            assert(ci->decomp_len == 3);
+            put16(data_buf, &idx,  ci->decomp_data[1]);
+        }
+    } else if (de->type <= DECOMP_TYPE_S2_UL) {
+        for(i = 0; i < de->len; i += 2) {
+            ci = &unicode_db[de->code + i];
+            c = ci->decomp_data[0];
+            c = get_short_code(c);
+            assert(c >= 0);
+            data_buf[idx++] = c;
+            c = ci->decomp_data[1];
+            c = get_short_code(c);
+            assert(c >= 0);
+            data_buf[idx++] = c;
+        }
+    } else if (de->type <= DECOMP_TYPE_LS2_UL) {
+        for(i = 0; i < de->len; i += 2) {
+            ci = &unicode_db[de->code + i];
+            c = ci->decomp_data[0];
+            put16(data_buf, &idx,  c);
+            c = ci->decomp_data[1];
+            c = get_short_code(c);
+            assert(c >= 0);
+            data_buf[idx++] = c;
+        }
+    } else {
+        abort();
+    }
+    *pidx = idx;
+}
+
+#if 0
+void dump_large_char(void)
+{
+    int i, j;
+    for(i = 0; i <= CHARCODE_MAX; i++) {
+        CCInfo *ci = &unicode_db[i];
+        for(j = 0; j < ci->decomp_len; j++) {
+            if (ci->decomp_data[j] > 0xffff)
+                printf("%05x\n", ci->decomp_data[j]);
+        }
+    }
+}
+#endif
+
+void build_compose_table(FILE *f, const DecompEntry *tab_de);
+
+void build_decompose_table(FILE *f)
+{
+    int i, array_len, code_max, data_len, count;
+    DecompEntry *tab_de, de_s, *de = &de_s;
+    uint8_t *data_buf;
+    
+    code_max = CHARCODE_MAX;
+    
+    tab_de = mallocz((code_max + 2) * sizeof(*tab_de));
+
+    for(i = code_max; i >= 0; i--) {
+        find_decomp_run(tab_de, i);
+    }
+
+    /* build the data buffer */
+    data_buf = malloc(100000);
+    data_len = 0;
+    array_len = 0;
+    for(i = 0; i <= code_max; i++) {
+        de = &tab_de[i];
+        if (de->len != 0) {
+            add_decomp_data(data_buf, &data_len, de);
+            i += de->len - 1;
+            array_len++;
+        }
+    }
+
+#ifdef DUMP_DECOMP_TABLE
+    /* dump */
+    {
+        int size, size1;
+        
+        printf("START LEN   TYPE  L C SIZE\n");
+        size = 0;
+        for(i = 0; i <= code_max; i++) {
+            de = &tab_de[i];
+            if (de->len != 0) {
+                size1 = get_decomp_run_size(de);
+                printf("%05x %3d %6s %2d %1d %4d\n", i, de->len,
+                       decomp_type_str[de->type], de->c_len,
+                       unicode_db[i].is_compat, size1);
+                i += de->len - 1;
+                size += size1;
+            }
+        }
+        
+        printf("array_len=%d estimated size=%d bytes actual=%d bytes\n",
+               array_len, size, array_len * 6 + data_len);
+    }
+#endif
+
+    fprintf(f, "static const uint32_t unicode_decomp_table1[%u] = {",
+            array_len);
+    count = 0;
+    for(i = 0; i <= code_max; i++) {
+        de = &tab_de[i];
+        if (de->len != 0) {
+            uint32_t v;
+            if (count++ % 4 == 0)
+                fprintf(f, "\n   ");
+            v = (de->code << (32 - 18)) |
+                (de->len << (32 - 18 - 7)) |
+                (de->type << (32 - 18 - 7 - 6)) |
+                unicode_db[de->code].is_compat;
+            fprintf(f, " 0x%08x,", v);
+            i += de->len - 1;
+        }
+    }
+    fprintf(f, "\n};\n\n");
+
+    fprintf(f, "static const uint16_t unicode_decomp_table2[%u] = {",
+            array_len);
+    count = 0;
+    for(i = 0; i <= code_max; i++) {
+        de = &tab_de[i];
+        if (de->len != 0) {
+            if (count++ % 8 == 0)
+                fprintf(f, "\n   ");
+            fprintf(f, " 0x%04x,", de->data_index);
+            i += de->len - 1;
+        }
+    }
+    fprintf(f, "\n};\n\n");
+    
+    fprintf(f, "static const uint8_t unicode_decomp_data[%u] = {",
+            data_len);
+    for(i = 0; i < data_len; i++) {
+        if (i % 8 == 0)
+            fprintf(f, "\n   ");
+        fprintf(f, " 0x%02x,", data_buf[i]);
+    }
+    fprintf(f, "\n};\n\n");
+
+    build_compose_table(f, tab_de);
+
+    free(data_buf);
+    
+    free(tab_de);
+}
+
+typedef struct {
+    uint32_t c[2];
+    uint32_t p;
+} ComposeEntry;
+
+#define COMPOSE_LEN_MAX 10000
+
+static int ce_cmp(const void *p1, const void *p2)
+{
+    const ComposeEntry *ce1 = p1;
+    const ComposeEntry *ce2 = p2;
+    int i;
+
+    for(i = 0; i < 2; i++) {
+        if (ce1->c[i] < ce2->c[i])
+            return -1;
+        else if (ce1->c[i] > ce2->c[i])
+            return 1;
+    }
+    return 0;
+}
+
+
+static int get_decomp_pos(const DecompEntry *tab_de, int c)
+{
+    int i, v, k;
+    const DecompEntry *de;
+    
+    k = 0;
+    for(i = 0; i <= CHARCODE_MAX; i++) {
+        de = &tab_de[i];
+        if (de->len != 0) {
+            if (c >= de->code && c < de->code + de->len) {
+                v = c - de->code;
+                assert(v < 64);
+                v |= k << 6;
+                assert(v < 65536);
+                return v;
+            }
+            i += de->len - 1;
+            k++;
+        }
+    }
+    return -1;
+}
+
+void build_compose_table(FILE *f, const DecompEntry *tab_de)
+{
+    int i, v, tab_ce_len;
+    ComposeEntry *ce, *tab_ce;
+    
+    tab_ce = malloc(sizeof(*tab_ce) * COMPOSE_LEN_MAX);
+    tab_ce_len = 0;
+    for(i = 0; i <= CHARCODE_MAX; i++) {
+        CCInfo *ci = &unicode_db[i];
+        if (ci->decomp_len == 2 && !ci->is_compat &&
+            !ci->is_excluded) {
+            assert(tab_ce_len < COMPOSE_LEN_MAX); 
+            ce = &tab_ce[tab_ce_len++];
+            ce->c[0] = ci->decomp_data[0];
+            ce->c[1] = ci->decomp_data[1];
+            ce->p = i;
+        }
+    }
+    qsort(tab_ce, tab_ce_len, sizeof(*tab_ce), ce_cmp);
+
+#if 0
+    {
+        printf("tab_ce_len=%d\n", tab_ce_len);
+        for(i = 0; i < tab_ce_len; i++) {
+            ce = &tab_ce[i];
+            printf("%05x %05x %05x\n", ce->c[0], ce->c[1], ce->p);
+        }
+    }
+#endif
+    
+    fprintf(f, "static const uint16_t unicode_comp_table[%u] = {",
+            tab_ce_len);
+    for(i = 0; i < tab_ce_len; i++) {
+        if (i % 8 == 0)
+            fprintf(f, "\n   ");
+        v = get_decomp_pos(tab_de, tab_ce[i].p);
+        if (v < 0) {
+            printf("ERROR: entry for c=%04x not found\n",
+                   tab_ce[i].p);
+            exit(1);
+        }
+        fprintf(f, " 0x%04x,", v);
+    }
+    fprintf(f, "\n};\n\n");
+    
+    free(tab_ce);
+}
+
+#ifdef USE_TEST
+void check_decompose_table(void)
+{
+    int c;
+    CCInfo *ci;
+    int res[UNICODE_DECOMP_LEN_MAX], *ref;
+    int len, ref_len, is_compat;
+
+    for(is_compat = 0; is_compat <= 1; is_compat++) {
+        for(c = 0; c < CHARCODE_MAX; c++) {
+            ci = &unicode_db[c];
+            ref_len = ci->decomp_len;
+            ref = ci->decomp_data;
+            if (!is_compat && ci->is_compat) {
+                ref_len = 0;
+            }
+            len = unicode_decomp_char((uint32_t *)res, c, is_compat);
+            if (len != ref_len ||
+                tabcmp(res, ref, ref_len) != 0) {
+                printf("ERROR c=%05x compat=%d\n", c, is_compat);
+                dump_str("res", res, len);
+                dump_str("ref", ref, ref_len);
+                exit(1);
+            }
+        }
+    }
+}
+
+void check_compose_table(void)
+{
+    int i, p;
+    /* XXX: we don't test all the cases */
+
+    for(i = 0; i <= CHARCODE_MAX; i++) {
+        CCInfo *ci = &unicode_db[i];
+        if (ci->decomp_len == 2 && !ci->is_compat &&
+            !ci->is_excluded) {
+            p = unicode_compose_pair(ci->decomp_data[0], ci->decomp_data[1]);
+            if (p != i) {
+                printf("ERROR compose: c=%05x %05x -> %05x ref=%05x\n",
+                       ci->decomp_data[0], ci->decomp_data[1], p, i);
+                exit(1);
+            }
+        }
+    }
+    
+
+
+}
+
+#endif
+
+
+
+#ifdef USE_TEST
+
+void check_str(const char *msg, int num, const int *in_buf, int in_len,
+               const int *buf1, int len1,
+               const int *buf2, int len2)
+{
+    if (len1 != len2 || tabcmp(buf1, buf2, len1) != 0) {
+        printf("%d: ERROR %s:\n", num, msg);
+        dump_str(" in", in_buf, in_len);
+        dump_str("res", buf1, len1);
+        dump_str("ref", buf2, len2);
+        exit(1);
+    }
+}
+
+void check_cc_table(void)
+{
+    int cc, cc_ref, c;
+
+    for(c = 0; c <= CHARCODE_MAX; c++) {
+        cc_ref = unicode_db[c].combining_class;
+        cc = unicode_get_cc(c);
+        if (cc != cc_ref) {
+            printf("ERROR: c=%04x cc=%d cc_ref=%d\n",
+                   c, cc, cc_ref);
+            exit(1);
+        }
+    }
+#ifdef PROFILE
+    {
+        int64_t ti, count;
+    
+        ti = get_time_ns();
+        count = 0;
+        /* only do it on meaningful chars */
+        for(c = 0x20; c <= 0xffff; c++) {
+            cc_ref = unicode_db[c].combining_class;
+            cc = unicode_get_cc(c);
+            count++;
+        }
+        ti = get_time_ns() - ti;
+        printf("cc time=%0.1f ns/char\n",
+               (double)ti / count);
+    }
+#endif
+}
+
+void normalization_test(const char *filename)
+{
+    FILE *f;
+    char line[4096], *p;
+    int *in_str, *nfc_str, *nfd_str, *nfkc_str, *nfkd_str;
+    int in_len, nfc_len, nfd_len, nfkc_len, nfkd_len;
+    int *buf, buf_len, pos;
+    
+    f = fopen(filename, "rb");
+    if (!f) {
+        perror(filename);
+        exit(1);
+    }
+    pos = 0;
+    for(;;) {
+        if (!get_line(line, sizeof(line), f))
+            break;
+        pos++;
+        p = line;
+        while (isspace(*p))
+            p++;
+        if (*p == '#' || *p == '@')
+            continue;
+        in_str = get_field_str(&in_len, p, 0);
+        nfc_str = get_field_str(&nfc_len, p, 1);
+        nfd_str = get_field_str(&nfd_len, p, 2);
+        nfkc_str = get_field_str(&nfkc_len, p, 3);
+        nfkd_str = get_field_str(&nfkd_len, p, 4);
+
+        //        dump_str("in", in_str, in_len);
+
+        buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFD, NULL, NULL);
+        check_str("nfd", pos, in_str, in_len, buf, buf_len, nfd_str, nfd_len);
+        free(buf);
+
+        buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFKD, NULL, NULL);
+        check_str("nfkd", pos, in_str, in_len, buf, buf_len, nfkd_str, nfkd_len);
+        free(buf);
+        
+        buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFC, NULL, NULL);
+        check_str("nfc", pos, in_str, in_len, buf, buf_len, nfc_str, nfc_len);
+        free(buf);
+
+        buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFKC, NULL, NULL);
+        check_str("nfkc", pos, in_str, in_len, buf, buf_len, nfkc_str, nfkc_len);
+        free(buf);
+
+        free(in_str);
+        free(nfc_str);
+        free(nfd_str);
+        free(nfkc_str);
+        free(nfkd_str);
+    }
+    fclose(f);
+}
+#endif
+
+int main(int argc, char **argv)
+{
+    const char *unicode_db_path, *outfilename;
+    char filename[1024];
+    
+    if (argc < 2) {
+        printf("usage: %s unicode_db_path [output_file]\n"
+               "\n"
+               "If no output_file is given, a self test is done using the current unicode library\n",
+               argv[0]);
+        exit(1);
+    }
+    unicode_db_path = argv[1];
+    outfilename = NULL;
+    if (argc >= 3)
+        outfilename = argv[2];
+
+    unicode_db = mallocz(sizeof(unicode_db[0]) * (CHARCODE_MAX + 1));
+
+    snprintf(filename, sizeof(filename), "%s/UnicodeData.txt", unicode_db_path);
+
+    parse_unicode_data(filename);
+
+    snprintf(filename, sizeof(filename), "%s/SpecialCasing.txt", unicode_db_path);
+    parse_special_casing(unicode_db, filename);
+    
+    snprintf(filename, sizeof(filename), "%s/CaseFolding.txt", unicode_db_path);
+    parse_case_folding(unicode_db, filename);
+
+    snprintf(filename, sizeof(filename), "%s/CompositionExclusions.txt", unicode_db_path);
+    parse_composition_exclusions(filename);
+    
+    snprintf(filename, sizeof(filename), "%s/DerivedCoreProperties.txt", unicode_db_path);
+    parse_derived_core_properties(filename);
+
+    snprintf(filename, sizeof(filename), "%s/DerivedNormalizationProps.txt", unicode_db_path);
+    parse_derived_norm_properties(filename);
+
+    snprintf(filename, sizeof(filename), "%s/PropList.txt", unicode_db_path);
+    parse_prop_list(filename);
+
+    snprintf(filename, sizeof(filename), "%s/Scripts.txt", unicode_db_path);
+    parse_scripts(filename);
+
+    snprintf(filename, sizeof(filename), "%s/ScriptExtensions.txt",
+             unicode_db_path);
+    parse_script_extensions(filename);
+                        
+    snprintf(filename, sizeof(filename), "%s/emoji-data.txt",
+             unicode_db_path);
+    parse_prop_list(filename);
+
+    //    dump_data(unicode_db);
+
+    build_conv_table(unicode_db);
+    
+    //    dump_table();
+
+    if (!outfilename) {
+#ifdef USE_TEST
+        check_case_conv();
+        check_flags();
+        check_decompose_table();
+        check_compose_table();
+        check_cc_table();
+        snprintf(filename, sizeof(filename), "%s/NormalizationTest.txt", unicode_db_path);
+        normalization_test(filename);
+#else
+        fprintf(stderr, "Tests are not compiled\n");
+        exit(1);
+#endif
+    } else
+    {
+        FILE *fo = fopen(outfilename, "wb");
+        
+        if (!fo) {
+            perror(outfilename);
+            exit(1);
+        }
+        fprintf(fo,
+                "/* Compressed unicode tables */\n"
+                "/* Automatically generated file - do not edit */\n"
+                "\n"
+                "#include <stdint.h>\n"
+                "\n");
+        dump_case_conv_table(fo);
+        compute_internal_props();
+        build_flags_tables(fo);
+        fprintf(fo, "#ifdef CONFIG_ALL_UNICODE\n\n");
+        build_cc_table(fo);
+        build_decompose_table(fo);
+        build_general_category_table(fo);
+        build_script_table(fo);
+        build_script_ext_table(fo);
+        build_prop_list_table(fo);
+        fprintf(fo, "#endif /* CONFIG_ALL_UNICODE */\n");
+        fclose(fo);
+    }
+    return 0;
+}
diff --git a/unicode_gen_def.h b/unicode_gen_def.h
new file mode 100644
index 0000000..08dd8d7
--- /dev/null
+++ b/unicode_gen_def.h
@@ -0,0 +1,280 @@
+#ifdef UNICODE_GENERAL_CATEGORY
+DEF(Cn, "Unassigned") /* must be zero */
+DEF(Lu, "Uppercase_Letter")
+DEF(Ll, "Lowercase_Letter")
+DEF(Lt, "Titlecase_Letter")
+DEF(Lm, "Modifier_Letter")
+DEF(Lo, "Other_Letter")
+DEF(Mn, "Nonspacing_Mark")
+DEF(Mc, "Spacing_Mark")
+DEF(Me, "Enclosing_Mark")
+DEF(Nd, "Decimal_Number,digit")
+DEF(Nl, "Letter_Number")
+DEF(No, "Other_Number")
+DEF(Sm, "Math_Symbol")
+DEF(Sc, "Currency_Symbol")
+DEF(Sk, "Modifier_Symbol")
+DEF(So, "Other_Symbol")
+DEF(Pc, "Connector_Punctuation")
+DEF(Pd, "Dash_Punctuation")
+DEF(Ps, "Open_Punctuation")
+DEF(Pe, "Close_Punctuation")
+DEF(Pi, "Initial_Punctuation")
+DEF(Pf, "Final_Punctuation")
+DEF(Po, "Other_Punctuation")
+DEF(Zs, "Space_Separator")
+DEF(Zl, "Line_Separator")
+DEF(Zp, "Paragraph_Separator")
+DEF(Cc, "Control,cntrl")
+DEF(Cf, "Format")
+DEF(Cs, "Surrogate")
+DEF(Co, "Private_Use")
+/* synthetic properties */
+DEF(LC, "Cased_Letter")
+DEF(L, "Letter")
+DEF(M, "Mark,Combining_Mark")
+DEF(N, "Number")
+DEF(S, "Symbol")
+DEF(P, "Punctuation,punct")
+DEF(Z, "Separator")
+DEF(C, "Other")
+#endif
+
+#ifdef UNICODE_SCRIPT
+/* scripts aliases names in PropertyValueAliases.txt */
+DEF(Unknown, "Zzzz")
+DEF(Adlam, "Adlm")
+DEF(Ahom, "Ahom")
+DEF(Anatolian_Hieroglyphs, "Hluw")
+DEF(Arabic, "Arab")
+DEF(Armenian, "Armn")
+DEF(Avestan, "Avst")
+DEF(Balinese, "Bali")
+DEF(Bamum, "Bamu")
+DEF(Bassa_Vah, "Bass")
+DEF(Batak, "Batk")
+DEF(Bengali, "Beng")
+DEF(Bhaiksuki, "Bhks")
+DEF(Bopomofo, "Bopo")
+DEF(Brahmi, "Brah")
+DEF(Braille, "Brai")
+DEF(Buginese, "Bugi")
+DEF(Buhid, "Buhd")
+DEF(Canadian_Aboriginal, "Cans")
+DEF(Carian, "Cari")
+DEF(Caucasian_Albanian, "Aghb")
+DEF(Chakma, "Cakm")
+DEF(Cham, "Cham")
+DEF(Cherokee, "Cher")
+DEF(Common, "Zyyy")
+DEF(Coptic, "Copt,Qaac")
+DEF(Cuneiform, "Xsux")
+DEF(Cypriot, "Cprt")
+DEF(Cyrillic, "Cyrl")
+DEF(Deseret, "Dsrt")
+DEF(Devanagari, "Deva")
+DEF(Dogra, "Dogr")
+DEF(Duployan, "Dupl")
+DEF(Egyptian_Hieroglyphs, "Egyp")
+DEF(Elbasan, "Elba")
+DEF(Elymaic, "Elym")
+DEF(Ethiopic, "Ethi")
+DEF(Georgian, "Geor")
+DEF(Glagolitic, "Glag")
+DEF(Gothic, "Goth")
+DEF(Grantha, "Gran")
+DEF(Greek, "Grek")
+DEF(Gujarati, "Gujr")
+DEF(Gunjala_Gondi, "Gong")
+DEF(Gurmukhi, "Guru")
+DEF(Han, "Hani")
+DEF(Hangul, "Hang")
+DEF(Hanifi_Rohingya, "Rohg")
+DEF(Hanunoo, "Hano")
+DEF(Hatran, "Hatr")
+DEF(Hebrew, "Hebr")
+DEF(Hiragana, "Hira")
+DEF(Imperial_Aramaic, "Armi")
+DEF(Inherited, "Zinh,Qaai")
+DEF(Inscriptional_Pahlavi, "Phli")
+DEF(Inscriptional_Parthian, "Prti")
+DEF(Javanese, "Java")
+DEF(Kaithi, "Kthi")
+DEF(Kannada, "Knda")
+DEF(Katakana, "Kana")
+DEF(Kayah_Li, "Kali")
+DEF(Kharoshthi, "Khar")
+DEF(Khmer, "Khmr")
+DEF(Khojki, "Khoj")
+DEF(Khudawadi, "Sind")
+DEF(Lao, "Laoo")
+DEF(Latin, "Latn")
+DEF(Lepcha, "Lepc")
+DEF(Limbu, "Limb")
+DEF(Linear_A, "Lina")
+DEF(Linear_B, "Linb")
+DEF(Lisu, "Lisu")
+DEF(Lycian, "Lyci")
+DEF(Lydian, "Lydi")
+DEF(Makasar, "Maka")
+DEF(Mahajani, "Mahj")
+DEF(Malayalam, "Mlym")
+DEF(Mandaic, "Mand")
+DEF(Manichaean, "Mani")
+DEF(Marchen, "Marc")
+DEF(Masaram_Gondi, "Gonm")
+DEF(Medefaidrin, "Medf")
+DEF(Meetei_Mayek, "Mtei")
+DEF(Mende_Kikakui, "Mend")
+DEF(Meroitic_Cursive, "Merc")
+DEF(Meroitic_Hieroglyphs, "Mero")
+DEF(Miao, "Plrd")
+DEF(Modi, "Modi")
+DEF(Mongolian, "Mong")
+DEF(Mro, "Mroo")
+DEF(Multani, "Mult")
+DEF(Myanmar, "Mymr")
+DEF(Nabataean, "Nbat")
+DEF(Nandinagari, "Nand")
+DEF(New_Tai_Lue, "Talu")
+DEF(Newa, "Newa")
+DEF(Nko, "Nkoo")
+DEF(Nushu, "Nshu")
+DEF(Nyiakeng_Puachue_Hmong, "Hmnp")
+DEF(Ogham, "Ogam")
+DEF(Ol_Chiki, "Olck")
+DEF(Old_Hungarian, "Hung")
+DEF(Old_Italic, "Ital")
+DEF(Old_North_Arabian, "Narb")
+DEF(Old_Permic, "Perm")
+DEF(Old_Persian, "Xpeo")
+DEF(Old_Sogdian, "Sogo")
+DEF(Old_South_Arabian, "Sarb")
+DEF(Old_Turkic, "Orkh")
+DEF(Oriya, "Orya")
+DEF(Osage, "Osge")
+DEF(Osmanya, "Osma")
+DEF(Pahawh_Hmong, "Hmng")
+DEF(Palmyrene, "Palm")
+DEF(Pau_Cin_Hau, "Pauc")
+DEF(Phags_Pa, "Phag")
+DEF(Phoenician, "Phnx")
+DEF(Psalter_Pahlavi, "Phlp")
+DEF(Rejang, "Rjng")
+DEF(Runic, "Runr")
+DEF(Samaritan, "Samr")
+DEF(Saurashtra, "Saur")
+DEF(Sharada, "Shrd")
+DEF(Shavian, "Shaw")
+DEF(Siddham, "Sidd")
+DEF(SignWriting, "Sgnw")
+DEF(Sinhala, "Sinh")
+DEF(Sogdian, "Sogd")
+DEF(Sora_Sompeng, "Sora")
+DEF(Soyombo, "Soyo")
+DEF(Sundanese, "Sund")
+DEF(Syloti_Nagri, "Sylo")
+DEF(Syriac, "Syrc")
+DEF(Tagalog, "Tglg")
+DEF(Tagbanwa, "Tagb")
+DEF(Tai_Le, "Tale")
+DEF(Tai_Tham, "Lana")
+DEF(Tai_Viet, "Tavt")
+DEF(Takri, "Takr")
+DEF(Tamil, "Taml")
+DEF(Tangut, "Tang")
+DEF(Telugu, "Telu")
+DEF(Thaana, "Thaa")
+DEF(Thai, "Thai")
+DEF(Tibetan, "Tibt")
+DEF(Tifinagh, "Tfng")
+DEF(Tirhuta, "Tirh")
+DEF(Ugaritic, "Ugar")
+DEF(Vai, "Vaii")
+DEF(Wancho, "Wcho")
+DEF(Warang_Citi, "Wara")
+DEF(Yi, "Yiii")
+DEF(Zanabazar_Square, "Zanb")
+#endif
+
+#ifdef UNICODE_PROP_LIST
+/* Prop list not exported to regexp */
+DEF(Hyphen, "")
+DEF(Other_Math, "")
+DEF(Other_Alphabetic, "")
+DEF(Other_Lowercase, "")
+DEF(Other_Uppercase, "")
+DEF(Other_Grapheme_Extend, "")
+DEF(Other_Default_Ignorable_Code_Point, "")
+DEF(Other_ID_Start, "")
+DEF(Other_ID_Continue, "")
+DEF(Prepended_Concatenation_Mark, "")
+/* additional computed properties for smaller tables */
+DEF(ID_Continue1, "")
+DEF(XID_Start1, "")
+DEF(XID_Continue1, "")
+DEF(Changes_When_Titlecased1, "")
+DEF(Changes_When_Casefolded1, "")
+DEF(Changes_When_NFKC_Casefolded1, "")
+
+/* Prop list exported to JS */
+DEF(ASCII_Hex_Digit, "AHex")
+DEF(Bidi_Control, "Bidi_C")
+DEF(Dash, "")
+DEF(Deprecated, "Dep")
+DEF(Diacritic, "Dia")
+DEF(Extender, "Ext")
+DEF(Hex_Digit, "Hex")
+DEF(IDS_Binary_Operator, "IDSB")
+DEF(IDS_Trinary_Operator, "IDST")
+DEF(Ideographic, "Ideo")
+DEF(Join_Control, "Join_C")
+DEF(Logical_Order_Exception, "LOE")
+DEF(Noncharacter_Code_Point, "NChar")
+DEF(Pattern_Syntax, "Pat_Syn")
+DEF(Pattern_White_Space, "Pat_WS")
+DEF(Quotation_Mark, "QMark")
+DEF(Radical, "")
+DEF(Regional_Indicator, "RI")
+DEF(Sentence_Terminal, "STerm")
+DEF(Soft_Dotted, "SD")
+DEF(Terminal_Punctuation, "Term")
+DEF(Unified_Ideograph, "UIdeo")
+DEF(Variation_Selector, "VS")
+DEF(White_Space, "space")
+DEF(Bidi_Mirrored, "Bidi_M")
+DEF(Emoji, "")
+DEF(Emoji_Component, "")
+DEF(Emoji_Modifier, "")
+DEF(Emoji_Modifier_Base, "")
+DEF(Emoji_Presentation, "")
+DEF(Extended_Pictographic, "")
+DEF(Default_Ignorable_Code_Point, "DI")
+DEF(ID_Start, "IDS")
+DEF(Case_Ignorable, "CI")
+
+/* other binary properties */
+DEF(ASCII,"")
+DEF(Alphabetic, "Alpha")
+DEF(Any, "")
+DEF(Assigned,"")
+DEF(Cased, "")
+DEF(Changes_When_Casefolded, "CWCF")
+DEF(Changes_When_Casemapped, "CWCM")
+DEF(Changes_When_Lowercased, "CWL")
+DEF(Changes_When_NFKC_Casefolded, "CWKCF")
+DEF(Changes_When_Titlecased, "CWT")
+DEF(Changes_When_Uppercased, "CWU")
+DEF(Grapheme_Base, "Gr_Base")
+DEF(Grapheme_Extend, "Gr_Ext")
+DEF(ID_Continue, "IDC")
+DEF(Lowercase, "Lower")
+DEF(Math, "")
+DEF(Uppercase, "Upper")
+DEF(XID_Continue, "XIDC")
+DEF(XID_Start, "XIDS")
+
+/* internal tables with index */
+DEF(Cased1, "")
+
+#endif