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.
Espruino's Makefile calls a few different Python scripts that precompute various things:
platform_config
file - with information about the current system (how many of each peripheral, what pin buttons + LEDs are on, etc)pininfo
source file - listing what peripherals are on what pinsjswrap_
files (looking for JSON-formatted comments above each function), and create:jswrapper.c
file containing a symbol table and calls to the functions that need to be executedMuch more detailed info is in the Espruino repository at https://github.com/espruino/Espruino/blob/master/README_BuildProcess.md
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.
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:
O(1)
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.
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:
jsvLock
and decrease with jsvUnLock
JsVar*
will return a locked variable. When JsVar*
is passed as an argument
it's usually the caller's responsibility to unlock the variable, unless the function's name is something like js....AndUnLock
jsvRef
and decrease with jsvUnRef
but generally you never have to do this. If you're using jsvAddChild/etc
then they handle references for youjsvDefrag
to help avoid fragmentationStringExt
- this is a part of a string that gets added
on the end with more characters, so it's always assumed that it's only referenced once (by the `String`` it is part of)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:
jsv*
functions for adding/removing children and setting values and you won't have to worry about using jsvRef
/jsvUnRef
JsVar*
in pretty much all cases you'll be responsible for calling jsvUnLock
on itJsVar*
, unless it's called something like js....AndUnLock
, you're still responsible for freeing the variableJsVar
structures, and if sensibe avoid even having JsVarRef
. However if you do keep them you need to
ensure they are locked/referenced as appropriate, and that you have a jswrap_..._kill
handler that will unlock/reference them when the interpreter needs to be torn down.This page is auto-generated from GitHub. If you see any mistakes or have suggestions, please let us know.