User:Waldo/JavaScript object layout:Old
In the old system, objects are represented using the following basic layout:
class ObjectImpl { Shape* shape; TypeObject* type; Value* slots; Value* elements; };
For efficiency sometimes objects have extra memory allocated immediately after them as a "fused" allocation to store property values.
Contents
Basic fields
The very basic semantics of these fields are as follows.
shape
shape
stores information about object properties, as a linked list of Shape
s, ending with an EmptyShape
. Each Shape
stores information for a single property. Some data is largely the same for large numbers of Shape
, so the conceptual data for a property is split into that stored in a Shape
and that stored in a BaseShape
, with Shape::base_
(accessed by Shape::base()
) pointing to that base shape. A Shape
can be shared amongst many objects, and a BaseShape
can be shared amongst many Shape
s (and therefore amongst many objects).
type
type
stores information for the type inference algorithm, also the object's [[Prototype]]
.
slots
slots
is a pointer to a property value storage vector. It is used only by native objects (see below).
elements
elements
is akin to slots
, but for element properties. It's only used for dense arrays right now (see below).
Ancillary data
Objects encapsulate various other key constructs which can be derived from these fields with enough effort.
- Base shape
-
shape
is a linked list encoding object properties, one perShape
in the list. Shapes also contain a pointer to a base shape inshape->base()
. Base shapes have two distinct purposes: to track data common among many shapes (getter, setter), and to track data particular to all objects whose last property shares a base shape (class, parent, object flags, and slot-counting information). - Class
- This is a
Class*
that roughly corresponds to ECMAScript's[[Class]]
internal property. It includes various data on how the object functions, including how various operations upon it work. The class is stored inobj->lastProperty()->base()->clasp
, so a couple hops away from the object structure. - Ops
- This is a function pointer table, stored in the class, that describes how various operations on objects of that class work (getting a property, defining one, etc.). It's one further step away from the object in the class, of course.
- Object flags
- Objects occasionally need to store various bits, encoding things like extensibility (whether new properties can be added to it), whether the object's being used as a delegate (either a parent [in the JSAPI sense] or a prototype), whether it's an object where variable bindings are stored, and other things. These object flags are stored in the
BaseShape
of the object's last shape (i.e.obj->shape->base()
). Keeping these flags always correct across property additions and deletions and across modifications to the last property requires some care — particularly considering that shapes (and therefore base shapes) can be shared by different objects. - Private data
- Objects whose class includes the
JSCLASS_HAS_PRIVATE
flag store avoid*
in the first slot after the end of all fixed slots. - Reserved slots
- Slots primarily hold property values, but a class of objects may also store arbitrary values in a few leading slots using
JSCLASS_HAS_RESERVED_SLOTS(n)
.
Object subtypes
The object layout is interpreted in accordance with three distinct, latent subtypes: dense arrays, native objects, and non-native objects.
Depending on how the object is allocated, this basic structure may be followed by some number of additional sizeof(Value)-sized locations. This extra memory is then used in different ways depending upon the object's latent subtype.
Dense arrays
Arrays which have never had named properties (that is, properties whose characters don't encode a uint32_t
— but excluding the length
property), where all indexed properties have the default property attributes (enumerable, configurable, writable) are dense arrays, represented by the DenseArray
class. Dense arrays store all their properties in elements
.
The elements
pointer is associated via pointer arithmetic with an ObjectElements
structure with this layout:
class ObjectElements { uint32_t capacity; uint32_t initializedLength; uint32_t length; uint32_t _; /* padding */ };
The length
property of an array is stored in ObjectElements::length
. The DenseArray
class specially retrieves the length
value from this location when requested. The length
property's attributes cannot be changed: it is always non-enumerable, non-configurable, and writable (an ES5 spec violation).
Array elements are stored as follows. Any element >= initializedLength
is a hole -- that is, the property doesn't exist at all, and lookups will fall through to the prototype chain. (It's always the case that length >= initializedLength
.) Otherwise, an element at index i
< initializedLength
is stored in elements[i]
. If elements[i]
is MagicValue(JS_ARRAY_HOLE)
, it represents a hole. (Note that JS_ARRAY_HOLE
is a debug-only artifact; all distinct magic values are coalesced into one in optimized builds.) Otherwise that element is present with that value.
ObjectElements
fields always have these relationships:
-
capacity >= initializedLength
: capacity records available space for element storage without reallocation; initialized length records how many elements of capacity have values -
initializedLength <= length
: assigning above the current length updates the initialized length (and initializes unassigned elements), and decreasing length decreases the initialized length
A dense array is transformed into a native object if the attributes of a property are changed, if a named property is added, or if an indexed property is added that makes the array "empty enough". This transition is one-way: an object that is not a dense array can never become one.
Non-native objects
(Note: At the implementation level, dense arrays are a kind of non-native object. But SpiderMonkey encodes so much dense-array-specific knowledge into the VM that dense arrays are more not native than non-native specifically. Excepting this paragraph, this summary will not refer to dense arrays as non-native.)
Non-native objects are either typed arrays ({Ui,I}nt{8,16,32}Array
and Float{32,64}Array
), ArrayBuffer
s, or proxies. Non-native objects have a corresponding class which defines how various operations upon them function via js::Class::ops
. Non-native objects sometimes use fused slots allocated after the object (which should be accessed using setFixedSlot
, although this isn't always done). Sufficiently small array buffers and typed arrays, for example, store their data inline when possible, with varying memory layouts. (For example, array buffers use fused storage to store an ObjectElements
instance, from which obj->elements
is derived.) Non-native objects don't use obj->slots
for actual properties. Array buffers use obj->elements
with a special kind of ObjectElements
where ObjectElements::length
is the array buffer's byte length and obj->elements
itself is a pointer to the actual bytes. Other non-native objects don't use obj->elements
.
Native objects
All remaining JavaScript objects are native objects. All properties are represented in the sequence of shapes corresponding to obj->shape
. All property values are stored in obj->slots
. obj->elements
is entirely unused. Native objects may use fused storage, although adding properties in excess of the fused storage necessarily allocates additional memory for the extra properties.