This PR implements an optimization done by V8 and spidermonkey. Which stores indexed properties in two class storage methods dense and sparse. The dense method stores the property in a contiguous array ( `Vec<T>` ) where the index is the property key. This storage method is the default. While on the other hand we have sparse storage method which stores them in a hash map with key `u32` (like we currently do). This storage method is a backup and is slower to access because we have to do a hash map lookup, instead of an array access.
In the dense array we can store only data descriptors that have a value field and are `writable`, `configurable` and `enumerable` (which by default array elements are). Since all the fields of the property descriptor are the same except value field, we can omit them an just store the `JsValue`s in `Vec<JsValue>` this decreases the memory consumption and because it is smaller we are less likely to have cache misses.
There are cases where we have to convert from dense to sparse (the slow case):
- If we insert index elements in a non-incremental way, like `array[1000] = 1000` (inserting an element to an index that is already occupied only replaces it does not make it sparse)
- If we delete from the middle of the array (making a hole), like `delete array[10]` (only converts if there is actualy an element there, so calling delete on a non-existent index property will do nothing)
Once it becomes sparse is *stays* sparse there is no way to convert it again. (the computation needed to check whether it can be converted outweighs the benefits of this optimization)
I did some local benchmarks and on array creation/pop and push there is ~45% speed up and the impact _should_ be bigger the more elements we have. For example the code below on `main` takes `~21.5s` while with this optimization is `~3.5s` (both on release build).
```js
let array = [];
for (let i = 0; i < 50000; ++i) {
array[i] = i;
}
```
In addition I also made `Array::create_array_from_list` do a direct setting of the properties (small deviation from spec but it should have the same behaviour), with this #2058 should be fixed, conversion from `Vec` to `JsArray`, not `JsTypedArray` for that I will work on next :)
/// The index properties can be stored in two storage methods:
/// - `Dense` Storage
/// - `Sparse` Storage
///
/// By default it is dense storage.
#[derive(Debug, Trace, Finalize)]
enumIndexedProperties{
/// Dense storage holds a contiguous array of properties where the index in the array is the key of the property.
/// These are known to be data descriptors with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`.
///
/// Since we know the properties of the property descriptors (and they are all the same) we can omit it and just store only
/// the value field and construct the data property descriptor on demand.
///
/// This storage method is used by default.
Dense(Vec<JsValue>),
/// Sparse storage this storage is used as a backup if the element keys are not continuous or the property descriptors
/// are not data descriptors with with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`.
///
/// This method uses more space, since we also have to store the property descriptors, not just the value.
/// It is also slower because we need to to a hash lookup.