Object Model and Hidden Classes
Relevant source files
The following files were used as context for generating this wiki page:The Jurassic engine's object model is a sophisticated system designed to represent JavaScript objects within the .NET environment while achieving high performance. The core of this system is the ObjectInstance class, which serves as the base for all JavaScript objects. To optimize property access, a common bottleneck in dynamic languages, Jurassic employs a technique known as "hidden classes" (or "shapes"), implemented through the HiddenClassSchema class.
This model provides the flexibility of JavaScript's dynamic objects—where properties can be added or removed at any time—with performance approaching that of statically-typed languages. It achieves this by treating objects with the same structure as instances of a shared, underlying "hidden class," allowing for optimized property lookups.
Core Architecture
The object model revolves around two primary classes: ObjectInstance and HiddenClassSchema. Every JavaScript object created by the engine is a .NET object that derives from ObjectInstance.
Sources: Jurassic/Library/Object/ObjectInstance.cs, Jurassic/Library/Object/HiddenClassSchema.cs
ObjectInstance Class
ObjectInstance is the fundamental building block for all objects in the Jurassic runtime. It manages an object's prototype, properties, and values.
The following diagram shows the key relationships in the object model.
This diagram illustrates that the ScriptEngine creates ObjectInstances and holds the initial empty HiddenClassSchema. Each ObjectInstance has a schema, a prototype, and is associated with an engine. The schema, in turn, contains multiple SchemaProperty definitions.
Sources: Jurassic/Core/ScriptEngine.cs:23, Jurassic/Library/Object/ObjectInstance.cs:13-29, Jurassic/Library/Object/HiddenClassSchema.cs:10
The key internal fields of an ObjectInstance are:
| Field | Type | Description |
|---|---|---|
engine | ScriptEngine | A reference to the script engine instance that owns the object. |
prototype | ObjectInstance | The next object in the prototype chain. This is null for the root object. |
schema | HiddenClassSchema | A reference to the hidden class that defines the object's structure (its "shape"). |
propertyValues | object[] | A simple array that stores the actual values of the object's properties. |
flags | ObjectFlags | A bitfield that stores metadata, primarily whether the object is Extensible. |
Sources: Jurassic/Library/Object/ObjectInstance.cs:13-29
HiddenClassSchema Class
A HiddenClassSchema represents the structure of an ObjectInstance. Instead of storing property names in a dictionary for every object, objects with the same set of properties (in the same order and with the same attributes) share a single HiddenClassSchema. The schema maps a property name to an index in the ObjectInstance's propertyValues array.
Key components include:
properties: A dictionary mapping a property key (string or Symbol) to aSchemaProperty, which holds the property's index and attributes.addTransitions/deleteTransitions/modifyTransitions: Dictionaries that cache transitions to new schemas when a property is added, deleted, or its attributes are changed. This is the core of the dynamic optimization.nextValueIndex: The total number of properties in the schema, which is also the index for the next property to be added.
Sources: Jurassic/Library/Object/HiddenClassSchema.cs:13-25
SchemaProperty Struct
This struct holds the metadata for a single property within a schema.
| Field | Type | Description |
|---|---|---|
Index | int | The index of the property's value within the ObjectInstance.propertyValues array. |
Attributes | PropertyAttributes | A bitfield enum describing the property's characteristics (e.g., Writable, Enumerable, Configurable). |
Sources: Jurassic/Library/Object/SchemaProperty.cs:10-20
Schema Transitions
When a property is added to or removed from an object, or its attributes are modified, the object's shape changes. Instead of altering the existing HiddenClassSchema (which may be shared by other objects), the system transitions the object to a new schema.
The HiddenClassSchema class maintains transition caches (addTransitions, deleteTransitions, modifyTransitions) to quickly find an existing schema for a new shape. If a schema for the target shape doesn't exist, a new one is created and cached for future use.
Sources: Jurassic/Library/Object/HiddenClassSchema.cs:17-19, Jurassic/Library/Object/HiddenClassSchema.cs:128-251
The following diagram illustrates how schemas transition as properties are manipulated.
This diagram shows that starting from an empty schema, adding properties (x, y, z) creates a chain of new schemas. Deleting properties or changing their attributes also causes transitions to different schemas.
Sources: Jurassic/Library/Object/HiddenClassSchema.cs:128-251
The main methods responsible for these transitions are:
AddProperty(object key, PropertyAttributes attributes)DeleteProperty(object key)SetPropertyAttributes(object key, PropertyAttributes attributes)
Each of these methods returns a new HiddenClassSchema instance representing the object's new shape.
Property Management and Caching
ObjectInstance provides a suite of methods for getting, setting, and defining properties. These methods leverage the hidden class system for performance.
Property Lookup (GetPropertyValue)
When GetPropertyValue is called, it first checks the object's schema for the property.
- If the property exists in the schema, its value is retrieved directly from the
propertyValuesarray using the index stored in theSchemaProperty. - If the property is an accessor, the getter function is invoked.
- If the property is not found on the object itself, the lookup continues up the
prototypechain.
Sources: Jurassic/Library/Object/ObjectInstance.cs:302-343
Property Modification (SetPropertyValue)
Setting a property is more complex due to the dynamic nature of schema transitions.
- The system first checks if the property already exists on the object.
- If it exists and is a writable data property, the value is updated in the
propertyValuesarray. No schema change occurs. - If it is an accessor property, the setter function is invoked.
- If the property does not exist on the object, the system checks if the object is extensible.
- If it is,
AddPropertyis called, which transitions the object to a newHiddenClassSchemaand stores the new value.
Sources: Jurassic/Library/Object/ObjectInstance.cs:433-495
Fast Path Caching with PropertyReference
For frequently accessed properties, the JIT compiler uses a PropertyReference object to further optimize lookups. This object caches the HiddenClassSchema and the property's index.
The GetPropertyValue(PropertyReference) and SetPropertyValue(PropertyReference) methods implement a "fast path":
- Fast Path: If the
ObjectInstance's current schema matches theCachedSchemain thePropertyReference, the value can be accessed or updated directly in thepropertyValuesarray using theCachedIndex. This avoids any dictionary lookups. - Slow Path: If the schemas do not match (meaning the object's shape has changed), the cache is invalidated, and a full property lookup is performed. If the property is found and is a simple data property, the
PropertyReferenceis updated with the new schema and index for future fast-path access.
Sources: Jurassic/Library/Object/ObjectInstance.cs:345-381, 509-550, Jurassic/Compiler/Emit/OptimizationInfo.cs:113-132
The following sequence diagram shows the property access flow with PropertyReference.
Conclusion
The Jurassic object model, centered around ObjectInstance and HiddenClassSchema, is a powerful implementation of JavaScript's dynamic property system. By using hidden classes, schema transitions, and JIT-level property caching, the engine provides an efficient mechanism for property access that adapts to the runtime changes of an object's shape. This architecture is crucial for achieving high performance while maintaining compliance with the ECMAScript standard.