The VM uses a class called
HermesValue to encapsulate JS values efficiently,
preserving their type while still allowing them to fit in a register.
NaN-tagging is used to store different types of values;
we store values in the lower bits of a
Thus, when the
uint64_t is interpreted as a
NaN values can hold non-
StringPrimitive is used to store immutable UTF16 encoded strings,
StringPrimitive * can be stored in
HermesValue to make JS String values.
StringPrimitive can be
DynamicStringPrimitive(stored in the GC heap)
ExternalStringPrimitive(stored as a pointer outside the VM, such as into a bytecode file)
Runtime class is the primary driver of the VM.
It contains the current environment and heap, as well as the code to execute.
Runtime is used to execute
which are constructed from
RuntimeModule is the VM representation into a bytecode file.
RuntimeModules are stored outside the GC heap and are constructed via
To allow for segmentation of bytecode files and
requireing modules between
separate segments, we collect
RuntimeModules in a class called
You may think of the
Domain as the collection of bytecode files which were
all compiled in the same invocation of the compiler.
JSFunction shares ownership of a
Domain, and the
RuntimeModules which provide those functions. In this way, when all
JSFunctions which require the files in a
Domain are collected,
Domain and the
RuntimeModules are also collected.
Runtime contains an
which is used for getting unique IDs for strings.
The table is used to go from
SymbolID and back.
It's prepopulated with some "predefined strings",
the set of strings that are required by built in functions,
which can be seen in
Currently, the VM uses
GenGCNC (generational non-contiguous GC).
The collector allows non-contiguous heap allocation.
This avoids preallocating too much memory, as well as returning memory to the OS.
The garbage collector is precise
(it knows what
HermesValues are valid pointers to objects in the JS heap).
TODO: Elaborate on the garbage collector requirements and future plans.
The garbage collector moves objects to different place on the heap,
so there are a couple classes which allow updating them automatically.
Handle<T> are garbage collector-aware handles;
they are moved if a collection occurs in between two successive accesses.
So, to ensure correctness in the VM,
use the handles instead of passing raw
HermesValue between functions.
GCScope is used to keep track of all the current
GCScope must be constructed on the stack,
whence it tracks any scoped handles that are used until it falls out of scope.
GCScope allocates space in chunks,
and when it is destroyed (falls out of scope) it frees any chunks it allocated.
GCScope is used to internally generate
which are then stored in
We also provide
PseudoHandle<T> classes which are explicitly not handles.
These are used to be explicit about storage of raw pointers and
PseudoHandle should be used as an argument in place of a raw pointer to
functions which may want to turn that argument into a
but in which it's not necessary to always incur the cost of handle allocation.
PseudoHandle also does not have a copy constructor,
and moving out of one invalidates it.
This prevents the reuse of
PseudoHandle after an allocating function call.
- A function that can perform an allocation (even if it doesn't do it every
time) or calls a function that does, must accept and return only handles
(for GC-managed objects). It must also take a
Runtime*as an argument.
- A function that accepts or returns handles is allowed (and can be assumed to) allocate more handles, but the upper bound of allocated handles must be static.
- The number of handles in a given GCScope should have a static upper limit.
The motivation for these rules should be self-explanatory. The practical implication of rule 2 and 3 is that recursion and loops that allocate handles in every iteration must be treated specially. In case of recursion a new GCScope should be defined in each recurrence (is that the correct term?). In case of a loop, there are a couple of possibilities:
- in loops that are expected to be low iteration and not performance critical, a new GCScope can be defined in the body of the loop.
- otherwise a GCScope::Marker should be used to flush the allocated handles of the previous iteration.
- mutable handles can be used to avoid allocating a new handle on every iteration.
Currently the object model is a VTable-based scheme,
in which all possible JS values inherit from a base garbage collector VTable.
These are called "cells", and all the cells are defined in
Objects have a special
ObjectVTable, Callables have a
Each JS object is represented by
Object (or a class derived from
JS objects have a set of name/value pairs, and some optional "indexed storage".
Read more about how
Object works in
The Runtime contains a global object which is used to store in global scope.
arguments object, etc. inherit from Object directly,
but simply provide their own implementations of
*OwnIndexed using the VTable.
Functions and native functions inherit from
This allows them to call
executeCall* to run functions using the internal API.
PrimitiveBox class is used to contain Booleans, Strings, and Numbers,
when they are constructed using their respective JS constructors.
JSString is a
PrimitiveBox that is used for
String objects, etc.
The HermesVM provides a REPL in
which calls through to the
eval() global function in the