瀏覽代碼

Optimize array access and add ability to hint array size (#455)

Marko Lahma 7 年之前
父節點
當前提交
98dc732be2

+ 3 - 7
Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj

@@ -1,5 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
   <PropertyGroup>
     <TargetFrameworks>netcoreapp2.0</TargetFrameworks>
     <AssemblyName>Jint.Tests.CommonScripts</AssemblyName>
@@ -14,21 +13,18 @@
     <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
     <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
   </PropertyGroup>
-
   <ItemGroup>
     <EmbeddedResource Include="Scripts\*.*" Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
   </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
     <PackageReference Include="xunit" Version="2.3.1" />
+    <PackageReference Include="xunit.analyzers" Version="0.8.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
-    <PackageReference Include="xunit.analyzers" Version="0.7.0" />
     <PackageReference Include="xunit.runner.console" Version="2.3.1" />
+    <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
   </ItemGroup>
-
-</Project>
+</Project>

+ 6 - 9
Jint.Tests.Ecma/Jint.Tests.Ecma.csproj

@@ -1,5 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
   <PropertyGroup>
     <TargetFramework>netcoreapp2.0</TargetFramework>
     <AssemblyName>Jint.Tests.Ecma</AssemblyName>
@@ -15,21 +14,19 @@
     <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
     <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
   </PropertyGroup>
-
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
-    <PackageReference Include="xunit" Version="2.2.0" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
-    <PackageReference Include="xunit.analyzers" Version="0.3.0" />
+    <PackageReference Include="xunit" Version="2.3.1" />
+    <PackageReference Include="xunit.analyzers" Version="0.8.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
+    <PackageReference Include="xunit.runner.console" Version="2.3.1" />
+    <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
   </ItemGroup>
-
   <ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">
     <Reference Include="System" />
     <Reference Include="Microsoft.CSharp" />
   </ItemGroup>
-
-</Project>
+</Project>

+ 2 - 1
Jint.Tests/Jint.Tests.csproj

@@ -22,8 +22,9 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
     <PackageReference Include="xunit" Version="2.3.1" />
+    <PackageReference Include="xunit.analyzers" Version="0.8.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
-    <PackageReference Include="xunit.analyzers" Version="0.7.0" />
     <PackageReference Include="xunit.runner.console" Version="2.3.1" />
+    <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
   </ItemGroup>
 </Project>

+ 23 - 1
Jint.Tests/Runtime/EngineTests.cs

@@ -310,6 +310,28 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void DenseArrayTurnsToSparseArrayWhenSizeGrowsTooMuch()
+        {
+            RunTest(@"
+                var n = 1024*10+2;
+                var o = Array(n);
+                for (var i = 0; i < n; i++) o[i] = i;
+                assert(o[0] == 0);
+                assert(o[n - 1] == n -1);
+            ");
+        }
+
+        [Fact]
+        public void DenseArrayTurnsToSparseArrayWhenSparseIndexed()
+        {
+            RunTest(@"
+                var o = Array();
+                o[100] = 1;
+                assert(o[100] == 1);
+            ");
+        }
+
         [Fact]
         public void ArrayPopShouldDecrementLength()
         {
@@ -1893,7 +1915,7 @@ namespace Jint.Tests.Runtime
 
             Assert.True(val.AsString() == "53.6841659");
         }
-		
+
         [Theory]
         [InlineData("", "escape('')")]
         [InlineData("%u0100%u0101%u0102", "escape('\u0100\u0101\u0102')")]

+ 36 - 6
Jint/Native/Array/ArrayConstructor.cs

@@ -1,4 +1,5 @@
-using System.Collections;
+using System;
+using System.Collections;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Runtime;
@@ -19,7 +20,7 @@ namespace Jint.Native.Array
             var obj = new ArrayConstructor(engine);
             obj.Extensible = true;
 
-            // The value of the [[Prototype]] internal property of the Array constructor is the Function prototype object 
+            // The value of the [[Prototype]] internal property of the Array constructor is the Function prototype object
             obj.Prototype = engine.Function.PrototypeObject;
             obj.PrototypeObject = ArrayPrototype.CreatePrototypeObject(engine, obj);
 
@@ -36,7 +37,7 @@ namespace Jint.Native.Array
             FastAddProperty("isArray", new ClrFunctionInstance(Engine, IsArray, 1), true, false, true);
         }
 
-        private JsValue IsArray(JsValue thisObj, JsValue[] arguments)
+        private static JsValue IsArray(JsValue thisObj, JsValue[] arguments)
         {
             if (arguments.Length == 0)
             {
@@ -55,7 +56,36 @@ namespace Jint.Native.Array
 
         public ObjectInstance Construct(JsValue[] arguments)
         {
-            var instance = new ArrayInstance(Engine);
+            // check if we can figure out good size
+            var capacity = arguments.Length > 0 ? (uint) arguments.Length : 0;
+            if (arguments.Length == 1 && arguments[0].Type == Types.Number)
+            {
+                var number = arguments[0].AsNumber();
+                if (number > 0)
+                {
+                    capacity = (uint) number;
+                }
+            }
+            return Construct(arguments, capacity);
+        }
+
+        public ArrayInstance Construct(int capacity)
+        {
+            if (capacity < 0)
+            {
+                throw new ArgumentException("invalid array length", nameof(capacity));
+            }
+            return Construct(System.Array.Empty<JsValue>(), (uint) capacity);
+        }
+
+        public ArrayInstance Construct(uint capacity)
+        {
+            return Construct(System.Array.Empty<JsValue>(), capacity);
+        }
+
+        public ArrayInstance Construct(JsValue[] arguments, uint capacity)
+        {
+            var instance = new ArrayInstance(Engine, capacity);
             instance.Prototype = PrototypeObject;
             instance.Extensible = true;
 
@@ -66,7 +96,7 @@ namespace Jint.Native.Array
                 {
                     throw new JavaScriptException(Engine.RangeError, "Invalid array length");
                 }
-                
+
                 instance.FastAddProperty("length", length, true, false, false);
             }
             else if (arguments.Length == 1 && arguments.At(0).IsObject() && arguments.At(0).As<ObjectWrapper>() != null )
@@ -75,7 +105,7 @@ namespace Jint.Native.Array
 
                 if (enumerable != null)
                 {
-                    var jsArray = Engine.Array.Construct(Arguments.Empty);
+                    var jsArray = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
                     foreach (var item in enumerable)
                     {
                         var jsItem = JsValue.FromObject(Engine, item);

+ 26 - 0
Jint/Native/Array/ArrayExecutionContext.cs

@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Jint.Native.Array
+{
+    /// <summary>
+    /// Helper to cache common data structures needed in array access on a per thread basis.
+    /// </summary>
+    internal class ArrayExecutionContext
+    {
+        // cache key container for array iteration for less allocations
+        private static readonly ThreadLocal<ArrayExecutionContext> _executionContext = new ThreadLocal<ArrayExecutionContext>(() => new ArrayExecutionContext());
+
+        private ArrayExecutionContext()
+        {
+        }
+
+        public List<uint> KeyCache = new List<uint>();
+
+        public JsValue[] CallArray1 = new JsValue[1];
+        public JsValue[] CallArray3 = new JsValue[3];
+        public JsValue[] CallArray4 = new JsValue[4];
+
+        public static ArrayExecutionContext Current => _executionContext.Value;
+    }
+}

+ 280 - 65
Jint/Native/Array/ArrayInstance.cs

@@ -1,23 +1,33 @@
 using System.Collections.Generic;
-using System.Threading;
+using System.Runtime.CompilerServices;
 using Jint.Native.Object;
 using Jint.Runtime;
-using Jint.Runtime.Descriptors;
+using PropertyDescriptor = Jint.Runtime.Descriptors.PropertyDescriptor;
+using TypeConverter = Jint.Runtime.TypeConverter;
 
 namespace Jint.Native.Array
 {
     public class ArrayInstance : ObjectInstance
     {
-        // cache key container for array iteration for less allocations
-        private static readonly ThreadLocal<List<uint>> keyCache = new ThreadLocal<List<uint>>(() => new List<uint>());
-
         private readonly Engine _engine;
-        private readonly Dictionary<uint, PropertyDescriptor> _array = new Dictionary<uint, PropertyDescriptor>();
-        private PropertyDescriptor _length;
 
-        public ArrayInstance(Engine engine) : base(engine)
+        private const int MaxDenseArrayLength = 1024 * 10;
+
+        // we have dense and sparse, we usually can start with dense and fall back to sparse when necessary
+        private PropertyDescriptor[] _dense;
+        private Dictionary<uint, PropertyDescriptor> _sparse;
+
+        public ArrayInstance(Engine engine, uint capacity = 0) : base(engine)
         {
             _engine = engine;
+            if (capacity < MaxDenseArrayLength)
+            {
+                _dense = capacity > 0 ? new PropertyDescriptor[capacity] : System.Array.Empty<PropertyDescriptor>();
+            }
+            else
+            {
+                _sparse = new Dictionary<uint, PropertyDescriptor>((int) (capacity <= 1024 ? capacity : 1024));
+            }
         }
 
         public override string Class => "Array";
@@ -64,8 +74,7 @@ namespace Jint.Native.Array
         public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
         {
             var oldLenDesc = GetOwnProperty("length");
-            var oldLen = (uint)TypeConverter.ToNumber(oldLenDesc.Value);
-            uint index;
+            var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc.Value);
 
             if (propertyName == "length")
             {
@@ -84,8 +93,9 @@ namespace Jint.Native.Array
                 newLenDesc.Value = newLen;
                 if (newLen >= oldLen)
                 {
-                    return base.DefineOwnProperty("length", _length = newLenDesc, throwOnError);
+                    return base.DefineOwnProperty("length", newLenDesc, throwOnError);
                 }
+
                 if (!oldLenDesc.Writable.Value)
                 {
                     if (throwOnError)
@@ -95,6 +105,7 @@ namespace Jint.Native.Array
 
                     return false;
                 }
+
                 bool newWritable;
                 if (!newLenDesc.Writable.HasValue || newLenDesc.Writable.Value)
                 {
@@ -106,41 +117,94 @@ namespace Jint.Native.Array
                     newLenDesc.Writable = true;
                 }
 
-                var succeeded = base.DefineOwnProperty("length", _length = newLenDesc, throwOnError);
+                var succeeded = base.DefineOwnProperty("length", newLenDesc, throwOnError);
                 if (!succeeded)
                 {
                     return false;
                 }
 
-                // in the case of sparse arrays, treat each concrete element instead of
-                // iterating over all indexes
+                int count = 0;
+                if (_dense != null)
+                {
+                    for (int i = 0; i < _dense.Length; ++i)
+                    {
+                        if (_dense[i] != null)
+                        {
+                            count++;
+                        }
+                    }
+                }
+                else
+                {
+                    count = _sparse.Count;
+                }
 
-                if (_array.Count < oldLen - newLen)
+                if (count < oldLen - newLen)
                 {
-                    var keys = keyCache.Value;
-                    keys.Clear();
-                    keys.AddRange(_array.Keys);
-                    foreach (var keyIndex in keys)
+                    if (_dense != null)
                     {
-                        // is it the index of the array
-                        if (keyIndex >= newLen && keyIndex < oldLen)
+                        for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex)
                         {
-                            var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
-                            if (!deleteSucceeded)
+                            if (_dense[keyIndex] == null)
                             {
-                                newLenDesc.Value = JsValue.FromInt(keyIndex + 1);
-                                if (!newWritable)
+                                continue;
+                            }
+
+                            // is it the index of the array
+                            if (keyIndex >= newLen && keyIndex < oldLen)
+                            {
+                                var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
+                                if (!deleteSucceeded)
                                 {
-                                    newLenDesc.Writable = false;
+                                    newLenDesc.Value = new JsValue(keyIndex + 1);
+                                    if (!newWritable)
+                                    {
+                                        newLenDesc.Writable = false;
+                                    }
+
+                                    base.DefineOwnProperty("length", newLenDesc, false);
+
+                                    if (throwOnError)
+                                    {
+                                        throw new JavaScriptException(_engine.TypeError);
+                                    }
+
+                                    return false;
                                 }
-                                base.DefineOwnProperty("length", _length = newLenDesc, false);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        // in the case of sparse arrays, treat each concrete element instead of
+                        // iterating over all indexes
 
-                                if (throwOnError)
+                        var keys = ArrayExecutionContext.Current.KeyCache;
+                        keys.Clear();
+                        keys.AddRange(_sparse.Keys);
+                        foreach (var keyIndex in keys)
+                        {
+                            // is it the index of the array
+                            if (keyIndex >= newLen && keyIndex < oldLen)
+                            {
+                                var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
+                                if (!deleteSucceeded)
                                 {
-                                    throw new JavaScriptException(_engine.TypeError);
-                                }
+                                newLenDesc.Value = JsValue.FromInt(keyIndex + 1);
+                                    if (!newWritable)
+                                    {
+                                        newLenDesc.Writable = false;
+                                    }
 
-                                return false;
+                                    base.DefineOwnProperty("length", newLenDesc, false);
+
+                                    if (throwOnError)
+                                    {
+                                        throw new JavaScriptException(_engine.TypeError);
+                                    }
+
+                                    return false;
+                                }
                             }
                         }
                     }
@@ -159,7 +223,8 @@ namespace Jint.Native.Array
                             {
                                 newLenDesc.Writable = false;
                             }
-                            base.DefineOwnProperty("length", _length = newLenDesc, false);
+
+                            base.DefineOwnProperty("length", newLenDesc, false);
 
                             if (throwOnError)
                             {
@@ -170,13 +235,15 @@ namespace Jint.Native.Array
                         }
                     }
                 }
+
                 if (!newWritable)
                 {
                     DefineOwnProperty("length", new PropertyDescriptor(value: null, writable: false, enumerable: null, configurable: null), false);
                 }
+
                 return true;
             }
-            else if (IsArrayIndex(propertyName, out index))
+            else if (IsArrayIndex(propertyName, out var index))
             {
                 if (index >= oldLen && !oldLenDesc.Writable.Value)
                 {
@@ -187,6 +254,7 @@ namespace Jint.Native.Array
 
                     return false;
                 }
+
                 var succeeded = base.DefineOwnProperty(propertyName, desc, false);
                 if (!succeeded)
                 {
@@ -197,11 +265,13 @@ namespace Jint.Native.Array
 
                     return false;
                 }
+
                 if (index >= oldLen)
                 {
                     oldLenDesc.Value = index + 1;
-                    base.DefineOwnProperty("length", _length = oldLenDesc, false);
+                    base.DefineOwnProperty("length", oldLenDesc, false);
                 }
+
                 return true;
             }
 
@@ -210,17 +280,30 @@ namespace Jint.Native.Array
 
         public uint GetLength()
         {
-            return TypeConverter.ToUint32(_length.Value);
+            return GetLengthValue();
         }
 
         public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()
         {
-            foreach(var entry in _array)
+            if (_dense != null)
             {
-                yield return new KeyValuePair<string, PropertyDescriptor>(TypeConverter.ToString(entry.Key), entry.Value);
+                for (var i = 0; i < _dense.Length; i++)
+                {
+                    if (_dense[i] != null)
+                    {
+                        yield return new KeyValuePair<string, PropertyDescriptor>(TypeConverter.ToString(i), _dense[i]);
+                    }
+                }
+            }
+            else
+            {
+                foreach (var entry in _sparse)
+                {
+                    yield return new KeyValuePair<string, PropertyDescriptor>(TypeConverter.ToString(entry.Key), entry.Value);
+                }
             }
 
-            foreach(var entry in base.GetOwnProperties())
+            foreach (var entry in base.GetOwnProperties())
             {
                 yield return entry;
             }
@@ -228,18 +311,14 @@ namespace Jint.Native.Array
 
         public override PropertyDescriptor GetOwnProperty(string propertyName)
         {
-            uint index;
-            if (IsArrayIndex(propertyName, out index))
+            if (IsArrayIndex(propertyName, out var index))
             {
-                PropertyDescriptor result;
-                if (_array.TryGetValue(index, out result))
+                if (TryGetDescriptor(index, out var result))
                 {
                     return result;
                 }
-                else
-                {
-                    return PropertyDescriptor.Undefined;
-                }
+
+                return PropertyDescriptor.Undefined;
             }
 
             return base.GetOwnProperty(propertyName);
@@ -247,28 +326,23 @@ namespace Jint.Native.Array
 
         protected override void SetOwnProperty(string propertyName, PropertyDescriptor desc)
         {
-            uint index;
-            if (IsArrayIndex(propertyName, out index))
+            if (IsArrayIndex(propertyName, out var index))
             {
-                _array[index] = desc;
+                WriteArrayValue(index, desc);
             }
             else
             {
-                if(propertyName == "length")
-                {
-                    _length = desc;
-                }
-
                 base.SetOwnProperty(propertyName, desc);
             }
         }
 
         public override bool HasOwnProperty(string p)
         {
-            uint index;
-            if (IsArrayIndex(p, out index))
+            if (IsArrayIndex(p, out var index))
             {
-                return index < GetLength() && _array.ContainsKey(index);
+                return index < GetLengthValue()
+                       && (_sparse == null || _sparse.ContainsKey(index))
+                       && (_dense == null || _dense[index] != null);
             }
 
             return base.HasOwnProperty(p);
@@ -277,14 +351,15 @@ namespace Jint.Native.Array
         public override void RemoveOwnProperty(string p)
         {
             uint index;
-            if(IsArrayIndex(p, out index))
+            if (IsArrayIndex(p, out index))
             {
-                _array.Remove(index);
+                DeleteAt(index);
             }
 
             base.RemoveOwnProperty(p);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static bool IsArrayIndex(string p, out uint index)
         {
             index = ParseArrayIndex(p);
@@ -294,7 +369,8 @@ namespace Jint.Native.Array
             // return TypeConverter.ToString(index) == TypeConverter.ToString(p) && index != uint.MaxValue;
         }
 
-        internal static uint ParseArrayIndex(string p)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static uint ParseArrayIndex(string p)
         {
             int d = p[0] - '0';
 
@@ -303,7 +379,7 @@ namespace Jint.Native.Array
                 return uint.MaxValue;
             }
 
-            if(d == 0 && p.Length > 1)
+            if (d == 0 && p.Length > 1)
             {
                 // If p is a number that start with '0' and is not '0' then
                 // its ToString representation can't be the same a p. This is
@@ -313,7 +389,7 @@ namespace Jint.Native.Array
                 return uint.MaxValue;
             }
 
-            ulong result = (uint)d;
+            ulong result = (uint) d;
 
             for (int i = 1; i < p.Length; i++)
             {
@@ -324,7 +400,7 @@ namespace Jint.Native.Array
                     return uint.MaxValue;
                 }
 
-                result = result * 10 + (uint)d;
+                result = result * 10 + (uint) d;
 
                 if (result >= uint.MaxValue)
                 {
@@ -332,7 +408,146 @@ namespace Jint.Native.Array
                 }
             }
 
-            return (uint)result;
+            return (uint) result;
+        }
+
+        internal void SetIndexValue(uint index, JsValue value, bool throwOnError)
+        {
+            var length = GetLengthValue();
+            if (index >= length)
+            {
+                var p = base.GetOwnProperty("length");
+                p.Value = index + 1;
+            }
+
+            WriteArrayValue(index, new PropertyDescriptor(value, true, true, true));
+        }
+
+        internal uint GetSmallestIndex()
+        {
+            if (_dense != null)
+            {
+                return 0;
+            }
+
+            uint smallest = 0;
+            // only try to help if collection reasonable small
+            if (_sparse.Count > 0 && _sparse.Count < 100 && !_sparse.ContainsKey(0))
+            {
+                smallest = uint.MaxValue;
+                foreach (var key in _sparse.Keys)
+                {
+                    smallest = System.Math.Min(key, smallest);
+                }
+            }
+
+            return smallest;
+        }
+
+        public bool TryGetValue(uint index, out JsValue value)
+        {
+            value = JsValue.Undefined;
+
+            if (!TryGetDescriptor(index, out var desc)
+                || desc == null
+                || desc == PropertyDescriptor.Undefined
+                || (desc.Value == null && desc.Get == null))
+            {
+                desc = GetProperty(TypeConverter.ToString(index));
+            }
+
+            if (desc != null && desc != PropertyDescriptor.Undefined)
+            {
+                bool success = desc.TryGetValue(this, out value);
+                return success;
+            }
+
+            return false;
+        }
+
+        internal void DeleteAt(uint index)
+        {
+            if (_dense != null)
+            {
+                if (index < _dense.Length)
+                {
+                    _dense[index] = null;
+                }
+            }
+            else
+            {
+                _sparse.Remove(index);
+            }
+        }
+
+        private bool TryGetDescriptor(uint index, out PropertyDescriptor descriptor)
+        {
+            if (_dense != null)
+            {
+                if (index >= _dense.Length)
+                {
+                    descriptor = null;
+                    return false;
+                }
+
+                descriptor = _dense[index];
+                return descriptor != null;
+            }
+
+            return _sparse.TryGetValue(index, out descriptor);
+        }
+
+        private void WriteArrayValue(uint index, PropertyDescriptor desc)
+        {
+            // calculate eagerly so we know if we outgrow
+            var newSize = _dense != null && index >= _dense.Length
+                ? System.Math.Max(index, System.Math.Max(_dense.Length, 2)) * 2
+                : 0;
+
+            bool canUseDense = _dense != null
+                               && index < MaxDenseArrayLength
+                               && newSize < MaxDenseArrayLength
+                               && index < _dense.Length + 50; // looks sparse
+
+            if (canUseDense)
+            {
+                if (index >= _dense.Length)
+                {
+                    EnsureCapacity((uint) newSize);
+                }
+
+                _dense[index] = desc;
+            }
+            else
+            {
+                if (_dense != null)
+                {
+                    _sparse = new Dictionary<uint, PropertyDescriptor>(_dense.Length <= 1024 ? _dense.Length : 0);
+                    // need to move data
+                    for (uint i = 0; i < _dense.Length; ++i)
+                    {
+                        if (_dense[i] != null)
+                        {
+                            _sparse[i] = _dense[i];
+                        }
+                    }
+
+                    _dense = null;
+                }
+
+                _sparse[index] = desc;
+            }
+        }
+
+        internal void EnsureCapacity(uint capacity)
+        {
+            if (capacity > _dense.Length)
+            {
+                // need to grow
+                var newArray = new PropertyDescriptor[capacity];
+                System.Array.Copy(_dense, newArray, _dense.Length);
+                _dense = newArray;
+            }
         }
     }
-}
+}

File diff suppressed because it is too large
+ 285 - 297
Jint/Native/Array/ArrayPrototype.cs


+ 5 - 4
Jint/Native/Global/GlobalObject.cs

@@ -355,6 +355,9 @@ namespace Jint.Native.Global
             '~', '*', '\'', '(', ')'
         };
 
+        private static readonly char[] UnescapedUriSet = UriReserved.Concat(UriUnescaped).Concat(new[] { '#' }).ToArray();
+        private static readonly char[] ReservedUriSet = UriReserved.Concat(new[] { '#' }).ToArray();
+
         private const string HexaMap = "0123456789ABCDEF";
 
         private static bool IsValidHexaChar(char c)
@@ -373,9 +376,8 @@ namespace Jint.Native.Global
         public JsValue EncodeUri(JsValue thisObject, JsValue[] arguments)
         {
             var uriString = TypeConverter.ToString(arguments.At(0));
-            var unescapedUriSet = UriReserved.Concat(UriUnescaped).Concat(new[] { '#' }).ToArray();
 
-            return Encode(uriString, unescapedUriSet);
+            return Encode(uriString, UnescapedUriSet);
         }
 
 
@@ -498,9 +500,8 @@ namespace Jint.Native.Global
         public JsValue DecodeUri(JsValue thisObject, JsValue[] arguments)
         {
             var uriString = TypeConverter.ToString(arguments.At(0));
-            var reservedUriSet = UriReserved.Concat(new[] { '#' }).ToArray();
 
-            return Decode(uriString, reservedUriSet);
+            return Decode(uriString, ReservedUriSet);
         }
 
         public JsValue DecodeUriComponent(JsValue thisObject, JsValue[] arguments)

+ 1 - 1
Jint/Native/JsValue.cs

@@ -478,7 +478,7 @@ namespace Jint.Native
                 {
                     var array = (System.Array)v;
 
-                    var jsArray = engine.Array.Construct(Arguments.Empty);
+                    var jsArray = engine.Array.Construct(a.Length);
                     foreach (var item in array)
                     {
                         var jsItem = JsValue.FromObject(engine, item);

+ 3 - 2
Jint/Native/Json/JsonParser.cs

@@ -614,8 +614,9 @@ namespace Jint.Native.Json
 
         public ObjectInstance CreateArrayInstance(IEnumerable<JsValue> values)
         {
-            var jsArray = _engine.Array.Construct(Arguments.Empty);
-            _engine.Array.PrototypeObject.Push(jsArray, values.ToArray());
+            var jsValues = values.ToArray();
+            var jsArray = _engine.Array.Construct(jsValues.Length);
+            _engine.Array.PrototypeObject.Push(jsArray, jsValues);
             return jsArray;
         }
 

+ 21 - 19
Jint/Native/Object/ObjectConstructor.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.String;
 using Jint.Runtime;
@@ -140,22 +141,25 @@ namespace Jint.Native.Object
                 throw new JavaScriptException(Engine.TypeError);
             }
 
-            var array = Engine.Array.Construct(Arguments.Empty);
-            var n = 0;
+            uint n = 0;
 
-            var s = o as StringInstance;
-            if (s != null)
+            ArrayInstance array = null;
+            var ownProperties = o.GetOwnProperties().ToList();
+            if (o is StringInstance s)
             {
-                for (var i = 0; i < s.PrimitiveValue.AsString().Length; i++)
+                var length = s.PrimitiveValue.AsString().Length;
+                array = Engine.Array.Construct(ownProperties.Count + length);
+                for (var i = 0; i < length; i++)
                 {
-                    array.DefineOwnProperty(TypeConverter.ToString(n), new PropertyDescriptor(TypeConverter.ToString(i), true, true, true), false);
+                    array.SetIndexValue(n, TypeConverter.ToString(i), throwOnError: false);
                     n++;
                 }
             }
 
-            foreach (var p in o.GetOwnProperties())
+            array = array ?? Engine.Array.Construct(ownProperties.Count);
+            foreach (var p in ownProperties)
             {
-                array.DefineOwnProperty(TypeConverter.ToString(n), new PropertyDescriptor(p.Key, true, true, true), false);
+                array.SetIndexValue(n, p.Key, false);
                 n++;
             }
 
@@ -243,7 +247,8 @@ namespace Jint.Native.Object
                 throw new JavaScriptException(Engine.TypeError);
             }
 
-            foreach (var prop in o.GetOwnProperties())
+            var properties = new List<KeyValuePair<string, PropertyDescriptor>>(o.GetOwnProperties());
+            foreach (var prop in properties)
             {
                 if (prop.Value.Configurable.HasValue && prop.Value.Configurable.Value)
                 {
@@ -267,10 +272,10 @@ namespace Jint.Native.Object
                 throw new JavaScriptException(Engine.TypeError);
             }
 
-            var keys = o.GetOwnProperties().Select(x => x.Key);
-            foreach (var p in keys)
+            var properties = new List<KeyValuePair<string, PropertyDescriptor>>(o.GetOwnProperties());
+            foreach (var p in properties)
             {
-                var desc = o.GetOwnProperty(p);
+                var desc = o.GetOwnProperty(p.Key);
                 if (desc.IsDataDescriptor())
                 {
                     if (desc.Writable.HasValue && desc.Writable.Value)
@@ -282,7 +287,7 @@ namespace Jint.Native.Object
                 {
                     desc.Configurable = false;
                 }
-                o.DefineOwnProperty(p, desc, true);
+                o.DefineOwnProperty(p.Key, desc, true);
             }
 
             o.Extensible = false;
@@ -387,15 +392,12 @@ namespace Jint.Native.Object
                 .Where(x => x.Value.Enumerable.HasValue && x.Value.Enumerable.Value)
                 .ToArray();
             var n = enumerableProperties.Length;
-            var array = Engine.Array.Construct(new JsValue[] {n});
-            var index = 0;
+            var array = Engine.Array.Construct(new JsValue[] {n}, (uint) n);
+            uint index = 0;
             foreach (var prop in enumerableProperties)
             {
                 var p = prop.Key;
-                array.DefineOwnProperty(
-                    TypeConverter.ToString(index),
-                    new PropertyDescriptor(p, true, true, true),
-                    false);
+                array.SetIndexValue(index, p, throwOnError: false);
                 index++;
             }
             return array;

+ 48 - 9
Jint/Native/Object/ObjectInstance.cs

@@ -92,7 +92,7 @@ namespace Jint.Native.Object
             if (_prototype != null)
             {
                 yield return new KeyValuePair<string, PropertyDescriptor>(PropertyNamePrototype, _prototype);
-            }
+        }
             if (_constructor != null)
             {
                 yield return new KeyValuePair<string, PropertyDescriptor>(PropertyNameConstructor, _constructor);
@@ -103,7 +103,7 @@ namespace Jint.Native.Object
             }
 
             if (_properties != null)
-            {
+        {
                 foreach (var pair in _properties.GetEnumerator())
                 {
                     yield return pair;
@@ -171,7 +171,7 @@ namespace Jint.Native.Object
             if (propertyName == PropertyNamePrototype)
             {
                 return _prototype != null;
-            }
+        }
             if (propertyName == PropertyNameConstructor)
             {
                 return _constructor != null;
@@ -191,7 +191,7 @@ namespace Jint.Native.Object
             if (propertyName == PropertyNamePrototype)
             {
                 _prototype = null;
-            }
+        }
             if (propertyName == PropertyNameConstructor)
             {
                 _constructor = null;
@@ -254,13 +254,13 @@ namespace Jint.Native.Object
                 return _prototype ?? PropertyDescriptor.Undefined;
             }
             if (propertyName == PropertyNameConstructor)
-            {
+                {
                 return _constructor ?? PropertyDescriptor.Undefined;
-            }
+                }
             if (propertyName == PropertyNameLength)
-            {
+                {
                 return _length ?? PropertyDescriptor.Undefined;
-            }
+                }
 
             PropertyDescriptor x;
             if (_properties != null && _properties.TryGetValue(propertyName, out x))
@@ -279,7 +279,7 @@ namespace Jint.Native.Object
             {
                 _prototype = desc;
                 return;
-            }
+        }
             if (propertyName == PropertyNameConstructor)
             {
                 _constructor = desc;
@@ -321,6 +321,45 @@ namespace Jint.Native.Object
             return Prototype.GetProperty(propertyName);
         }
 
+        public bool TryGetValue(string propertyName, out JsValue value)
+        {
+            value = JsValue.Undefined;
+            var desc = GetOwnProperty(propertyName);
+            if (desc != null && desc != PropertyDescriptor.Undefined)
+            {
+                if (desc == PropertyDescriptor.Undefined)
+                {
+                    return false;
+                }
+
+                if (desc.IsDataDescriptor() && desc.Value != null)
+                {
+                    value = desc.Value;
+                    return true;
+                }
+
+                var getter = desc.Get != null ? desc.Get : Undefined.Instance;
+
+                if (getter.IsUndefined())
+                {
+                    value = Undefined.Instance;
+                    return false;
+                }
+
+                // if getter is not undefined it must be ICallable
+                var callable = getter.TryCast<ICallable>();
+                value = callable.Call(this, Arguments.Empty);
+                return true;
+            }
+
+            if(Prototype == null)
+            {
+                return false;
+            }
+
+            return Prototype.TryGetValue(propertyName, out value);
+        }
+
         /// <summary>
         /// Sets the specified named property to the value
         /// of the second parameter. The flag controls

+ 7 - 6
Jint/Native/RegExp/RegExpPrototype.cs

@@ -1,4 +1,5 @@
 using System.Text.RegularExpressions;
+using Jint.Native.Array;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
@@ -80,7 +81,7 @@ namespace Jint.Native.RegExp
             if (R.Source == "(?:)")  // Reg Exp is really ""
             {
                 // "aaa".match() => [ '', index: 0, input: 'aaa' ]
-                var aa = InitReturnValueArray(Engine.Array.Construct(Arguments.Empty), s, 1, 0);
+                var aa = InitReturnValueArray((ArrayInstance) Engine.Array.Construct(Arguments.Empty), s, 1, 0);
                 aa.DefineOwnProperty("0", new PropertyDescriptor("", true, true, true), true);
                 return aa;
             }
@@ -109,19 +110,19 @@ namespace Jint.Native.RegExp
             var n = r.Groups.Count;
             var matchIndex = r.Index;
 
-            var a = InitReturnValueArray(Engine.Array.Construct(Arguments.Empty), s, n, matchIndex);
+            var a = InitReturnValueArray((ArrayInstance) Engine.Array.Construct(Arguments.Empty), s, n, matchIndex);
 
-            for (var k = 0; k < n; k++)
+            for (uint k = 0; k < n; k++)
             {
-                var group = r.Groups[k];
+                var group = r.Groups[(int) k];
                 var value = group.Success ? group.Value : Undefined.Instance;
-                a.DefineOwnProperty(TypeConverter.ToString(k), new PropertyDescriptor(value, true, true, true), true);
+                a.SetIndexValue(k, value, throwOnError: true);
             }
 
             return a;
         }
 
-        private static Object.ObjectInstance InitReturnValueArray(Object.ObjectInstance array, string inputValue, int lengthValue, int indexValue)
+        private static ArrayInstance InitReturnValueArray(ArrayInstance array, string inputValue, int lengthValue, int indexValue)
         {
             array.DefineOwnProperty("index", new PropertyDescriptor(indexValue, writable: true, enumerable: true, configurable: true), true);
             array.DefineOwnProperty("input", new PropertyDescriptor(inputValue, writable: true, enumerable: true, configurable: true), true);

+ 9 - 9
Jint/Native/String/StringPrototype.cs

@@ -269,12 +269,12 @@ namespace Jint.Native.String
 
                 if (!match.Success) // No match at all return the string in an array
                 {
-                    a.DefineOwnProperty("0", new PropertyDescriptor(s, true, true, true), false);
+                    a.SetIndexValue(0, s, throwOnError: false);
                     return a;
                 }
 
                 int lastIndex = 0;
-                int index = 0;
+                uint index = 0;
                 while (match.Success && index < limit)
                 {
                     if (match.Length == 0 && (match.Index == 0 || match.Index == len || match.Index == lastIndex))
@@ -284,7 +284,7 @@ namespace Jint.Native.String
                     }
 
                     // Add the match results to the array.
-                    a.DefineOwnProperty(TypeConverter.ToString(index++), new PropertyDescriptor(s.Substring(lastIndex, match.Index - lastIndex), true, true, true), false);
+                    a.SetIndexValue(index++, s.Substring(lastIndex, match.Index - lastIndex), throwOnError: false);
 
                     if (index >= limit)
                     {
@@ -301,7 +301,7 @@ namespace Jint.Native.String
                             item = match.Groups[i].Value;
                         }
 
-                        a.DefineOwnProperty(TypeConverter.ToString(index++), new PropertyDescriptor(item, true, true, true ), false);
+                        a.SetIndexValue(index++, item, throwOnError: false);
 
                         if (index >= limit)
                         {
@@ -312,7 +312,7 @@ namespace Jint.Native.String
                     match = match.NextMatch();
                     if (!match.Success) // Add the last part of the split
                     {
-                        a.DefineOwnProperty(TypeConverter.ToString(index++), new PropertyDescriptor(s.Substring(lastIndex), true, true, true), false);
+                        a.SetIndexValue(index++, s.Substring(lastIndex), throwOnError: false);
                     }
                 }
 
@@ -338,7 +338,7 @@ namespace Jint.Native.String
 
                 for (int i = 0; i < segments.Count && i < limit; i++)
                 {
-                    a.DefineOwnProperty(TypeConverter.ToString(i), new PropertyDescriptor(segments[i], true, true, true), false);
+                    a.SetIndexValue((uint) i, segments[i], throwOnError: false);
                 }
 
                 return a;
@@ -578,9 +578,9 @@ namespace Jint.Native.String
             else
             {
                 rx.Put("lastIndex", 0, false);
-                var a = Engine.Array.Construct(Arguments.Empty);
+                var a = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
                 double previousLastIndex = 0;
-                var n = 0;
+                uint n = 0;
                 var lastMatch = true;
                 while (lastMatch)
                 {
@@ -599,7 +599,7 @@ namespace Jint.Native.String
                         }
 
                         var matchStr = result.Get("0");
-                        a.DefineOwnProperty(TypeConverter.ToString(n), new PropertyDescriptor(matchStr, true, true, true), false);
+                        a.SetIndexValue(n, matchStr, throwOnError: false);
                         n++;
                     }
                 }

+ 30 - 0
Jint/Runtime/Descriptors/PropertyDescriptor.cs

@@ -186,5 +186,35 @@ namespace Jint.Runtime.Descriptors
 
             return obj;
         }
+
+        internal bool TryGetValue(ObjectInstance thisArg, out JsValue value)
+        {
+            value = JsValue.Undefined;
+
+            if (this == Undefined)
+            {
+                value = JsValue.Undefined;
+                return false;
+            }
+
+            if (IsDataDescriptor())
+            {
+                var val = Value;
+                if (val != null)
+                {
+                    value = val;
+                    return true;
+                }
+            }
+
+            if (Get != null && !Get.IsUndefined())
+            {
+                // if getter is not undefined it must be ICallable
+                var callable = Get.TryCast<ICallable>();
+                value = callable.Call(thisArg, Arguments.Empty);
+            }
+
+            return true;
+        }
     }
 }

+ 3 - 4
Jint/Runtime/ExpressionIntepreter.cs

@@ -910,7 +910,7 @@ namespace Jint.Runtime
             var result = Undefined.Instance;
             foreach (var expression in sequenceExpression.Expressions)
             {
-                result = _engine.GetValue(_engine.EvaluateExpression(expression));
+                result = _engine.GetValue(_engine.EvaluateExpression(expression.As<Expression>()));
             }
 
             return result;
@@ -987,15 +987,14 @@ namespace Jint.Runtime
         {
             var elements = arrayExpression.Elements;
             var count = elements.Count;
-            var a = _engine.Array.Construct(new JsValue[] { count });
+            var a = _engine.Array.Construct(new JsValue[] {count}, (uint) count);
             for (var n = 0; n < count; n++)
             {
                 var expr = elements[n];
                 if (expr != null)
                 {
                     var value = _engine.GetValue(EvaluateExpression(expr.As<Expression>()));
-                    a.DefineOwnProperty(TypeConverter.ToString(n),
-                        new PropertyDescriptor(value, true, true, true), false);
+                    a.SetIndexValue((uint) n, value, throwOnError: false);
                 }
             }
 

+ 2 - 6
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -48,14 +48,10 @@ namespace Jint.Runtime.Interop
                         var arrayInstance = arguments[i].AsArray();
                         var len = TypeConverter.ToInt32(arrayInstance.Get("length"));
                         var result = new JsValue[len];
-                        for (var k = 0; k < len; k++)
+                        for (uint k = 0; k < len; k++)
                         {
-                            var pk = TypeConverter.ToString(k);
-                            result[k] = arrayInstance.HasProperty(pk)
-                                ? arrayInstance.Get(pk)
-                                : JsValue.Undefined;
+                            result[k] = arrayInstance.TryGetValue(k, out var value) ? value : JsValue.Undefined;
                         }
-
                         parameters[i] = result;
                     }
                     else

+ 1 - 1
Jint/Runtime/MruPropertyCache.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 
 namespace Jint.Runtime
 {
-    public class MruPropertyCache<TKey, TValue> : IDictionary<TKey, TValue>
+    internal class MruPropertyCache<TKey, TValue> : IDictionary<TKey, TValue>
     {
         private IDictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
         private LinkedList<KeyValuePair<TKey, TValue>> _list;

+ 1 - 1
Jint/Runtime/MruPropertyCache2.cs

@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
 
 namespace Jint.Runtime
 {
-    internal class MruPropertyCache2<TKey, TValue> where TValue : class
+    internal class MruPropertyCache2<TKey, TValue> where TKey : class where TValue : class
     {
         private Dictionary<TKey, TValue> _dictionary;
         private bool _set;

Some files were not shown because too many files changed in this diff