docs/objects/fields-and-elements.md
This document explains how V8 optimizes object properties and array elements by specializing their storage representation based on the types of values they contain.
JavaScript is dynamically typed, but V8 attempts to treat objects as if they had static types to improve performance. Two key mechanisms for this are Field Representations (for named properties) and Elements Kinds (for indexed properties/array elements).
When properties are stored in the Descriptor Array of a Map, V8 tracks the Representation of each data property. This allows V8 to avoid boxing numbers or storing full pointers when not necessary.
Defined in src/objects/property-details.h, the main representations are:
kSmi: The property always holds a Small Integer (Smi). Stored directly in the object without allocation.kDouble: The property holds a double-precision float. It is stored as a boxed HeapNumber (in-object unboxing is no longer supported). Tracking this representation allows V8 to avoid type checks. Furthermore, the boxed number is allowed to be mutated in-place on store (normally HeapNumbers are immutable), avoiding allocation. However, reading it requires a copy unless in optimized code that can handle raw float64 values.
kHeapObject: The property always holds a reference to a heap object. It can store a specific "field type" in the property descriptor (i.e., the expected Map of the field), or it can be a generic non-Smi HeapObject.kWasmValue: Used for WasmObject fields. It indicates that the actual field type information must be taken from the Wasm RTT (Runtime Type) associated with the map.kTagged: The most general representation. It can hold any valid JavaScript value (Smi or HeapObject).kNone: Uninitialized property.Every property has an associated PropertyDetails value (a 32-bit integer) that packs:
V8 packs this information tightly into a 32-bit integer. The layout differs between fast mode (using descriptor arrays) and slow mode (dictionary properties).
For Fast Mode Properties:
For Dictionary Mode Properties:
This bit-packing allows V8 to pass property metadata efficiently and perform quick checks using bitmasks.
It is important to distinguish between Generalization (representation changes) and Transitions (adding fields):
kSmi).If a property is initialized as a Smi and later assigned a Double, V8 will generalize the representation. This may require Map Deprecation (marking the old map as invalid for new objects) and creating a new map with the generalized representation. Objects with the old deprecated map are not updated immediately; instead, they are lazily migrated to the new map when they are next accessed or mutated. V8 cannot generalize "backwards" to a more specific representation.
[!NOTE] A field representation change (generalization) is one of the ways to trigger a Lazy Deoptimization in optimized code that relied on the more specific representation. See Deoptimization for details.
Some representation changes can be done in-place without deprecating the map. Specifically, generalizing from
Smi,Double, orHeapObjecttoTaggedcan be done in-place. However, changing representation fromSmitoDoublerequires deprecation because doubles might require a box allocation (e.g.,HeapNumber).
When V8 allocates a new object instance, it often allocates more space than currently needed for properties (in-object slack).
In addition to representation, V8 tracks where a property's value is stored, defined by PropertyLocation in src/objects/property-details.h:
kField: The value is stored in the object instance itself.
JSObject memory layout at a fixed offset.PropertyArray pointed to by the object.kDescriptor: The value is stored in the Descriptor Array attached to the Map.
AccessorPairs) are typically stored here. All instances share the same accessor function references.By combining PropertyKind (Data vs. Accessor) and PropertyLocation (Field vs. Descriptor), V8 can represent:
For indexed properties (arrays), V8 uses Elements Kinds to specialize the backing store (FixedArray or FixedDoubleArray) and optimize operations like map, reduce, and forEach.
Defined in src/objects/elements-kind.h, the most common "fast" kinds are:
PACKED_SMI_ELEMENTS: Array contains only Smis and has no holes. Backed by a FixedArray.HOLEY_SMI_ELEMENTS: Contains only Smis but has missing indices (holes).PACKED_DOUBLE_ELEMENTS: Contains only unboxed doubles. Backed by a FixedDoubleArray. Highly efficient for numerical work.HOLEY_DOUBLE_ELEMENTS: Contains unboxed doubles but has holes.PACKED_ELEMENTS: Contains arbitrary JS objects (tagged values). Backed by a FixedArray.HOLEY_ELEMENTS: Contains arbitrary JS objects and has holes.For completeness, V8 also defines elements kinds for special cases:
PACKED_FROZEN_ELEMENTS, HOLEY_SEALED_ELEMENTS).FAST_SLOPPY_ARGUMENTS_ELEMENTS and SLOW_SLOPPY_ARGUMENTS_ELEMENTS are used for arguments objects in sloppy mode.FAST_STRING_WRAPPER_ELEMENTS and SLOW_STRING_WRAPPER_ELEMENTS are used for string wrapper objects.When an array becomes very sparse or has a large number of elements, V8 may switch from a flat backing store (FixedArray or FixedDoubleArray) to a dictionary-based representation (NumberDictionary).
DICTIONARY_ELEMENTS: Elements are stored in a hash table. This saves memory for sparse arrays but makes access slower.0 to length-1 is initialized. Accessing elements is direct and fast.Elements kinds only transition from more specific to more general through a lattice. Once transitioned, they rarely go back:
PACKED_SMI -> PACKED_DOUBLE -> PACKED_ELEMENTSPACKED kind can transition to its HOLEY counterpart.[!NOTE] While conceptually elements kinds form a lattice of generalization, V8's implementation in the transition tree linearizes transitions for fast elements kinds to keep the tree simple and avoid path explosion. The linear sequence used is:
PACKED_SMI->HOLEY_SMI->PACKED_DOUBLE->HOLEY_DOUBLE->PACKED_ELEMENTS->HOLEY_ELEMENTSThis means V8 may create intermediate transition maps (e.g., creating aHOLEY_SMImap when transitioning fromPACKED_SMItoPACKED_DOUBLE) even if they are not strictly needed for the final representation.
Examples of Transitions:
const array = [1, 2, 3]; // PACKED_SMI_ELEMENTS
array.push(4.56); // Transitions to PACKED_DOUBLE_ELEMENTS
array.push('x'); // Transitions to PACKED_ELEMENTS
array[9] = 1; // Transitions to HOLEY_ELEMENTS (indices 5-8 are holes)
src/objects/property-details.h: Representation and PropertyDetails definitions.src/objects/elements-kind.h: ElementsKind enum and helper predicates.