mquickjs README

The project's README from github.com/bellard/mquickjs. As one markdown file: README.md.

MicroQuickJS

Introduction

MicroQuickJS (aka. MQuickJS) is a JavaScript engine targeted at embedded systems. It compiles and runs JavaScript programs using as little as 10 kB of RAM. The whole engine requires about 100 kB of ROM (ARM Thumb-2 code) including the C library. The speed is comparable to QuickJS.

MQuickJS only supports a subset of JavaScript close to ES5. It implements a stricter mode where some error prone or inefficient JavaScript constructs are forbidden.

Although MQuickJS shares much code with QuickJS, it internals are different in order to consume less memory. In particular, it relies on a tracing garbage collector, the VM does not use the CPU stack and strings are stored in UTF-8.

REPL

The REPL is mqjs. Usage:

usage: mqjs [options] [file [args]]
-h  --help            list options
-e  --eval EXPR       evaluate EXPR
-i  --interactive     go to interactive mode
-I  --include file    include an additional file
-d  --dump            dump the memory usage stats
    --memory-limit n  limit the memory usage to 'n' bytes
--no-column           no column number in debug information
-o FILE               save the bytecode to FILE
-m32                  force 32 bit bytecode output (use with -o)
-b  --allow-bytecode  allow bytecode in input file

Compile and run a program using 10 kB of RAM:

./mqjs --memory-limit 10k tests/mandelbrot.js

In addition to normal script execution, mqjs can output the compiled bytecode to a persistent storage (file or ROM):

./mqjs -o mandelbrot.bin tests/mandelbrot.js

Then you can run the compiled bytecode as a normal script:

./mqjs -b mandelbrot.bin

The bytecode format depends on the endianness and word length (32 or 64 bit) of the CPU. On a 64 bit CPU, it is possible to use the option -m32 to generate 32 bit bytecode that can run on an embedded 32 bit system.

Use the option --no-column to remove the column number debug info (only line numbers are remaining) if you want to save some storage.

Stricter mode

MQuickJS only supports a subset of JavaScript (mostly ES5). It is always in stricter mode where some error prone JavaScript features are disabled. The general idea is that the stricter mode is a subset of JavaScript, so it still works as usual in other JavaScript engines. Here are the main points:

    a = []
    a[0] = 1; // OK to extend the array length
    a[10] = 2; // TypeError

If you need an array like object with holes, use a normal object instead:

    a = {}
    a[0] = 1;
    a[10] = 2;

new Array(len) still works as expected, but the array elements are initialized to undefined. Array literals with holes are a syntax error:

    [ 1, , 3 ] // SyntaxError
    eval('1 + 2'); // forbidden
    (1, eval)('1 + 2'); // OK

JavaScript Subset Reference

    for(var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            ...
        }
    }

Always prefer using for of instead which is supported with arrays:

    for(var prop of Object.keys(obj)) {
        ...
    }

ES5 extensions:

C API

Engine initialization

MQuickJS has almost no dependency on the C library. In particular it does not use malloc(), free() nor printf(). When creating a MQuickJS context, a memory buffer must be provided. The engine only allocates memory in this buffer:

    JSContext *ctx;
    uint8_t mem_buf[8192];
    ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib);
    ...
    JS_FreeContext(ctx);

JS_FreeContext(ctx) is only necessary to call the finalizers of user objects as no system memory is allocated by the engine.

Memory handling

The C API is very similar to QuickJS (see mquickjs.h). However, since there is a compacting garbage collector, there are important differences:

  1. Explicitly freeing values is not necessary (no JS_FreeValue()).

  2. The address of objects can move each time a JS allocation is called. The general rule is to avoid having variables of type JSValue in C. They may be present only for temporary use between MQuickJS API calls. In the other cases, always use a pointer to a JSValue. JS_PushGCRef() returns a pointer to a temporary opaque JSValue stored in a JSGCRef variable. JS_PopGCRef() must be used to release the temporary reference. The opaque value in JSGCRef is automatically updated when objects move. Example:

JSValue my_js_func(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv)
{
        JSGCRef obj1_ref, obj2_ref;
        JSValue *obj1, *obj2, ret;

        ret = JS_EXCEPTION;
        obj1 = JS_PushGCRef(ctx, &obj1_ref);
        obj2 = JS_PushGCRef(ctx, &obj2_ref);
        *obj1 = JS_NewObject(ctx);
        if (JS_IsException(*obj1))
            goto fail;
        *obj2 = JS_NewObject(ctx); // obj1 may move
        if (JS_IsException(*obj2))
            goto fail;
        JS_SetPropertyStr(ctx, *obj1, "x", *obj2);  // obj1 and obj2 may move
        ret = *obj1;
     fail:
        PopGCRef(ctx, &obj2_ref);
        PopGCRef(ctx, &obj1_ref);
        return ret;
}

When running on a PC, the DEBUG_GC define can be used to force the JS allocator to always move objects at each allocation. It is a good way to check no invalid JSValue is used.

Standard library

The standard library is compiled by a custom tool (mquickjs_build.c) to C structures that may reside in ROM. Hence the standard library instantiation is very fast and requires almost no RAM. An example of standard library for mqjs is provided in mqjs_stdlib.c. The result of its compilation is mqjs_stdlib.h.

example.c is a complete example using the MQuickJS C API.

Persistent bytecode

The bytecode generated by mqjs may be executed from ROM. In this case, it must be relocated before being flashed into ROM (see JS_RelocateBytecode()). It is then instantiated with JS_LoadBytecode() and run as normal script with JS_Run() (see mqjs.c).

As with QuickJS, no backward compatibility is guaranteed at the bytecode level. Moreover, the bytecode is not verified before being executed. Only run JavaScript bytecode from trusted sources.

Mathematical library and floating point emulation

MQuickJS contains its own tiny mathematical library (in libm.c). Moreover, in case the CPU has no floating point support, it contains its own floating point emulator which may be smaller than the one provided with the GCC toolchain.

Internals and comparison with QuickJS

Garbage collection

A tracing and compacting garbage collector is used instead of reference counting. It allows smaller objects. The GC adds an overhead of a few bits per allocated memory block. Moreover, memory fragmentation is avoided.

The engine has its own memory allocator and does not depend on the C library malloc.

Value and object representation

The value has the same size as a CPU word (hence 32 bits on a 32 bit CPU). A value may contain:

JavaScript objects require at least 3 CPU words (hence 12 bytes on a 32 bit CPU). Additional data may be allocated depending on the object class. The properties are stored in a hash table. Each property requires at least 3 CPU words. Properties may reside in ROM for standard library objects.

Property keys are JSValues unlike QuickJS where they have a specific type. They are either a string or a positive 31 bit integer. String property keys are internalized (unique).

Strings are internally stored in WTF-8 (UTF-8 + unpaired surrogates) instead of 8 or 16 bit arrays in QuickJS. Surrogate pairs are not stored explicitly but are still visible when iterating thru 16 bit code units in JavaScript. Hence full compatibility with JavaScript and UTF-8 is maintained.

C Functions can be stored as a single value to reduce the overhead. In this case, no additional properties can be added. Most standard library functions are stored this way.

Standard library

The whole standard library resides in ROM. It is generated at compile time. Only a few objects are created in RAM. Hence the engine instantiation time is very low.

Bytecode

It is a stack based bytecode (similar to QuickJS). However, the bytecode references atoms thru an indirect table.

Line and column number information is compressed with exponential-Golomb codes.

Compilation

The parser is very close to the QuickJS one but it avoids recursion so the C stack usage is bounded. There is no abstract syntax tree. The bytecode is generated in one pass with several tricks to optimize it (QuickJS has several optimization passes).

Tests and benchmarks

Running the basic tests:

make test

Running the QuickJS micro benchmark:

make microbench

Additional tests and a patched version of the Octane benchmark running in stricter mode can be downloaded here:

Running the V8 octane benchmark:

make octane

License

MQuickJS is released under the MIT license.

Unless otherwise specified, the MQuickJS sources are copyright Fabrice Bellard and Charlie Gordon.

mquickjs (bellard/mquickjs)

Searching Database…

这是什么?MicroQuickJS(MQuickJS)是一款为嵌入式系统打造的 JavaScript 引擎,运行 JS 程序时最低只需约 10 kB 的 RAM 和约 100 kB 的 ROM,速度可与完整版 QuickJS 相媲美。
这是一个可以嵌入的库,还是独立应用?它是一个 C 语言库/引擎,可通过其自身的 C API(参照 QuickJS 设计)嵌入使用;同时还附带 mqjs,这是一个命令行 REPL 和编译器,用于运行或预编译脚本。
我该如何构建和运行它?克隆仓库,然后使用自带的 Makefile 进行构建;构建出的 mqjs 可执行文件可以直接编译并运行 JS 脚本,也可以将编译后的字节码输出到文件或 ROM 镜像中,之后通过 JSLoadBytecode() 加载。
构建时是否需要额外的库或特殊的工具链?不需要。MQuickJS 几乎不依赖 C 标准库,也从不调用 malloc()、free() 或 printf();你只需在创建上下文时交给它一块内存缓冲区,然后通过提供的 Makefile 用普通的 C 编译器即可构建。
它是否支持完整的现代 JavaScript?不支持,它只实现了一个接近 ES5 的更严格子集——没有 with 关键字,没有直接 eval,没有值装箱(如 new Number(1)),数组也不能有空洞;此外还额外提供了一些 ES5+ 的扩展功能,如类型化数组以及对数组使用 for-of。按这种方式编写的代码在完整的 JS 引擎中仍可正常运行。
我能在普通电脑上运行和测试它,还是只能用在单片机上?该引擎主要面向嵌入式/单片机系统,但也可以在 PC 上构建和运行以便测试(例如 DEBUGGC 选项会在每次分配时强制移动对象,以便发现错误);字节码依赖于主机的字长和字节序,若要在 64 位机器上为嵌入式目标生成 32 位字节码,可使用 -m32 标志。
为什么它使用追踪式垃圾回收,而不是 QuickJS 的引用计数?追踪式、带压缩的垃圾回收可以让对象占用更小的空间,并避免内存碎片,这在只有几 kB RAM 可用的情况下非常重要。
为了节省 RAM,标准库是如何存放的?标准库在构建时就被预编译为可驻留在 ROM 中的 C 结构体,因此在运行时实例化它几乎是瞬间完成的,且几乎不占用 RAM。
编译后的字节码能否在不同芯片之间通用?不能。字节码格式取决于 CPU 字长和字节序,而且在执行前不会进行验证,因此你只应运行自己生成的或可信来源的字节码。
What are similar projects?lua, rust, goose, v8, micropython, porffor, hermes, quickjs
Creator
bellard
Size
0.2 MB
Stars
6.2K
Updated
CommitSep 26
Age
9mo
Commits · 90 Days