User:Waldo/JavaScript object layout/Old object layout

From MozillaWiki
Jump to: navigation, search

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.

Basic fields

The very basic semantics of these fields are as follows.

shape

shape stores information about object properties, as a linked list of Shapes, 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 Shapes (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 per Shape in the list. Shapes also contain a pointer to a base shape in shape->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 in obj->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 a void* 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), ArrayBuffers, 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.