Espruino Interpreter Internals

Please see the Performance section first for a rough overview, and for the practical implications of the implementation.

Note: There's also a forum thread with some more in-depth answers to questions about the interpreter.

Compilation

Espruino's Makefile calls a few different Python scripts that precompute various things:

Much more detailed info is in the Espruino repository at https://github.com/espruino/Espruino/blob/master/README_BuildProcess.md

Parsing

There is no bytecode, so execution happens inside the parser in jsparse.c

The parser is a hand-written recursive-descent parser, and code to be executed is executed directly from variables (see below), not from a C-style string or flat buffer. The Performance page has more information on this.

Variable Storage

Variables are usually stored in fixed-size blocks defined by the JsVar data type. The size of them depends on how many variables need to be addressed. In small devices JsVar can get down to 12 bytes (10 bit addresses), but a device like Bangle.js 2 JsVar will have 14 byte blocks, and larger devices may use 16 bytes or more.

You can check the size of JsVar using process.memory().blocksize from within Espruino

Using fixed sized blocks has several implications:

Each block has optional links to children and siblings, which allows a tree structure to be built. However in many cases these references aren't needed and can be used to store other data.

What follows are the basic variable fields and offsets for 16 byte variable:

| Offset | Size | Name | STRING | STR_EXT | NAME_STR | NAME_INT | INT | DOUBLE | OBJ/FUNC/ARRAY | ARRAYBUFFER | NATIVE_STR | FLAT_STR | | 16b | | | | | | | | | | | FLASH_STR | | |--------|------|---------|--------|----------|----------|----------|------|---------|----------------|-------------|------------|----------| | 0 - 3 | 4 | varData | data | data | data | data | data | data | nativePtr | size | ptr | charLen | | 4 - 5 | ? | next | data | data | next | next | - | data | argTypes | format | len | - | | 6 - 7 | ? | prev | data | data | prev | prev | - | data | argTypes | format | ..len | - | | 8 - 9 | ? | first | data | data | child | child | - | data? | first | stringPtr | ..len | - | | 10-11 | ? | refs | refs | data | refs | refs | refs | refs | refs | refs | refs | refs | | 12-13 | ? | last | nextPtr| nextPtr | nextPtr | - | - | - | last | - | - | - | | 14-15 | 2 | Flags | Flags | Flags | Flags | Flags | Flags| Flags | Flags | Flags | Flags | Flags |

On platforms where the variable size is under 16 bytes, some elements may not be byte-aligned, for example prev, first and last

Note: NAME_INT_INT, NAME_INT_BOOL, and NAME_STRING_INT follow the same pattern as NAME_STR/NAME_INT - they just use child to store a value rather than a reference.

Garbage Collection, Reference Counts and Locks

Espruino contains a mark/sweep garbage collector as well as reference counting.

Garbage Collection is only needed when objects contain circular references. For the vast majority of allocations/deallocations reference counting can be used.

In Espruino, each JsVar contains:

In most cases a variable can be freed without a GC pass. It will be allocated, used, and then when finally unlocked, if the reference count is zero then it and all its children will be freed.

When Garbage Collecting, Espruino will sweep over all variables in memory setting the JSV_GARBAGE_COLLECT bit unless they are locked. It will then traverse all children of non-garbage collected variables and clear the JSV_GARBAGE_COLLECT bit on them, and finally anything still with the JSV_GARBAGE_COLLECT bit set will be freed.

When writing C code to run inside Espruino the rules are reasonably straightforward:

This page is auto-generated from GitHub. If you see any mistakes or have suggestions, please let us know.