Преглед на файлове

Function call performance improvements (#703)

* use adapting HybridDictionary for DeclarativeEnvironmentRecord
* lazily materialize arguments when needed
* return ArgumentsInstance to pool when we can
* return compatibility with RavenDB usage
* remove function callback logic from DeclarativeEnvironmentRecord
Marko Lahma преди 5 години
родител
ревизия
026ebc79d2

+ 74 - 0
Jint.Benchmark/DictionaryBenchmark.cs

@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using Jint.Collections;
+using BenchmarkDotNet.Attributes;
+
+namespace Jint.Benchmark
+{
+    [MemoryDiagnoser]
+    public class DictionaryBenchmark
+    {
+        private static readonly string[] _keys =
+        {
+            "some",
+            "key and",
+            "another",
+            "varying",
+            "---the --",
+            "keys and more",
+            "aa bbb",
+            "asasd asd ",
+            "asdsad asd as s",
+            "asdadsasa",
+            "23323232323",
+            "asdadsada sa213"
+        };
+
+        [Params(0, 2, 3, 5, 8, 9, 10)]
+        public int N { get; set; }
+
+        [Benchmark]
+        public void HybridDictionary()
+        {
+            var hybridDictionary = new HybridDictionary<string, object>();
+            for (var i = 0; i < N; i++)
+            {
+                hybridDictionary.Add(_keys[i], _keys);
+            }
+
+            foreach (var key in _keys)
+            {
+                hybridDictionary.ContainsKey(key);
+            }
+        }
+
+        [Benchmark]
+        public void Dictionary()
+        {
+            var dictionary = new Dictionary<string, object>();
+            for (var i = 0; i < N; i++)
+            {
+                dictionary.Add(_keys[i], _keys);
+            }
+
+            foreach (var key in _keys)
+            {
+                dictionary.ContainsKey(key);
+            }
+        }
+
+        [Benchmark]
+        public void StringDictionarySlim()
+        {
+            var dictionary = new StringDictionarySlim<object>();
+            for (var i = 0; i < N; i++)
+            {
+                dictionary[_keys[i]] =_keys;
+            }
+
+            foreach (var key in _keys)
+            {
+                dictionary.ContainsKey(key);
+            }
+        }
+    }
+}

+ 2 - 0
Jint.Benchmark/Jint.Benchmark.csproj

@@ -12,6 +12,8 @@
     <GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
     <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
     <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
+    <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
+    <SignAssembly>true</SignAssembly>
     <LangVersion>latest</LangVersion>
   </PropertyGroup>
   <ItemGroup>

+ 13 - 75
Jint.Benchmark/UncacheableExpressionsBenchmark.cs

@@ -21,11 +21,18 @@ namespace Jint.Benchmark
         private string targetObject;
         private JsValue[] targetJsObject;
 
-        private const string script = @"
+        private const string NonArrowFunctionScript = @"
 function output(d) {
     var doc = d.SubDocuments.find(function(x){return x.Id==='testing';});
     return { Id : d.Id, Deleted : d.Deleted, SubTestId : (doc!==null&&doc!==undefined)?doc.Id:null, Values : d.SubDocuments.map(function(x){return {TargetId:x.TargetId,TargetValue:x.TargetValue,SubDocuments:x.SubDocuments.filter(function(s){return (s!==null&&s!==undefined);}).map(function(s){return {TargetId:s.TargetId,TargetValue:s.TargetValue};})};}) };
 }
+";
+
+        private const string ArrowFunctionScript = @"
+function output(d) {
+    var doc = d.SubDocuments.find(x => x.Id==='testing');
+    return { Id : d.Id, Deleted : d.Deleted, SubTestId : (doc!==null&&doc!==undefined)?doc.Id:null, Values : d.SubDocuments.map(x => ({ TargetId:x.TargetId,TargetValue:x.TargetValue,SubDocuments:x.SubDocuments.filter(s => (s!==null&&s!==undefined)).map(s => ({ TargetId: s.TargetId, TargetValue: s.TargetValue}))})) };
+}
 ";
 
         private Engine engine;
@@ -72,7 +79,7 @@ function output(d) {
                 }
             }
 
-            CreateEngine();
+            CreateEngine(Arrow ? ArrowFunctionScript : NonArrowFunctionScript);
         }
 
         private static void InitializeEngine(Options options)
@@ -87,6 +94,9 @@ function output(d) {
         [Params(500)]
         public int N { get; set; }
 
+        [Params(true, false)]
+        public bool Arrow { get; set; }
+
         [Benchmark]
         public void Benchmark()
         {
@@ -97,84 +107,12 @@ function output(d) {
             }
         }
 
-        private void CreateEngine()
+        private void CreateEngine(string script)
         {
             engine = new Engine(InitializeEngine);
-            engine.Execute(Polyfills);
             engine.Execute(script);
             engine.Execute(targetObject);
             targetJsObject = new[] {engine.GetValue("d")};
         }
-
-        private const string Polyfills = @"
-//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
-if (!String.prototype.endsWith) {
-    String.prototype.endsWith = function (searchStr, position) {
-        if (!(position < this.length))
-            position = this.length;
-        else
-            position |= 0; // round position
-        return this.substr(position - searchStr.length,
-            searchStr.length) === searchStr;
-    };
-}
-//https://github.com/jsPolyfill/Array.prototype.find/blob/master/find.js
-if (!Array.prototype.find) {
-    Array.prototype.find = Array.prototype.find || function(callback) {
-        if (this === null) {
-            throw new TypeError('Array.prototype.find called on null or undefined');
-        } else if (typeof callback !== 'function') {
-            throw new TypeError('callback must be a function');
-        }
-        var list = Object(this);
-        // Makes sures is always has an positive integer as length.
-        var length = list.length >>> 0;
-        var thisArg = arguments[1];
-        for (var i = 0; i < length; i++) {
-            var element = list[i];
-            if ( callback.call(thisArg, element, i, list) ) {
-                return element;
-            }
-        }
-    };
-}
-
-if (!Array.prototype.fastFilter) {
-    Array.prototype.fastFilter = function(callback) {
-        var results = [];
-        var item;
-        var len = this.length;
-        for (var i = 0, len = len; i < len; i++) {
-            item = this[i];
-            if (callback(item)) results.push(item);
-        }
-        return results;
-    }
-}
-
-if (!Array.prototype.fastMap) {
-    Array.prototype.fastMap = function(callback) {
-        var h = [];
-        var len = this.length;
-        for (var i = 0, len = len; i < len; i++) {
-            h.push(callback(this[i]));
-        }
-        return h;
-    }
-}
-
-
-if (!Array.prototype.fastFind) {
-    Array.prototype.fastFind = function(callback) {
-        var item;
-        var len = this.length;
-        for (var i = 0, len = len; i < len; i++) {
-            item = this[i];
-            if (callback(item)) return item;
-        }
-    }
-}
-
-";
     }
 }

+ 3 - 0
Jint.Tests.Ecma/Jint.Tests.Ecma.csproj

@@ -13,4 +13,7 @@
     <PackageReference Include="xunit.runner.console" Version="2.4.1" />
     <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
   </ItemGroup>
+  <ItemGroup>
+    <None Remove="TestCases\ch*\**" />
+  </ItemGroup>
 </Project>

+ 4 - 0
Jint.Tests.Test262/Jint.Tests.Test262.csproj

@@ -14,4 +14,8 @@
     <PackageReference Include="xunit.runner.console" Version="2.4.1" />
     <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
   </ItemGroup>
+  <ItemGroup>
+    <None Remove="harness\**" />
+    <None Remove="test\**" />
+  </ItemGroup>
 </Project>

+ 3 - 0
Jint/AssemblyInfoExtras.cs

@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Jint.Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]

+ 205 - 0
Jint/Collections/HybridDictionary.cs

@@ -0,0 +1,205 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Jint.Collections
+{
+    internal sealed class HybridDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> where TKey : IEquatable<TKey>
+    {
+        private const int CutoverPoint = 9;
+        private const int InitialDictionarySize = 13;
+        private const int FixedSizeCutoverPoint = 6;
+
+        // Instance variables. This keeps the HybridDictionary very light-weight when empty
+        private ListDictionary<TKey, TValue> _list;
+        private Dictionary<TKey, TValue> _dictionary;
+
+        public HybridDictionary()
+        {
+        }
+
+        public HybridDictionary(int initialSize)
+        {
+            if (initialSize >= FixedSizeCutoverPoint)
+            {
+                _dictionary = new Dictionary<TKey, TValue>(initialSize);
+            }
+        }
+
+        public TValue this[TKey key]
+        {
+            get
+            {
+                TryGetValue(key, out var value);
+                return value;
+            }
+            set
+            {
+                if (_dictionary != null)
+                {
+                    _dictionary[key] = value;
+                }
+                else if (_list != null)
+                {
+                    if (_list.Count >= CutoverPoint - 1)
+                    {
+                        SwitchToDictionary();
+                        _dictionary[key] = value;
+                    }
+                    else
+                    {
+                        _list[key] = value;
+                    }
+                }
+                else
+                {
+                    _list = new ListDictionary<TKey, TValue>();
+                    _list[key] = value;
+                }
+            }
+        }
+
+        public bool TryGetValue(TKey key, out TValue value)
+        {
+            if (_dictionary != null)
+            {
+                return _dictionary.TryGetValue(key, out value);
+            }
+
+            if (_list != null)
+            {
+                return _list.TryGetValue(key, out value);
+            }
+
+            value = default;
+            return false;
+        }
+
+        private void SwitchToDictionary()
+        {
+            var dictionary = new Dictionary<TKey, TValue>(InitialDictionarySize);
+            foreach (var pair in _list)
+            {
+                dictionary[pair.Key] = pair.Value;
+            }
+            _dictionary = dictionary;
+            _list = null;
+        }
+
+        public int Count
+        {
+            get
+            {
+                if (_dictionary != null)
+                {
+                    return _dictionary.Count;
+                }
+
+                return _list?.Count ?? 0;
+            }
+        }
+
+        public void Add(TKey key, TValue value)
+        {
+            if (_dictionary != null)
+            {
+                _dictionary.Add(key, value);
+            }
+            else
+            {
+                if (_list == null)
+                {
+                    _list = new ListDictionary<TKey, TValue>();
+                    _list.Add(key, value);
+                }
+                else
+                {
+                    if (_list.Count + 1 >= CutoverPoint)
+                    {
+                        SwitchToDictionary();
+                        _dictionary.Add(key, value);
+                    }
+                    else
+                    {
+                        _list.Add(key, value);
+                    }
+                }
+            }
+        }
+
+        public void Clear()
+        {
+            if (_dictionary != null)
+            {
+                var dictionary = _dictionary;
+                _dictionary = null;
+                dictionary.Clear();
+            }
+
+            if (_list != null)
+            {
+                var cachedList = _list;
+                _list = null;
+                cachedList.Clear();
+            }
+        }
+
+        public bool ContainsKey(TKey key)
+        {
+            if (_dictionary != null)
+            {
+                return _dictionary.ContainsKey(key);
+            }
+
+            var cachedList = _list;
+            if (cachedList != null)
+            {
+                return cachedList.ContainsKey(key);
+            }
+
+            return false;
+        }
+
+        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
+        {
+            if (_dictionary != null)
+            {
+                return _dictionary.GetEnumerator();
+            }
+
+            if (_list != null)
+            {
+                return _list.GetEnumerator();
+            }
+
+            return Enumerable.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator();
+
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            if (_dictionary != null)
+            {
+                return _dictionary.GetEnumerator();
+            }
+
+            if (_list != null)
+            {
+                return _list.GetEnumerator();
+            }
+
+            return Enumerable.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator();
+        }
+
+        public bool Remove(TKey key)
+        {
+            if (_dictionary != null)
+            {
+                return _dictionary.Remove(key);
+            }
+
+            return _list != null && _list.Remove(key);
+        }
+    }
+}

+ 239 - 0
Jint/Collections/ListDictionary.cs

@@ -0,0 +1,239 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Jint.Runtime;
+
+namespace Jint.Collections
+{
+    internal sealed class ListDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> where TKey : IEquatable<TKey>
+    {
+        private DictionaryNode head;
+        private int count;
+
+        public ListDictionary()
+        {
+        }
+
+        public TValue this[TKey key]
+        {
+            get
+            {
+                TryGetValue(key, out var value);
+                return value;
+            }
+            set
+            {
+                DictionaryNode last = null;
+                DictionaryNode node;
+                for (node = head; node != null; node = node.Next)
+                {
+                    var oldKey = node.Key;
+                    if (oldKey.Equals(key))
+                    {
+                        break;
+                    }
+
+                    last = node;
+                }
+
+                if (node != null)
+                {
+                    // Found it
+                    node.Value = value;
+                    return;
+                }
+
+                // Not found, so add a new one
+                DictionaryNode newNode = new DictionaryNode();
+                newNode.Key = key;
+                newNode.Value = value;
+                if (last != null)
+                {
+                    last.Next = newNode;
+                }
+                else
+                {
+                    head = newNode;
+                }
+
+                count++;
+            }
+        }
+
+        public bool TryGetValue(TKey key, out TValue value)
+        {
+            var node = head;
+            while (node != null)
+            {
+                if (node.Key.Equals(key))
+                {
+                    value = node.Value;
+                    return true;
+                }
+
+                node = node.Next;
+            }
+
+            value = default;
+            return false;
+        }
+
+        public int Count => count;
+
+        public void Add(TKey key, TValue value)
+        {
+            DictionaryNode last = null;
+            DictionaryNode node;
+            for (node = head; node != null; node = node.Next)
+            {
+                var oldKey = node.Key;
+                if (oldKey.Equals(key))
+                {
+                    ExceptionHelper.ThrowArgumentException();
+                }
+
+                last = node;
+            }
+
+            // Not found, so add a new one
+            DictionaryNode newNode = new DictionaryNode();
+            newNode.Key = key;
+            newNode.Value = value;
+            if (last != null)
+            {
+                last.Next = newNode;
+            }
+            else
+            {
+                head = newNode;
+            }
+
+            count++;
+        }
+
+        public void Clear()
+        {
+            count = 0;
+            head = null;
+        }
+
+        public bool ContainsKey(TKey key)
+        {
+            for (var node = head; node != null; node = node.Next)
+            {
+                var oldKey = node.Key;
+                if (oldKey.Equals(key))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public NodeEnumerator GetEnumerator()
+        {
+            return new NodeEnumerator(this);
+        }
+
+        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
+        {
+            return new NodeEnumerator(this);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return new NodeEnumerator(this);
+        }
+
+        public bool Remove(TKey key)
+        {
+            DictionaryNode last = null;
+            DictionaryNode node;
+            for (node = head; node != null; node = node.Next)
+            {
+                var oldKey = node.Key;
+                if (oldKey.Equals(key))
+                {
+                    break;
+                }
+
+                last = node;
+            }
+
+            if (node == null)
+            {
+                return false;
+            }
+
+            if (node == head)
+            {
+                head = node.Next;
+            }
+            else
+            {
+                last.Next = node.Next;
+            }
+
+            count--;
+            return true;
+        }
+
+        internal struct NodeEnumerator : IEnumerator<KeyValuePair<TKey, TValue>>
+        {
+            private readonly ListDictionary<TKey, TValue> _list;
+            private DictionaryNode _current;
+            private bool _start;
+
+            public NodeEnumerator(ListDictionary<TKey, TValue> list)
+            {
+                _list = list;
+                _start = true;
+                _current = null;
+            }
+
+            public KeyValuePair<TKey, TValue> Current
+            {
+                get
+                {
+                    return new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
+                }
+            }
+
+            public bool MoveNext()
+            {
+                if (_start)
+                {
+                    _current = _list.head;
+                    _start = false;
+                }
+                else if (_current != null)
+                {
+                    _current = _current.Next;
+                }
+
+                return (_current != null);
+            }
+
+            void IEnumerator.Reset()
+            {
+                _start = true;
+                _current = null;
+            }
+
+            public void Dispose()
+            {
+            }
+
+            object IEnumerator.Current => _current;
+        }
+
+        internal class DictionaryNode
+        {
+            public TKey Key;
+            public TValue Value;
+            public DictionaryNode Next;
+        }
+    }
+}

+ 13 - 9
Jint/Engine.cs

@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
 using Esprima;
 using Esprima.Ast;
 using Jint.Native;
+using Jint.Native.Argument;
 using Jint.Native.Array;
 using Jint.Native.Boolean;
 using Jint.Native.Date;
@@ -714,7 +715,7 @@ namespace Jint
         }
 
         //  http://www.ecma-international.org/ecma-262/5.1/#sec-10.5
-        internal bool DeclarationBindingInstantiation(
+        internal ArgumentsInstance DeclarationBindingInstantiation(
             DeclarationBindingType declarationBindingType,
             HoistingScope hoistingScope,
             FunctionInstance functionInstance,
@@ -723,27 +724,30 @@ namespace Jint
             var env = ExecutionContext.VariableEnvironment._record;
             bool configurableBindings = declarationBindingType == DeclarationBindingType.EvalCode;
             var strict = StrictModeScope.IsStrictModeCode;
+            ArgumentsInstance argsObj = null;
 
             var der = env as DeclarativeEnvironmentRecord;
-            bool canReleaseArgumentsInstance = false;
             if (declarationBindingType == DeclarationBindingType.FunctionCode)
             {
-                var argsObj = _argumentsInstancePool.Rent(functionInstance, functionInstance._formalParameters, arguments, env, strict);
-                canReleaseArgumentsInstance = true;
+                // arrow functions don't needs arguments
+                var arrowFunctionInstance = functionInstance as ArrowFunctionInstance;
+                argsObj = arrowFunctionInstance is null
+                    ? _argumentsInstancePool.Rent(functionInstance, functionInstance._formalParameters, arguments, env, strict)
+                    : null;
 
                 var functionDeclaration = (functionInstance as ScriptFunctionInstance)?.FunctionDeclaration ??
-                    (functionInstance as ArrowFunctionInstance)?.FunctionDeclaration;
+                                          arrowFunctionInstance?.FunctionDeclaration;
 
                 if (!ReferenceEquals(der, null))
                 {
-                    der.AddFunctionParameters(functionInstance, arguments, argsObj, functionDeclaration);
+                    der.AddFunctionParameters(arguments, argsObj, functionDeclaration);
                 }
                 else
                 {
                     // TODO: match functionality with DeclarationEnvironmentRecord.AddFunctionParameters here
                     // slow path
                     var parameters = functionInstance._formalParameters;
-                    for (var i = 0; i < parameters.Length; i++)
+                    for (uint i = 0; i < (uint) parameters.Length; i++)
                     {
                         Key argName = parameters[i];
                         var v = i + 1 > arguments.Length ? Undefined.Instance : arguments[i];
@@ -770,7 +774,7 @@ namespace Jint
             var variableDeclarations = hoistingScope.VariableDeclarations;
             if (variableDeclarations.Count == 0)
             {
-                return canReleaseArgumentsInstance;
+                return argsObj;
             }
 
             // process all variable declarations in the current parser scope
@@ -803,7 +807,7 @@ namespace Jint
                 }
             }
 
-            return canReleaseArgumentsInstance;
+            return argsObj;
         }
 
         private void AddFunctionDeclarations(

+ 2 - 0
Jint/Key.cs

@@ -90,11 +90,13 @@ namespace Jint
 
         public static implicit operator string(Key key) => key.Name;
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool operator ==(in Key a, Key b)
         {
             return a.HashCode == b.HashCode && a.Name == b.Name && a._symbolIdentity == b._symbolIdentity;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool operator !=(in Key a, Key b)
         {
             return a.HashCode != b.HashCode || a.Name != b.Name || a._symbolIdentity != b._symbolIdentity;

+ 24 - 3
Jint/Native/Argument/ArgumentsInstance.cs

@@ -19,9 +19,10 @@ namespace Jint.Native.Argument
 
         private FunctionInstance _func;
         private string[] _names;
-        internal JsValue[] _args;
+        private JsValue[] _args;
         private EnvironmentRecord _env;
         private bool _strict;
+        private bool _canReturnToPool;
 
         internal ArgumentsInstance(Engine engine) : base(engine, objectClass: "Arguments")
         {
@@ -40,11 +41,15 @@ namespace Jint.Native.Argument
             _env = env;
             _strict = strict;
 
+            _canReturnToPool = true;
+
             ClearProperties();
         }
 
         protected override void Initialize()
         {
+            _canReturnToPool = false;
+
             var args = _args;
             SetOwnProperty(KnownKeys.Length, new PropertyDescriptor(args.Length, PropertyFlag.NonEnumerable));
 
@@ -70,7 +75,7 @@ namespace Jint.Native.Argument
                         var name = _names[i];
                         if (!_strict && !mappedNamed.Contains(name))
                         {
-                            map = map ?? Engine.Object.Construct(Arguments.Empty);
+                            map ??= Engine.Object.Construct(Arguments.Empty);
                             mappedNamed.Add(name);
                             map.SetOwnProperty(indexKey, new ClrAccessDescriptor(_env, Engine, name));
                         }
@@ -225,8 +230,10 @@ namespace Jint.Native.Argument
             return base.Delete(propertyName);
         }
 
-        internal void PersistArguments()
+        internal override JsValue Clone()
         {
+            // there's an assignment or return value of function, need to create persistent state
+
             EnsureInitialized();
 
             var args = _args;
@@ -234,8 +241,22 @@ namespace Jint.Native.Argument
             System.Array.Copy(args, copiedArgs, args.Length);
             _args = copiedArgs;
 
+            _canReturnToPool = false;
+
+            return this;
+        }
+
+        internal void FunctionWasCalled()
+        {
             // should no longer expose arguments which is special name
             ParameterMap = null;
+
+            if (_canReturnToPool)
+            {
+                _engine._argumentsInstancePool.Return(this);
+                // prevent double-return
+                _canReturnToPool = false;
+            }
         }
     }
 }

+ 2 - 2
Jint/Native/Array/ArrayInstance.cs

@@ -245,8 +245,8 @@ namespace Jint.Native.Array
 
             return base.TryGetProperty(propertyName, out descriptor);
         }
-        
-        internal override List<JsValue> GetOwnPropertyKeys(Types types)
+
+        public override List<JsValue> GetOwnPropertyKeys(Types types)
         {
             var properties = new List<JsValue>(_dense?.Length ?? 0 + 1);
             if (_dense != null)

+ 1 - 1
Jint/Native/Date/DateConstructor.cs

@@ -251,7 +251,7 @@ namespace Jint.Native.Date
                 result = PrototypeObject.Utc(result);
             }
 
-            return System.Math.Round(result);
+            return System.Math.Floor(result);
         }
     }
 }

+ 2 - 8
Jint/Native/Function/ArrowFunctionInstance.cs

@@ -63,7 +63,7 @@ namespace Jint.Native.Function
 
                 try
                 {
-                    var argumentInstanceRented = _engine.DeclarationBindingInstantiation(
+                    _engine.DeclarationBindingInstantiation(
                         DeclarationBindingType.FunctionCode,
                         _function._hoistingScope,
                         functionInstance: this,
@@ -71,13 +71,7 @@ namespace Jint.Native.Function
 
                     var result = _function._body.Execute();
 
-                    var value = result.GetValueOrDefault();
-
-                    if (argumentInstanceRented)
-                    {
-                        _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled();
-                        _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled();
-                    }
+                    var value = result.GetValueOrDefault().Clone();
 
                     if (result.Type == CompletionType.Throw)
                     {

+ 2 - 6
Jint/Native/Function/EvalFunctionInstance.cs

@@ -57,7 +57,7 @@ namespace Jint.Native.Function
                                 Engine.EnterExecutionContext(strictVarEnv, strictVarEnv, Engine.ExecutionContext.ThisBinding);
                             }
 
-                            bool argumentInstanceRented = Engine.DeclarationBindingInstantiation(
+                            var argumentsInstance = Engine.DeclarationBindingInstantiation(
                                 DeclarationBindingType.EvalCode,
                                 program.HoistingScope,
                                 functionInstance: this,
@@ -67,11 +67,7 @@ namespace Jint.Native.Function
                             var result = statement.Execute();
                             var value = result.GetValueOrDefault();
 
-                            if (argumentInstanceRented)
-                            {
-                                lexicalEnvironment?._record?.FunctionWasCalled();
-                                _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled();
-                            }
+                            argumentsInstance?.FunctionWasCalled();
 
                             if (result.Type == CompletionType.Throw)
                             {

+ 1 - 1
Jint/Native/Function/FunctionInstance.cs

@@ -146,7 +146,7 @@ namespace Jint.Native.Function
             }
         }
 
-        internal override List<JsValue> GetOwnPropertyKeys(Types types)
+        public override List<JsValue> GetOwnPropertyKeys(Types types)
         {
             var keys = new List<JsValue>();
             if (_prototypeDescriptor != null)

+ 3 - 7
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -90,7 +90,7 @@ namespace Jint.Native.Function
 
                 try
                 {
-                    var argumentInstanceRented = _engine.DeclarationBindingInstantiation(
+                    var argumentsInstance = _engine.DeclarationBindingInstantiation(
                         DeclarationBindingType.FunctionCode,
                         _function._hoistingScope,
                         functionInstance: this,
@@ -98,13 +98,9 @@ namespace Jint.Native.Function
 
                     var result = _function._body.Execute();
 
-                    var value = result.GetValueOrDefault();
+                    var value = result.GetValueOrDefault().Clone();
 
-                    if (argumentInstanceRented)
-                    {
-                        _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled();
-                        _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled();
-                    }
+                    argumentsInstance?.FunctionWasCalled();
 
                     if (result.Type == CompletionType.Throw)
                     {

+ 2 - 2
Jint/Native/Object/ObjectInstance.cs

@@ -158,8 +158,8 @@ namespace Jint.Native.Object
                 }
             }
         }
-        
-        internal virtual List<JsValue> GetOwnPropertyKeys(Types types)
+
+        public virtual List<JsValue> GetOwnPropertyKeys(Types types)
         {
             EnsureInitialized();
 

+ 1 - 1
Jint/Native/Proxy/ProxyInstance.cs

@@ -106,7 +106,7 @@ namespace Jint.Native.Proxy
             return result;
         }
 
-        internal override List<JsValue> GetOwnPropertyKeys(Types types)
+        public override List<JsValue> GetOwnPropertyKeys(Types types)
         {
             if (!TryCallHandler(TrapOwnKeys, new JsValue[] {_target }, out var result))
             {

+ 1 - 1
Jint/Native/RegExp/RegExpInstance.cs

@@ -95,7 +95,7 @@ namespace Jint.Native.RegExp
             }
         }
 
-        internal override List<JsValue> GetOwnPropertyKeys(Types types)
+        public override List<JsValue> GetOwnPropertyKeys(Types types)
         {
             var keys = new List<JsValue>();
             if (_prototypeDescriptor != null)

+ 28 - 51
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -17,16 +17,11 @@ namespace Jint.Runtime.Environments
     /// </summary>
     public sealed class DeclarativeEnvironmentRecord : EnvironmentRecord
     {
-        private readonly StringDictionarySlim<Binding> _dictionary = new StringDictionarySlim<Binding>();
+        private readonly HybridDictionary<Key, Binding> _dictionary = new HybridDictionary<Key, Binding>();
 
         public DeclarativeEnvironmentRecord(Engine engine) : base(engine)
         {
         }
-        
-        private ref Binding GetOrCreateBinding(in Key key)
-        {
-            return ref _dictionary.GetOrAddValueRef(key);
-        }
 
         private bool ContainsKey(in Key key)
         {
@@ -61,11 +56,11 @@ namespace Jint.Runtime.Environments
 
         public override void SetMutableBinding(in Key name, JsValue value, bool strict)
         {
-            ref var binding = ref GetOrCreateBinding(name);
-
+            _dictionary.TryGetValue(name, out var binding);
             if (binding.Mutable)
             {
                 binding.Value = value;
+                _dictionary[name] = binding;
             }
             else
             {
@@ -78,7 +73,7 @@ namespace Jint.Runtime.Environments
 
         public override JsValue GetBindingValue(in Key name, bool strict)
         {
-            ref var binding = ref GetOrCreateBinding(name);
+            _dictionary.TryGetValue(name, out var binding);
             return UnwrapBindingValue(strict, binding);
         }
 
@@ -139,39 +134,50 @@ namespace Jint.Runtime.Environments
         }
 
         internal void AddFunctionParameters(
-            FunctionInstance functionInstance,
             JsValue[] arguments,
             ArgumentsInstance argumentsInstance,
             IFunction functionDeclaration)
         {
-            var parameters = functionDeclaration.Params;
-
             bool empty = _dictionary.Count == 0;
-
-            if (!(functionInstance is ArrowFunctionInstance))
+            if (!(argumentsInstance is null))
             {
                 _dictionary[KnownKeys.Arguments] = new Binding(argumentsInstance, canBeDeleted: false, mutable: true);
             }
 
+            var parameters = functionDeclaration.Params;
             for (var i = 0; i < parameters.Count; i++)
             {
                 SetFunctionParameter(parameters[i], arguments, i, empty);
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void SetFunctionParameter(
             INode parameter,
             JsValue[] arguments,
             int index,
             bool initiallyEmpty)
         {
-            var argument = arguments.Length > index ? arguments[index] : Undefined;
-
             if (parameter is Identifier identifier)
             {
+                var argument = arguments.Length > index ? arguments[index] : Undefined;
                 SetItemSafely(identifier.Name, argument, initiallyEmpty);
             }
-            else if (parameter is RestElement restElement)
+            else
+            {
+                SetFunctionParameterUnlikely(parameter, arguments, index, initiallyEmpty);
+            }
+        }
+
+        private void SetFunctionParameterUnlikely(
+            INode parameter,
+            JsValue[]arguments,
+            int index,
+            bool initiallyEmpty)
+        {
+            var argument = arguments.Length > index ? arguments[index] : Undefined;
+
+            if (parameter is RestElement restElement)
             {
                 // index + 1 == parameters.count because rest is last
                 int restCount = arguments.Length - (index + 1) + 1;
@@ -309,8 +315,7 @@ namespace Jint.Runtime.Environments
                 }
 
                 SetFunctionParameter(assignmentPattern.Left, new []{ argument }, 0, initiallyEmpty);
-            }
-        }
+            }        }
 
         private void SetItemSafely(in Key name, JsValue argument, bool initiallyEmpty)
         {
@@ -322,8 +327,8 @@ namespace Jint.Runtime.Environments
             {
                 if (existing.Mutable)
                 {
-                    ref var b = ref GetOrCreateBinding(name);
-                    b.Value = argument;
+                    existing.Value = argument;
+                    _dictionary[name] = existing;
                 }
                 else
                 {
@@ -355,13 +360,12 @@ namespace Jint.Runtime.Environments
             }
         }
 
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static JsValue HandleAssignmentPatternIfNeeded(IFunction functionDeclaration, JsValue jsValue, int index)
+        internal static JsValue HandleAssignmentPatternIfNeeded(IFunction functionDeclaration, JsValue jsValue, uint index)
         {
             // TODO remove this method, overwrite with above SetFunctionParameter logic
             if (jsValue.IsUndefined()
                 && index < functionDeclaration?.Params.Count
-                && functionDeclaration.Params[index] is AssignmentPattern ap
+                && functionDeclaration.Params[(int) index] is AssignmentPattern ap
                 && ap.Right is Literal l)
             {
                 return JintLiteralExpression.ConvertToJsValue(l);
@@ -369,33 +373,6 @@ namespace Jint.Runtime.Environments
 
             return jsValue;
         }
-        
-        internal override void FunctionWasCalled()
-        {
-            if (_dictionary.TryGetValue(KnownKeys.Arguments, out var arguments) && arguments.Value is ArgumentsInstance argumentsInstance)
-            {
-                argumentsInstance.PersistArguments();
-            }
-
-            // we can safely release arguments only if it doesn't have possibility to escape the scope
-            // so check if someone ever accessed it
-            //if (!(_argumentsBinding.Value is ArgumentsInstance argumentsInstance))
-            //{
-            //    return;
-            //}
-
-            //if (!argumentsInstance._initialized && _argumentsBindingWasAccessed == false)
-            //{
-            //    _engine._argumentsInstancePool.Return(argumentsInstance);
-            //    _argumentsBinding = default;
-            //}
-            //else if (_argumentsBindingWasAccessed != null && argumentsInstance._args.Length > 0)
-            //{
-            //    // we need to ensure we hold on to arguments given
-            //    argumentsInstance.PersistArguments();
-            //    _argumentsBindingWasAccessed = null;
-            //}
-        }
 
         private sealed class ArrayPatternProtocol : IteratorProtocol
         {

+ 0 - 6
Jint/Runtime/Environments/EnvironmentRecord.cs

@@ -80,12 +80,6 @@ namespace Jint.Runtime.Environments
         {
             return ExceptionHelper.ThrowNotSupportedException<bool>();
         }
-
-        /// <summary>
-        /// Informs whether arguments instance was accessed and maybe thus stored,
-        /// which makes it unsuitable for pooling and reuse.
-        /// </summary>
-        internal abstract void FunctionWasCalled();
     }
 }
 

+ 0 - 4
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -136,9 +136,5 @@ namespace Jint.Runtime.Environments
         {
             return ReferenceEquals(_bindingObject, other);
         }
-
-        internal override void FunctionWasCalled()
-        {
-        }
     }
 }

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -354,7 +354,7 @@ namespace Jint.Runtime.Interpreter.Expressions
         {
             for (var i = 0; i < jintExpressions.Length; i++)
             {
-                targetArray[i] = jintExpressions[i].GetValue();
+                targetArray[i] = jintExpressions[i].GetValue().Clone();
             }
         }
 
@@ -387,7 +387,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
                 else
                 {
-                    args.Add(jintExpression.GetValue());
+                    args.Add(jintExpression.GetValue().Clone());
                 }
             }
 

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs

@@ -107,7 +107,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var objectProperty = _properties[i];
                 var valueExpression = _valueExpressions[i];
-                var propValue = valueExpression.GetValue();
+                var propValue = valueExpression.GetValue().Clone();
                 hasSymbols |= objectProperty.Key.IsSymbol;
                 properties[objectProperty.Key] = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable);
             }
@@ -134,7 +134,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 if (property.Kind == PropertyKind.Init || property.Kind == PropertyKind.Data)
                 {
                     var expr = _valueExpressions[i];
-                    var propValue = expr.GetValue();
+                    var propValue = expr.GetValue().Clone();
                     if (expr._expression.IsFunctionWithName())
                     {
                         var functionInstance = (FunctionInstance) propValue;