Description
Object shapes should at the very least contain the following pieces of information:
- Object prototype (needed for prototype chain property caches)
- Object keys data (index into a keys array)
Possible additions into/removals from the shape:
- Extensible bit
- Key descriptors (writable, enumerable, configurable, getters, setters)
- Capacity of object property storage
- Is used as prototype
Storing descriptors in shape (or not)
If descriptors are part of shapes, the number of shapes in the engine increases. It comes with benefits: unconfigurable, unwritable properties can be cached to the value directly, getter and setter functions can be cached directly. V8-style "property is writable but is never written" eager optimisations also become possible. But the shape transition scheme also becomes much more involved, as it needs to track descriptor changes instead of only key changes. Whether it makes sense to increase the complexity of the shape transition scheme for these benefits is not easy to decide without trying it out.
Storing capacity in shape (or not)
Moving capacity of keys/values away from the shape may sound weird, as it means that the shape alone no longer knows how it should be traced, but it's not so weird if the capacity is moved to be a part of the shape index itself. The shape index is a u32 but we can pretty safely assume that 16 million shapes is an unlikely situation for the engine, so giving the top byte or a bit less from the index would not cause any issues. The top byte can then be used to store the capacity value. This saves at least one byte per shape, but more likely something like 4 bytes per shape. The extensible bit can likewise be stored in the top byte this way.
Prototype usage in shape
When prototype chain lookups are performed, we'd want to store the result by-value if at all possible. eg. performing ({}).hasOwnProperty("foo")
would preferably remember that the hasOwnProperty
property of {}
was found in a prototype and had the value BuiltinFunction(182)
or such. This way, when next we check {}
's hasOwnProperty
we can simply compare the shapes and if they are equal, then return the value directly.
But now if someone goes to Object.prototype
and assigns some other function into hasOwnProperty
, we'd somehow need to invalidate the property cache. This is hard to do as we might not have a way to find all shapes that use Object.prototype
in their prototype chain, or if we do we might not have a way to find all property caches that rely on hasOwnProperty
. The same issue crops up if we have a property cache for obj[3]
and someone assigns Object.prototype[3] = 5
: previously the property cache would've likely said "undefined" but now it should invalidate.
For these reasons, it's probably going to be useful to have information on if a shape is used as prototype. If it is not used as a prototype, then we do not need to perform any property cache fixing upon property assignment, but if it is then we do need to start doing invalidations.