From 553fdb86ecd628f74718fcec37ec26147647ba8e Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Tue, 29 Aug 2023 19:41:59 +0300 Subject: [PATCH] Improve array access performance (#1625) * always have JsValue in array and use sparse as helper to remove type checks * faster paths for HasProperty against arrays * remove JsArray constructor taking PropertyDescriptor[] --- .../RavenApiUsageTests.cs | 28 - Jint/Native/Array/ArrayInstance.cs | 562 +++++++++--------- Jint/Native/Array/ArrayOperations.cs | 23 +- Jint/Native/Array/ArrayPrototype.cs | 4 +- Jint/Native/JsArray.cs | 13 - Jint/Native/Object/ObjectInstance.cs | 5 +- Jint/Runtime/TypeConverter.cs | 25 +- 7 files changed, 303 insertions(+), 357 deletions(-) diff --git a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs index 5143642fa3..9ceb48f621 100644 --- a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs +++ b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs @@ -3,7 +3,6 @@ using Jint.Native; using Jint.Native.Function; using Jint.Runtime; -using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Tests.PublicInterface; @@ -57,20 +56,6 @@ public void CanChangeMaxStatementValue() Assert.Equal(321, constraint.MaxStatements); } - [Fact] - public void CanConstructArrayInstanceFromDescriptorArray() - { - var descriptors = new[] - { - new PropertyDescriptor(42, writable: false, enumerable: false, configurable: false), - }; - - var engine = new Engine(); - var array = new JsArray(engine, descriptors); - Assert.Equal(1L, array.Length); - Assert.Equal(42, array[0]); - } - [Fact] public void CanGetPropertyDescriptor() { @@ -97,15 +82,6 @@ public void CanInjectConstructedObjects() TestArrayAccess(engine, array1, "array1"); - var array3 = new JsArray(engine, new[] - { - new PropertyDescriptor(JsNumber.Create(1), true, true, true), - new PropertyDescriptor(JsNumber.Create(2), true, true, true), - new PropertyDescriptor(JsNumber.Create(3), true, true, true), - }); - engine.SetValue("array3", array3); - TestArrayAccess(engine, array3, "array3"); - engine.SetValue("obj", obj); Assert.Equal("test", engine.Evaluate("obj.name")); @@ -117,10 +93,6 @@ public void CanInjectConstructedObjects() Assert.Equal(0, engine.Evaluate("emptyArray.length")); Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length")); - engine.SetValue("emptyArray", new JsArray(engine, Array.Empty())); - Assert.Equal(0, engine.Evaluate("emptyArray.length")); - Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length")); - engine.SetValue("date", new JsDate(engine, new DateTime(2022, 10, 20))); Assert.Equal(2022, engine.Evaluate("date.getFullYear()")); } diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index f81b528179..1a75e47145 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -15,34 +15,30 @@ public class ArrayInstance : ObjectInstance, IEnumerable private const int MaxDenseArrayLength = 10_000_000; // we have dense and sparse, we usually can start with dense and fall back to sparse when necessary - // entries are lazy and can be either of type PropertyDescriptor or plain JsValue while there is no need for extra info - internal object?[]? _dense; - private Dictionary? _sparse; + // when we have plain JsValues, _denseValues is used - if any operation occurs which requires setting more property flags + // we convert to _sparse and _denseValues is set to null - it will be a slow array + internal JsValue?[]? _dense; + + private Dictionary? _sparse; private ObjectChangeFlags _objectChangeFlags; - private bool _isObjectArray = true; private protected ArrayInstance(Engine engine, InternalTypes type) : base(engine, type: type) { - _dense = System.Array.Empty(); + _dense = System.Array.Empty(); } private protected ArrayInstance(Engine engine, uint capacity = 0, uint length = 0) : base(engine, type: InternalTypes.Object | InternalTypes.Array) { - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; - - if (capacity > engine.Options.Constraints.MaxArraySize) - { - ThrowMaximumArraySizeReachedException(engine, capacity); - } + InitializePrototypeAndValidateCapacity(engine, capacity); if (capacity < MaxDenseArrayLength) { - _dense = capacity > 0 ? new object?[capacity] : System.Array.Empty(); + _dense = capacity > 0 ? new JsValue?[capacity] : System.Array.Empty(); } else { - _sparse = new Dictionary(1024); + _sparse = new Dictionary(1024); } _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable); @@ -50,42 +46,20 @@ private protected ArrayInstance(Engine engine, uint capacity = 0, uint length = private protected ArrayInstance(Engine engine, JsValue[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) { - Initialize(engine, items); - } - - private protected ArrayInstance(Engine engine, PropertyDescriptor[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) - { - Initialize(engine, items); - } + InitializePrototypeAndValidateCapacity(engine, capacity: 0); - private protected ArrayInstance(Engine engine, object[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) - { - Initialize(engine, items); + _dense = items; + _length = new PropertyDescriptor(items.Length, PropertyFlag.OnlyWritable); } - private void Initialize(Engine engine, T[] items) where T : class + private void InitializePrototypeAndValidateCapacity(Engine engine, uint capacity) { - if (items.Length > engine.Options.Constraints.MaxArraySize) - { - ThrowMaximumArraySizeReachedException(engine, (uint) items.Length); - } - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; - _isObjectArray = typeof(T) == typeof(object); - int length; - if (items == null || items.Length == 0) + if (capacity > 0 && capacity > engine.Options.Constraints.MaxArraySize) { - _dense = System.Array.Empty(); - length = 0; - } - else - { - _dense = items; - length = items.Length; + ThrowMaximumArraySizeReachedException(engine, capacity); } - - _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable); } public sealed override bool IsArrayLike => true; @@ -133,149 +107,155 @@ internal bool CanUseFastAccess public sealed override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { + if (property == CommonProperties.Length) + { + return DefineLength(desc); + } + var isArrayIndex = IsArrayIndex(property, out var index); TrackChanges(property, desc, isArrayIndex); if (isArrayIndex) { + ConvertToSparse(); return DefineOwnProperty(index, desc); } - if (property == CommonProperties.Length) + return base.DefineOwnProperty(property, desc); + } + + private bool DefineLength(PropertyDescriptor desc) + { + var value = desc.Value; + if (ReferenceEquals(value, null)) { - var value = desc.Value; - if (ReferenceEquals(value, null)) - { - return base.DefineOwnProperty(CommonProperties.Length, desc); - } + return base.DefineOwnProperty(CommonProperties.Length, desc); + } - var newLenDesc = new PropertyDescriptor(desc); - uint newLen = TypeConverter.ToUint32(value); - if (newLen != TypeConverter.ToNumber(value)) - { - ExceptionHelper.ThrowRangeError(_engine.Realm); - } + var newLenDesc = new PropertyDescriptor(desc); + uint newLen = TypeConverter.ToUint32(value); + if (newLen != TypeConverter.ToNumber(value)) + { + ExceptionHelper.ThrowRangeError(_engine.Realm); + } - var oldLenDesc = _length; - var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc!.Value); + var oldLenDesc = _length; + var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc!.Value); - newLenDesc.Value = newLen; - if (newLen >= oldLen) - { - return base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - } + newLenDesc.Value = newLen; + if (newLen >= oldLen) + { + return base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + } - if (!oldLenDesc.Writable) - { - return false; - } + if (!oldLenDesc.Writable) + { + return false; + } - bool newWritable; - if (!newLenDesc.WritableSet || newLenDesc.Writable) - { - newWritable = true; - } - else - { - newWritable = false; - newLenDesc.Writable = true; - } + bool newWritable; + if (!newLenDesc.WritableSet || newLenDesc.Writable) + { + newWritable = true; + } + else + { + newWritable = false; + newLenDesc.Writable = true; + } - var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - if (!succeeded) - { - return false; - } + var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + if (!succeeded) + { + return false; + } - var count = _dense?.Length ?? _sparse!.Count; - if (count < oldLen - newLen) + var count = _dense?.Length ?? _sparse!.Count; + if (count < oldLen - newLen) + { + if (_dense != null) { - if (_dense != null) + for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) { - for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) + if (_dense[keyIndex] == null) { - if (_dense[keyIndex] == null) - { - continue; - } + continue; + } - // is it the index of the array - if (keyIndex >= newLen && keyIndex < oldLen) + // is it the index of the array + if (keyIndex >= newLen && keyIndex < oldLen) + { + var deleteSucceeded = Delete(keyIndex); + if (!deleteSucceeded) { - var deleteSucceeded = Delete(keyIndex); - if (!deleteSucceeded) + newLenDesc.Value = keyIndex + 1; + if (!newWritable) { - newLenDesc.Value = keyIndex + 1; - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } + + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; } } } - else + } + else + { + // in the case of sparse arrays, treat each concrete element instead of + // iterating over all indexes + var keys = new List(_sparse!.Keys); + var keysCount = keys.Count; + for (var i = 0; i < keysCount; i++) { - // in the case of sparse arrays, treat each concrete element instead of - // iterating over all indexes - var keys = new List(_sparse!.Keys); - var keysCount = keys.Count; - for (var i = 0; i < keysCount; i++) - { - var keyIndex = keys[i]; + var keyIndex = keys[i]; - // is it the index of the array - if (keyIndex >= newLen && keyIndex < oldLen) + // is it the index of the array + if (keyIndex >= newLen && keyIndex < oldLen) + { + var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex)); + if (!deleteSucceeded) { - var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex)); - if (!deleteSucceeded) + newLenDesc.Value = JsNumber.Create(keyIndex + 1); + if (!newWritable) { - newLenDesc.Value = JsNumber.Create(keyIndex + 1); - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } + + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; } } } } - else + } + else + { + while (newLen < oldLen) { - while (newLen < oldLen) + // algorithm as per the spec + oldLen--; + var deleteSucceeded = Delete(oldLen); + if (!deleteSucceeded) { - // algorithm as per the spec - oldLen--; - var deleteSucceeded = Delete(oldLen); - if (!deleteSucceeded) + newLenDesc.Value = oldLen + 1; + if (!newWritable) { - newLenDesc.Value = oldLen + 1; - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } - } - } - if (!newWritable) - { - base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet)); + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; + } } + } - return true; + if (!newWritable) + { + base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet)); } - return base.DefineOwnProperty(property, desc); + return true; } private bool DefineOwnProperty(uint index, PropertyDescriptor desc) @@ -397,39 +377,28 @@ public sealed override IEnumerable> Ge if (temp != null) { var length = System.Math.Min(temp.Length, GetLength()); - for (var i = 0; i < length; i++) + for (uint i = 0; i < length; i++) { var value = temp[i]; if (value != null) { - if (value is not PropertyDescriptor descriptor) + if (_sparse is null || !_sparse.TryGetValue(i, out var descriptor) || descriptor is null) { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) - { - temp = _dense!; - } - temp[i] = descriptor = new PropertyDescriptor((JsValue) value, PropertyFlag.ConfigurableEnumerableWritable); + _sparse ??= new Dictionary(); + _sparse[i] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } yield return new KeyValuePair(TypeConverter.ToString(i), descriptor); } } } - else + else if (_sparse != null) { - foreach (var entry in _sparse!) + foreach (var entry in _sparse) { var value = entry.Value; if (value is not null) { - if (value is not PropertyDescriptor descriptor) - { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) - { - temp = _dense!; - } - _sparse[entry.Key] = descriptor = new PropertyDescriptor((JsValue) value, PropertyFlag.ConfigurableEnumerableWritable); - } - yield return new KeyValuePair(TypeConverter.ToString(entry.Key), descriptor); + yield return new KeyValuePair(TypeConverter.ToString(entry.Key), value); } } } @@ -454,7 +423,7 @@ public sealed override PropertyDescriptor GetOwnProperty(JsValue property) if (IsArrayIndex(property, out var index)) { - if (TryGetDescriptor(index, out var result)) + if (TryGetDescriptor(index, createIfMissing: true, out var result)) { return result; } @@ -497,33 +466,23 @@ public sealed override JsValue Get(JsValue property, JsValue receiver) public sealed override bool Set(JsValue property, JsValue value, JsValue receiver) { var isSafeSelfTarget = IsSafeSelfTarget(receiver); - if (isSafeSelfTarget && IsArrayIndex(property, out var index)) + if (isSafeSelfTarget && CanUseFastAccess) { - var temp = _dense; - if (temp is not null && CanUseFastAccess) + if (IsArrayIndex(property, out var index)) { - var current = index < temp.Length ? temp[index] : null; - if (current is not PropertyDescriptor p || p.IsDefaultArrayValueDescriptor()) - { - SetIndexValue(index, value, true); - return true; - } + SetIndexValue(index, value, updateLength: true); + return true; } - // slower and more allocating - if (TryGetDescriptor(index, out var descriptor)) + if (property == CommonProperties.Length + && _length is { Writable: true } + && value is JsNumber jsNumber + && jsNumber.IsInteger() + && jsNumber._value <= MaxDenseArrayLength + && jsNumber._value >= GetLength()) { - if (descriptor.IsDefaultArrayValueDescriptor()) - { - // fast path with direct write without allocations - descriptor.Value = value; - return true; - } - } - else if (CanUseFastAccess) - { - // we know it's to be written to own array backing field as new value - SetIndexValue(index, value, true); + // we don't need explicit resize + _length.Value = jsNumber; return true; } } @@ -544,6 +503,27 @@ public sealed override bool HasProperty(JsValue property) return base.HasProperty(property); } + internal bool HasProperty(ulong index) + { + if (index < uint.MaxValue) + { + var temp = _dense; + if (temp != null) + { + if (index < (uint) temp.Length && temp[index] is not null) + { + return true; + } + } + else if (_sparse!.ContainsKey((uint) index)) + { + return true; + } + } + + return base.HasProperty(index); + } + protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { var isArrayIndex = IsArrayIndex(property, out var index); @@ -568,11 +548,15 @@ private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArra if (isArrayIndex) { - if (!desc.IsDefaultArrayValueDescriptor()) + if (!desc.IsDefaultArrayValueDescriptor() && desc.Flags != PropertyFlag.None) { _objectChangeFlags |= ObjectChangeFlags.NonDefaultDataDescriptorUsage; } - _objectChangeFlags |= ObjectChangeFlags.ArrayIndex; + + if (GetType() != typeof(JsArray)) + { + _objectChangeFlags |= ObjectChangeFlags.ArrayIndex; + } } else { @@ -712,8 +696,7 @@ internal bool Delete(uint index) { if (index < (uint) temp.Length) { - var value = temp[index]; - if (value is JsValue || value is PropertyDescriptor { Configurable: true }) + if (!TryGetDescriptor(index, createIfMissing: false, out var descriptor) || descriptor.Configurable) { temp[index] = null; return true; @@ -721,7 +704,7 @@ internal bool Delete(uint index) } } - if (!TryGetDescriptor(index, out var desc)) + if (!TryGetDescriptor(index, createIfMissing: false, out var desc)) { return true; } @@ -754,7 +737,7 @@ internal bool DeleteAt(uint index) return false; } - private bool TryGetDescriptor(uint index, [NotNullWhen(true)] out PropertyDescriptor? descriptor) + private bool TryGetDescriptor(uint index, bool createIfMissing, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { descriptor = null; var temp = _dense; @@ -763,37 +746,30 @@ private bool TryGetDescriptor(uint index, [NotNullWhen(true)] out PropertyDescri if (index < (uint) temp.Length) { var value = temp[index]; - if (value is JsValue jsValue) + if (value != null) { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) + if (_sparse is null || !_sparse.TryGetValue(index, out descriptor) || descriptor is null) { - temp = _dense!; + if (!createIfMissing) + { + return false; + } + _sparse ??= new Dictionary(); + _sparse[index] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } - temp[index] = descriptor = new PropertyDescriptor(jsValue, PropertyFlag.ConfigurableEnumerableWritable); - } - else if (value is PropertyDescriptor propertyDescriptor) - { - descriptor = propertyDescriptor; - } - } - return descriptor != null; - } - if (_sparse!.TryGetValue(index, out var sparseValue)) - { - if (sparseValue is JsValue jsValue) - { - _sparse[index] = descriptor = new PropertyDescriptor(jsValue, PropertyFlag.ConfigurableEnumerableWritable); - } - else if (sparseValue is PropertyDescriptor propertyDescriptor) - { - descriptor = propertyDescriptor; + descriptor.Value = value; + return true; + } } + return false; } + _sparse?.TryGetValue(index, out descriptor); return descriptor is not null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryGetValue(uint index, out JsValue value) { value = GetValue(index, unwrapFromNonDataDescriptor: true)!; @@ -803,6 +779,12 @@ internal bool TryGetValue(uint index, out JsValue value) return true; } + return TryGetValueUnlikely(index, out value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private bool TryGetValueUnlikely(uint index, out JsValue value) + { if (!CanUseFastAccess) { // slow path must be checked for prototype @@ -825,54 +807,75 @@ internal bool TryGetValue(uint index, out JsValue value) return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private JsValue? GetValue(uint index, bool unwrapFromNonDataDescriptor) { - object? value = null; var temp = _dense; if (temp != null) { if (index < (uint) temp.Length) { - value = temp[index]; + return temp[index]; } - } - else - { - _sparse!.TryGetValue(index, out value); + return null; } - if (value is JsValue jsValue) - { - return jsValue; - } + return GetValueUnlikely(index, unwrapFromNonDataDescriptor); + } - if (value is PropertyDescriptor propertyDescriptor) + [MethodImpl(MethodImplOptions.NoInlining)] + private JsValue? GetValueUnlikely(uint index, bool unwrapFromNonDataDescriptor) + { + JsValue? value = null; + if (_sparse!.TryGetValue(index, out var descriptor) && descriptor != null) { - return propertyDescriptor.IsDataDescriptor() || unwrapFromNonDataDescriptor ? UnwrapJsValue(propertyDescriptor) : null; + value = descriptor.IsDataDescriptor() || unwrapFromNonDataDescriptor + ? UnwrapJsValue(descriptor) + : null; } - return null; + return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteArrayValue(uint index, object? value) + private void WriteArrayValue(uint index, PropertyDescriptor descriptor) { - var dense = _dense; - if (dense != null && index < (uint) dense.Length) + var temp = _dense; + if (temp != null && descriptor.IsDefaultArrayValueDescriptor()) { - if (value is not null && !_isObjectArray && EnsureCompatibleDense(value is JsValue ? typeof(JsValue) : typeof(PropertyDescriptor))) + if (index < (uint) temp.Length) { - dense = _dense; + temp[index] = descriptor.Value; + } + else + { + WriteArrayValueUnlikely(index, descriptor.Value); } - dense![index] = value; } else { - WriteArrayValueUnlikely(index, value); + WriteArrayValueUnlikely(index, descriptor); } } - private void WriteArrayValueUnlikely(uint index, object? value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteArrayValue(uint index, JsValue? value) + { + var temp = _dense; + if (temp != null) + { + if (index < (uint) temp.Length) + { + temp[index] = value; + return; + } + } + + WriteArrayValueUnlikely(index, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void WriteArrayValueUnlikely(uint index, JsValue? value) { // calculate eagerly so we know if we outgrow var dense = _dense; @@ -892,50 +895,45 @@ private void WriteArrayValueUnlikely(uint index, object? value) } else { - if (dense != null) - { - ConvertToSparse(); - } - - _sparse![index] = value; + ConvertToSparse(); + _sparse![index] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } } - /// - /// Converts to object array when needed. Returns true if conversion was made. - /// - private bool EnsureCompatibleDense(Type expectedElementType) + private void WriteArrayValueUnlikely(uint index, PropertyDescriptor? value) { - if (!_isObjectArray) + if (_sparse == null) { - return CheckConversionUnlikely(expectedElementType); + ConvertToSparse(); } - return false; + _sparse![index] = value; } - private bool CheckConversionUnlikely(Type expectedElementType) + private void ConvertToSparse() { - var currentElementType = _dense!.GetType().GetElementType(); - if (currentElementType != typeof(object) && !expectedElementType.IsAssignableFrom(currentElementType)) + // need to move data + var temp = _dense; + + if (temp is null) { - // triggers conversion for array - EnsureCapacity((uint) _dense.Length, force: true); - return true; + return; } - return false; - } - - private void ConvertToSparse() - { - _sparse = new Dictionary(_dense!.Length <= 1024 ? _dense.Length : 0); - // need to move data - for (uint i = 0; i < (uint) _dense.Length; ++i) + _sparse ??= new Dictionary(); + for (uint i = 0; i < (uint) temp.Length; ++i) { - if (_dense[i] != null) + var value = temp[i]; + if (value != null) { - _sparse[i] = _dense[i]; + _sparse.TryGetValue(i, out var descriptor); + descriptor ??= new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); + descriptor.Value = value; + _sparse[i] = descriptor; + } + else + { + _sparse.Remove(i); } } @@ -962,10 +960,9 @@ internal void EnsureCapacity(uint capacity, bool force = false) } // need to grow - var newArray = new object[capacity]; + var newArray = new JsValue[capacity]; System.Array.Copy(dense, newArray, dense.Length); _dense = newArray; - _isObjectArray = true; } public JsValue[] ToArray() @@ -1020,14 +1017,7 @@ private IEnumerable Enumerate() var value = temp[i]; if (value != null) { - if (value is not PropertyDescriptor descriptor) - { - yield return new IndexedEntry(i, (JsValue) value); - } - else - { - yield return new IndexedEntry(i, descriptor.Value); - } + yield return new IndexedEntry(i, value); } } } @@ -1035,17 +1025,10 @@ private IEnumerable Enumerate() { foreach (var entry in _sparse!) { - var value = entry.Value; - if (value is not null) + var descriptor = entry.Value; + if (descriptor is not null) { - if (value is not PropertyDescriptor descriptor) - { - yield return new IndexedEntry((int) entry.Key, (JsValue) value); - } - else - { - yield return new IndexedEntry((int) entry.Key, descriptor.Value); - } + yield return new IndexedEntry((int) entry.Key, descriptor.Value); } } } @@ -1069,7 +1052,7 @@ public void Push(JsValue value) } else { - WriteValueSlow(n, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); + WriteValueSlow(n, value); } // check if we can set length fast without breaking ECMA specification @@ -1146,15 +1129,15 @@ private bool CanSetLength() } [MethodImpl(MethodImplOptions.NoInlining)] - private void WriteValueSlow(double n, PropertyDescriptor desc) + private void WriteValueSlow(double n, JsValue value) { - if (n < uint.MaxValue) + if (n < ArrayOperations.MaxArrayLength) { - WriteArrayValue((uint) n, desc); + WriteArrayValue((uint) n, value); } else { - DefinePropertyOrThrow((uint) n, desc); + DefinePropertyOrThrow((uint) n, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); } } @@ -1325,21 +1308,18 @@ internal void CopyValues(JsArray source, uint sourceStartIndex, uint targetStart } var dense = _dense; - if (dense != null && sourceDense != null - && (uint) dense.Length >= targetStartIndex + length - && dense[targetStartIndex] is null) + if (dense != null + && sourceDense != null + && (uint) dense.Length >= targetStartIndex + length + && dense[targetStartIndex] is null) { uint j = 0; for (var i = sourceStartIndex; i < sourceStartIndex + length; ++i, j++) { - object? sourceValue; + JsValue? sourceValue; if (i < (uint) sourceDense.Length && sourceDense[i] != null) { sourceValue = sourceDense[i]; - if (sourceValue is PropertyDescriptor propertyDescriptor) - { - sourceValue = UnwrapJsValue(propertyDescriptor); - } } else { diff --git a/Jint/Native/Array/ArrayOperations.cs b/Jint/Native/Array/ArrayOperations.cs index 19493e32f9..96b7020a27 100644 --- a/Jint/Native/Array/ArrayOperations.cs +++ b/Jint/Native/Array/ArrayOperations.cs @@ -3,7 +3,6 @@ using Jint.Native.Object; using Jint.Native.TypedArray; using Jint.Runtime; -using Jint.Runtime.Descriptors; namespace Jint.Native.Array { @@ -70,7 +69,7 @@ public virtual JsValue[] GetAll( public abstract bool TryGetValue(ulong index, out JsValue value); - public bool HasProperty(ulong index) => Target.HasProperty(index); + public abstract bool HasProperty(ulong index); public abstract void CreateDataPropertyOrThrow(ulong index, JsValue value); @@ -201,6 +200,8 @@ public override void Set(ulong index, JsValue value, bool updateLength = false, public override void DeletePropertyOrThrow(ulong index) => _target.DeletePropertyOrThrow(JsString.Create(index)); + + public override bool HasProperty(ulong index) => Target.HasProperty(index); } private sealed class ArrayInstanceOperations : ArrayOperations @@ -244,22 +245,18 @@ public override JsValue[] GetAll(Types elementTypes, bool skipHoles = false) var jsValues = new JsValue[n]; for (uint i = 0; i < (uint) jsValues.Length; i++) { - var prop = _target._dense[i]; - if (prop is null) - { - prop = _target.Prototype?.Get(i) ?? JsValue.Undefined; - } - else if (prop is not JsValue) + var value = _target._dense[i]; + if (value is null) { - prop = _target.UnwrapJsValue((PropertyDescriptor) prop); + value = _target.Prototype?.Get(i) ?? JsValue.Undefined; } - if (prop is JsValue jsValue && (jsValue.Type & elementTypes) == 0) + if ((value.Type & elementTypes) == 0) { ExceptionHelper.ThrowTypeErrorNoEngine("invalid type"); } - jsValues[writeIndex++] = (JsValue?) prop ?? JsValue.Undefined; + jsValues[writeIndex++] = (JsValue?) value ?? JsValue.Undefined; } return jsValues; @@ -273,6 +270,8 @@ public override void CreateDataPropertyOrThrow(ulong index, JsValue value) public override void Set(ulong index, JsValue value, bool updateLength = false, bool throwOnError = true) => _target.SetIndexValue((uint) index, value, updateLength); + + public override bool HasProperty(ulong index) => _target.HasProperty(index); } private sealed class TypedArrayInstanceOperations : ArrayOperations @@ -336,6 +335,8 @@ public override void Set(ulong index, JsValue value, bool updateLength = false, public override void DeletePropertyOrThrow(ulong index) => _target.DeletePropertyOrThrow(index); + + public override bool HasProperty(ulong index) => _target.HasProperty(index); } } diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 1d48f6e3eb..bdc1d4d341 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1648,10 +1648,10 @@ public JsValue Pop(JsValue thisObject, JsValue[] arguments) return element; } - private object[] CreateBackingArray(ulong length) + private JsValue[] CreateBackingArray(ulong length) { ValidateArrayLength(length); - return new object[length]; + return new JsValue[length]; } private void ValidateArrayLength(ulong length) diff --git a/Jint/Native/JsArray.cs b/Jint/Native/JsArray.cs index f17d82c622..26068a839b 100644 --- a/Jint/Native/JsArray.cs +++ b/Jint/Native/JsArray.cs @@ -1,5 +1,4 @@ using Jint.Native.Array; -using Jint.Runtime.Descriptors; namespace Jint.Native; @@ -22,16 +21,4 @@ public JsArray(Engine engine, uint capacity = 0, uint length = 0) : base(engine, public JsArray(Engine engine, JsValue[] items) : base(engine, items) { } - - /// - /// Possibility to construct valid array fast, requires that supplied array does not have holes. - /// The array will be owned and modified by Jint afterwards. - /// - public JsArray(Engine engine, PropertyDescriptor[] items) : base(engine, items) - { - } - - internal JsArray(Engine engine, object[] items) : base(engine, items) - { - } } diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index ec09c45cca..b95ab8fd64 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -555,6 +555,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) return SetUnlikely(property, value, receiver); } + [MethodImpl(MethodImplOptions.NoInlining)] private bool SetUnlikely(JsValue property, JsValue value, JsValue receiver) { var ownDesc = GetOwnProperty(property); @@ -1510,7 +1511,7 @@ internal virtual ulong GetSmallestIndex(ulong length) } var min = length; - foreach (var entry in Properties) + foreach (var entry in GetOwnProperties()) { if (ulong.TryParse(entry.Key.ToString(), out var index)) { @@ -1520,7 +1521,7 @@ internal virtual ulong GetSmallestIndex(ulong length) if (Prototype?.Properties != null) { - foreach (var entry in Prototype.Properties) + foreach (var entry in Prototype.GetOwnProperties()) { if (ulong.TryParse(entry.Key.ToString(), out var index)) { diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index ece06bf412..3b6f7e88c6 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -845,40 +845,45 @@ public static uint ToIndex(Realm realm, JsValue value) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(long i) { - return i >= 0 && i < intToString.Length - ? intToString[i] + var temp = intToString; + return (ulong) i < (ulong) temp.Length + ? temp[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(int i) { - return i >= 0 && i < intToString.Length - ? intToString[i] + var temp = intToString; + return (uint) i < (uint) temp.Length + ? temp[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(uint i) { - return i < (uint) intToString.Length - ? intToString[i] + var temp = intToString; + return i < (uint) temp.Length + ? temp[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(char c) { - return c >= 0 && c < charToString.Length - ? charToString[c] + var temp = charToString; + return (uint) c < (uint) temp.Length + ? temp[c] : c.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(ulong i) { - return i >= 0 && i < (ulong) intToString.Length - ? intToString[i] + var temp = intToString; + return i < (ulong) temp.Length + ? temp[i] : i.ToString(); }