2
0
Эх сурвалжийг харах

StringDictionarySlim instead of Dictionary/StructDictionary (#554)

#451
Marko Lahma 6 жил өмнө
parent
commit
dc2ce8ceb7

+ 1 - 0
Jint.sln.DotSettings

@@ -4,6 +4,7 @@
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=2CC1F6A6_002D7DCC_002D4C7C_002DA619_002DACE1A4296446_002Fd_003ATestCases/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=44CF8026_002DA797_002D420C_002DA8BC_002D409BB67D32F6_002Fd_003Atest/@EntryIndexedValue">ExplicitlyExcluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=62FFFDBD_002DAB58_002D490D_002D9A50_002DAA7C53BF0409_002Fd_003Atest/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=95ED73F7_002D3519_002D4733_002DB361_002D7790053A1A71/@EntryIndexedValue"></s:String>
 	<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=95ED73F7_002D3519_002D4733_002DB361_002D7790053A1A71/@EntryIndexRemoved">True</s:Boolean>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=95ED73F7_002D3519_002D4733_002DB361_002D7790053A1A71_002Fd_003Aharness/@EntryIndexedValue">ExplicitlyExcluded</s:String>

+ 459 - 0
Jint/Collections/StringDictionarySlim.cs

@@ -0,0 +1,459 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using Jint.Runtime;
+
+namespace Jint.Collections
+{
+    /// <summary>
+    /// DictionarySlim<TKey, TValue> is similar to Dictionary<TKey, TValue> but optimized in three ways:
+    /// 1) It allows access to the value by ref replacing the common TryGetValue and Add pattern.
+    /// 2) It does not store the hash code (assumes it is cheap to equate values).
+    /// 3) It does not accept an equality comparer (assumes Object.GetHashCode() and Object.Equals() or overridden implementation are cheap and sufficient).
+    /// </summary>
+    [DebuggerTypeProxy(typeof(DictionarySlimDebugView<>))]
+    [DebuggerDisplay("Count = {Count}")]
+    internal sealed class StringDictionarySlim<TValue>
+    {
+        // We want to initialize without allocating arrays. We also want to avoid null checks.
+        // Array.Empty would give divide by zero in modulo operation. So we use static one element arrays.
+        // The first add will cause a resize replacing these with real arrays of three elements.
+        // Arrays are wrapped in a class to avoid being duplicated for each <TKey, TValue>
+        private static readonly Entry[] InitialEntries = new Entry[1];
+        private int _count;
+        // 1-based index into _entries; 0 means empty
+        private int[] _buckets;
+        private Entry[] _entries;
+
+        [DebuggerDisplay("({key}, {value})->{next}")]
+        private struct Entry
+        {
+            public string key;
+            public TValue value;
+            // 0-based index of next entry in chain: -1 means end of chain
+            public int next;
+        }
+
+        public StringDictionarySlim()
+        {
+            _buckets = HashHelpers.DictionarySlimSizeOneIntArray;
+            _entries = InitialEntries;
+        }
+
+        public StringDictionarySlim(int capacity)
+        {
+            if (capacity < 2) ExceptionHelper.ThrowArgumentOutOfRangeException();
+            capacity = HashHelpers.PowerOf2(capacity);
+            _buckets = new int[capacity];
+            _entries = new Entry[capacity];
+        }
+
+        public int Count => _count;
+
+        public int Capacity => _entries.Length;
+
+        public bool ContainsKey(string key)
+        {
+            Entry[] entries = _entries;
+            for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1;
+                    (uint)i < (uint)entries.Length; i = entries[i].next)
+            {
+                if (key == entries[i].key)
+                    return true;
+            }
+
+            return false;
+        }
+
+        public TValue GetValueOrDefault(string key)
+        {
+            bool result = TryGetValue(key, out TValue value);
+            return value;
+        }
+
+        public bool TryGetValue(string key, out TValue value)
+        {
+            Entry[] entries = _entries;
+            for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1;
+                    (uint)i < (uint)entries.Length; i = entries[i].next)
+            {
+                if (key == entries[i].key)
+                {
+                    value = entries[i].value;
+                    return true;
+                }
+            }
+
+            value = default;
+            return false;
+        }
+
+        public bool Remove(string key)
+        {
+            Entry[] entries = _entries;
+            int bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
+            int entryIndex = _buckets[bucketIndex] - 1;
+
+            int lastIndex = -1;
+            while (entryIndex != -1)
+            {
+                Entry candidate = entries[entryIndex];
+                if (candidate.key == key)
+                {
+                    if (lastIndex == -1)
+                    {
+                        // Fixup bucket to new head (if any)
+                        _buckets[bucketIndex] = candidate.next + 1;
+                    }
+                    else
+                    {
+                        // Fixup preceding element in chain to point to next (if any)
+                        entries[lastIndex].next = candidate.next;
+                    }
+
+                    // move last item to this index and fix link to it
+                    if (entryIndex != --_count)
+                    {
+                        entries[entryIndex] = entries[_count];
+
+                        bucketIndex = entries[entryIndex].key.GetHashCode() & (_buckets.Length - 1);
+                        lastIndex = _buckets[bucketIndex] - 1;
+
+                        if (lastIndex == _count)
+                        {
+                            // Fixup bucket to this index
+                            _buckets[bucketIndex] = entryIndex + 1;
+                        }
+                        else
+                        {
+                            // Find preceding element in chain and point to this index
+                            while (entries[lastIndex].next != _count)
+                                lastIndex = entries[lastIndex].next;
+                            entries[lastIndex].next = entryIndex;
+                        }
+                    }
+                    entries[_count] = default;
+                    return true;
+                }
+                lastIndex = entryIndex;
+                entryIndex = candidate.next;
+            }
+
+            return false;
+        }
+
+        public void Clear()
+        {
+            int count = _count;
+            if (count > 0)
+            {
+                Array.Clear(_buckets, 0, _buckets.Length);
+                _count = 0;
+                Array.Clear(_entries, 0, count);
+            }
+        }
+
+        public ref TValue this[string key]
+        {
+            get
+            {
+                Entry[] entries = _entries;
+                int bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
+                for (int i = _buckets[bucketIndex] - 1;
+                        (uint)i < (uint)entries.Length; i = entries[i].next)
+                {
+                    if (key == entries[i].key)
+                        return ref entries[i].value;
+                }
+
+                return ref AddKey(key, bucketIndex);
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private ref TValue AddKey(string key, int bucketIndex)
+        {
+            Entry[] entries = _entries;
+
+            if (_count == entries.Length || entries.Length == 1)
+            {
+                entries = Resize();
+                bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
+                // entry indexes were not changed by Resize
+            }
+
+            int entryIndex = _count++;
+            entries[entryIndex].key = key;
+            entries[entryIndex].next = _buckets[bucketIndex] - 1;
+            _buckets[bucketIndex] = entryIndex + 1;
+            return ref entries[entryIndex].value;
+        }
+
+        private Entry[] Resize()
+        {
+            int count = _count;
+            var entries = new Entry[_entries.Length * 2];
+            Array.Copy(_entries, 0, entries, 0, count);
+
+            var newBuckets = new int[entries.Length];
+            while (count-- > 0)
+            {
+                int bucketIndex = entries[count].key.GetHashCode() & (newBuckets.Length - 1);
+                entries[count].next = newBuckets[bucketIndex] - 1;
+                newBuckets[bucketIndex] = count + 1;
+            }
+
+            _buckets = newBuckets;
+            _entries = entries;
+
+            return entries;
+        }
+
+        public KeyCollection Keys => new KeyCollection(this);
+
+        public ValueCollection Values => new ValueCollection(this);
+
+        public void CopyTo(KeyValuePair<string, TValue>[] array, int index)
+        {
+            Entry[] entries = _entries;
+            for (int i = 0; i < _count; i++)
+            {
+                array[index++] = new KeyValuePair<string, TValue>(
+                    entries[i].key,
+                    entries[i].value);
+            }
+        }
+
+        public Enumerator GetEnumerator() => new Enumerator(this); // avoid boxing
+
+        public struct Enumerator : IEnumerator<KeyValuePair<string, TValue>>
+        {
+            private readonly StringDictionarySlim<TValue> _dictionary;
+            private int _index;
+            private KeyValuePair<string, TValue> _current;
+
+            internal Enumerator(StringDictionarySlim<TValue> dictionary)
+            {
+                _dictionary = dictionary;
+                _index = 0;
+                _current = default;
+            }
+
+            public bool MoveNext()
+            {
+                if (_index == _dictionary._count)
+                {
+                    _current = default;
+                    return false;
+                }
+
+                _current = new KeyValuePair<string, TValue>(
+                    _dictionary._entries[_index].key,
+                    _dictionary._entries[_index++].value);
+                return true;
+            }
+
+            public KeyValuePair<string, TValue> Current => _current;
+
+            object IEnumerator.Current => _current;
+
+            void IEnumerator.Reset()
+            {
+                _index = 0;
+            }
+
+            public void Dispose() { }
+        }
+
+        public struct KeyCollection : ICollection<string>, IReadOnlyCollection<string>
+        {
+            private readonly StringDictionarySlim<TValue> _dictionary;
+
+            internal KeyCollection(StringDictionarySlim<TValue> dictionary)
+            {
+                _dictionary = dictionary;
+            }
+
+            public int Count => _dictionary._count;
+
+            bool ICollection<string>.IsReadOnly => true;
+
+            void ICollection<string>.Add(string item) =>
+                ExceptionHelper.ThrowNotSupportedException();
+
+            void ICollection<string>.Clear() =>
+                ExceptionHelper.ThrowNotSupportedException();
+
+            public bool Contains(string item) => _dictionary.ContainsKey(item);
+
+            bool ICollection<string>.Remove(string item) =>
+                ExceptionHelper.ThrowNotSupportedException<bool>();
+
+            public void CopyTo(string[] array, int index)
+            {
+                Entry[] entries = _dictionary._entries;
+                for (int i = 0; i < _dictionary._count; i++)
+                {
+                    array[index++] = entries[i].key;
+                }
+            }
+
+            public Enumerator GetEnumerator() => new Enumerator(_dictionary); // avoid boxing
+            IEnumerator<string> IEnumerable<string>.GetEnumerator() => new Enumerator(_dictionary);
+            IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dictionary);
+
+            public struct Enumerator : IEnumerator<string>
+            {
+                private readonly StringDictionarySlim<TValue> _dictionary;
+                private int _index;
+                private string _current;
+
+                internal Enumerator(StringDictionarySlim<TValue> dictionary)
+                {
+                    _dictionary = dictionary;
+                    _index = 0;
+                    _current = default;
+                }
+
+                public string Current => _current;
+
+                object IEnumerator.Current => _current;
+
+                public void Dispose() { }
+
+                public bool MoveNext()
+                {
+                    if (_index == _dictionary._count)
+                    {
+                        _current = default;
+                        return false;
+                    }
+
+                    _current = _dictionary._entries[_index++].key;
+                    return true;
+                }
+
+                public void Reset()
+                {
+                    _index = 0;
+                }
+            }
+        }
+
+        public struct ValueCollection : ICollection<TValue>, IReadOnlyCollection<TValue>
+        {
+            private readonly StringDictionarySlim<TValue> _dictionary;
+
+            internal ValueCollection(StringDictionarySlim<TValue> dictionary)
+            {
+                _dictionary = dictionary;
+            }
+
+            public int Count => _dictionary._count;
+
+            bool ICollection<TValue>.IsReadOnly => true;
+
+            void ICollection<TValue>.Add(TValue item) =>
+                ExceptionHelper.ThrowNotSupportedException();
+
+            void ICollection<TValue>.Clear() =>
+                ExceptionHelper.ThrowNotSupportedException();
+
+            bool ICollection<TValue>.Contains(TValue item) =>
+                ExceptionHelper.ThrowNotSupportedException<bool>(); // performance antipattern
+
+            bool ICollection<TValue>.Remove(TValue item) =>
+                ExceptionHelper.ThrowNotSupportedException<bool>();
+
+            public void CopyTo(TValue[] array, int index)
+            {
+                Entry[] entries = _dictionary._entries;
+                for (int i = 0; i < _dictionary._count; i++)
+                {
+                    array[index++] = entries[i].value;
+                }
+            }
+
+            public Enumerator GetEnumerator() => new Enumerator(_dictionary); // avoid boxing
+            IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => new Enumerator(_dictionary);
+            IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dictionary);
+
+            public struct Enumerator : IEnumerator<TValue>
+            {
+                private readonly StringDictionarySlim<TValue> _dictionary;
+                private int _index;
+                private TValue _current;
+
+                internal Enumerator(StringDictionarySlim<TValue> dictionary)
+                {
+                    _dictionary = dictionary;
+                    _index = 0;
+                    _current = default;
+                }
+
+                public TValue Current => _current;
+
+                object IEnumerator.Current => _current;
+
+                public void Dispose() { }
+
+                public bool MoveNext()
+                {
+                    if (_index == _dictionary._count)
+                    {
+                        _current = default;
+                        return false;
+                    }
+
+                    _current = _dictionary._entries[_index++].value;
+                    return true;
+                }
+
+                public void Reset()
+                {
+                    _index = 0;
+                }
+            }
+        }
+
+        internal static class HashHelpers
+        {
+            internal static readonly int[] DictionarySlimSizeOneIntArray = new int[1];
+
+            internal static int PowerOf2(int v)
+            {
+                if ((v & (v - 1)) == 0) return v;
+                int i = 2;
+                while (i < v) i <<= 1;
+                return i;
+            }
+        }
+
+        internal sealed class DictionarySlimDebugView<V>
+        {
+            private readonly StringDictionarySlim<V> _dictionary;
+
+            public DictionarySlimDebugView(StringDictionarySlim<V> dictionary)
+            {
+                _dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
+            }
+
+            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+            public KeyValuePair<string, V>[] Items
+            {
+                get
+                {
+                    var array = new KeyValuePair<string, V>[_dictionary.Count];
+                    _dictionary.CopyTo(array, 0);
+                    return array;
+                }
+            }
+        }
+    }
+}

+ 2 - 3
Jint/Engine.cs

@@ -713,10 +713,9 @@ namespace Jint
                 }
             }
 
-            var record = (EnvironmentRecord) baseValue;
-            if (ReferenceEquals(record, null))
+            if (!(baseValue is EnvironmentRecord record))
             {
-                ExceptionHelper.ThrowArgumentException();
+                return ExceptionHelper.ThrowArgumentException<JsValue>();
             }
 
             var bindingValue = record.GetBindingValue(reference._name, reference._strict);

+ 1 - 2
Jint/Native/Argument/ArgumentsInstance.cs

@@ -43,8 +43,7 @@ namespace Jint.Native.Argument
             _strict = strict;
 
             _properties?.Clear();
-            _intrinsicProperties?.Clear();
-
+            
             _initialized = false;
         }
 

+ 6 - 3
Jint/Native/Object/ObjectConstructor.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using Jint.Collections;
 using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.String;
@@ -108,7 +109,9 @@ namespace Jint.Native.Object
             {
                 Extensible = true,
                 Prototype = Engine.Object.PrototypeObject,
-                _properties =  propertyCount > 0 ? new Dictionary<string, PropertyDescriptor>(propertyCount) : null
+                _properties =  propertyCount > 0
+                    ? new StringDictionarySlim<PropertyDescriptor>(System.Math.Max(2, propertyCount))
+                    : null
             };
 
             return obj;
@@ -298,14 +301,14 @@ namespace Jint.Native.Object
                 {
                     if (desc.Writable)
                     {
-                        var mutable = desc as PropertyDescriptor ?? new PropertyDescriptor(desc);
+                        var mutable = desc;
                         mutable.Writable = false;
                         desc = mutable;
                     }
                 }
                 if (desc.Configurable)
                 {
-                    var mutable = desc as PropertyDescriptor ?? new PropertyDescriptor(desc);
+                    var mutable = desc;
                     mutable.Configurable = false;
                     desc = mutable;
                 }

+ 6 - 38
Jint/Native/Object/ObjectInstance.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Dynamic;
 using System.Runtime.CompilerServices;
+using Jint.Collections;
 using Jint.Native.Array;
 using Jint.Native.Boolean;
 using Jint.Native.Date;
@@ -18,8 +19,7 @@ namespace Jint.Native.Object
 {
     public class ObjectInstance : JsValue, IEquatable<ObjectInstance>
     {
-        protected Dictionary<string, PropertyDescriptor> _intrinsicProperties;
-        protected internal Dictionary<string, PropertyDescriptor> _properties;
+        internal StringDictionarySlim<PropertyDescriptor> _properties;
 
         private readonly string _class;
         protected readonly Engine _engine;
@@ -36,38 +36,6 @@ namespace Jint.Native.Object
 
         public Engine Engine => _engine;
 
-        protected bool TryGetIntrinsicValue(JsSymbol symbol, out JsValue value)
-        {
-            if (_intrinsicProperties != null && _intrinsicProperties.TryGetValue(symbol.AsSymbol(), out var descriptor))
-            {
-                value = descriptor.Value;
-                return true;
-            }
-
-            if (ReferenceEquals(Prototype, null))
-            {
-                value = Undefined;
-                return false;
-            }
-
-            return Prototype.TryGetIntrinsicValue(symbol, out value);
-        }
-
-        public void SetIntrinsicValue(string name, JsValue value, bool writable, bool enumerable, bool configurable)
-        {
-            SetOwnProperty(name, new PropertyDescriptor(value, writable, enumerable, configurable));
-        }
-
-        protected void SetIntrinsicValue(JsSymbol symbol, JsValue value, bool writable, bool enumerable, bool configurable)
-        {
-            if (_intrinsicProperties == null)
-            {
-                _intrinsicProperties = new Dictionary<string, PropertyDescriptor>();
-            }
-
-            _intrinsicProperties[symbol.AsSymbol()] = new PropertyDescriptor(value, writable, enumerable, configurable);
-        }
-
         /// <summary>
         /// The prototype of this object.
         /// </summary>
@@ -102,10 +70,10 @@ namespace Jint.Native.Object
         {
             if (_properties == null)
             {
-                _properties = new Dictionary<string, PropertyDescriptor>();
+                _properties = new StringDictionarySlim<PropertyDescriptor>();
             }
 
-            _properties.Add(propertyName, descriptor);
+            _properties[propertyName] = descriptor;
         }
 
         protected virtual bool TryGetProperty(string propertyName, out PropertyDescriptor descriptor)
@@ -204,7 +172,7 @@ namespace Jint.Native.Object
 
             if (_properties == null)
             {
-                _properties = new Dictionary<string, PropertyDescriptor>();
+                _properties = new StringDictionarySlim<PropertyDescriptor>();
             }
 
             _properties[propertyName] = desc;
@@ -830,7 +798,7 @@ namespace Jint.Native.Object
                 case "Arguments":
                 case "Object":
 #if __IOS__
-                                IDictionary<string, object> o = new Dictionary<string, object>();
+                                IDictionary<string, object> o = new DictionarySlim<string, object>();
 #else
                     IDictionary<string, object> o = new ExpandoObject();
 #endif

+ 2 - 2
Jint/Native/Symbol/SymbolPrototype.cs

@@ -32,8 +32,8 @@ namespace Jint.Native.Symbol
             FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf), true, false, true);
             FastAddProperty("toStringTag", new JsString("Symbol"), false, false, true);
 
-            SetIntrinsicValue(GlobalSymbolRegistry.ToPrimitive, new ClrFunctionInstance(Engine, "toPrimitive", ToPrimitive), false, false, true);
-            SetIntrinsicValue(GlobalSymbolRegistry.ToStringTag, new JsString("Symbol"), false, false, true);
+            FastAddProperty(GlobalSymbolRegistry.ToPrimitive._value, new ClrFunctionInstance(Engine, "toPrimitive", ToPrimitive), false, false, true);
+            FastAddProperty(GlobalSymbolRegistry.ToStringTag._value, new JsString("Symbol"), false, false, true);
         }
 
         public string SymbolDescriptiveString(JsSymbol sym)

+ 11 - 13
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using Esprima.Ast;
+using Jint.Collections;
 using Jint.Native;
 using Jint.Native.Argument;
 using Jint.Native.Function;
@@ -13,7 +14,7 @@ namespace Jint.Runtime.Environments
     /// </summary>
     public sealed class DeclarativeEnvironmentRecord : EnvironmentRecord
     {
-        private StructDictionary<Binding> _dictionary;
+        private StringDictionarySlim<Binding> _dictionary;
         private bool _set;
         private string _key;
         private Binding _value;
@@ -31,17 +32,20 @@ namespace Jint.Runtime.Environments
             {
                 if (_dictionary == null)
                 {
-                    _dictionary = new StructDictionary<Binding>();
+                    _dictionary = new StringDictionarySlim<Binding>();
                 }
 
-                _dictionary.TryInsert(_key, _value, InsertionBehavior.OverwriteExisting);
+                _dictionary[_key] = _value;
             }
 
             _set = true;
             _key = key;
             _value = value;
 
-            _dictionary?.TryInsert(key, value, InsertionBehavior.OverwriteExisting);
+            if (_dictionary != null)
+            {
+                _dictionary[key] = value;
+            }
         }
 
         private ref Binding GetExistingItem(string key)
@@ -56,7 +60,7 @@ namespace Jint.Runtime.Environments
                 return ref _argumentsBinding;
             }
 
-            return ref _dictionary.GetItem(key);
+            return ref _dictionary[key];
         }
 
         private bool ContainsKey(string key)
@@ -103,7 +107,7 @@ namespace Jint.Runtime.Environments
                 return true;
             }
 
-            return _dictionary?.TryGetValue(key, out value) == true;
+            return _dictionary != null && _dictionary.TryGetValue(key, out value);
         }
 
         public override bool HasBinding(string name)
@@ -200,13 +204,7 @@ namespace Jint.Runtime.Environments
                 keys[n++] = BindingNameArguments;
             }
 
-            if (_dictionary != null)
-            {
-                foreach (var key in _dictionary.Keys)
-                {
-                    keys[n++] = key;
-                }
-            }
+            _dictionary?.Keys.CopyTo(keys, n);
 
             return keys;
         }

+ 5 - 0
Jint/Runtime/ExceptionHelper.cs

@@ -77,6 +77,11 @@ namespace Jint.Runtime
             throw new ArgumentOutOfRangeException();
         }
 
+        public static T ThrowNotSupportedException<T>(string message = null)
+        {
+            throw new NotSupportedException(message);
+        }
+
         public static void ThrowNotSupportedException(string message = null)
         {
             throw new NotSupportedException(message);

+ 0 - 802
Jint/Runtime/StructDictionary.cs

@@ -1,802 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reflection;
-
-namespace Jint.Runtime
-{
-    internal enum InsertionBehavior : byte
-    {
-        /// <summary>
-        /// Specifies that an existing entry with the same key should be overwritten if encountered.
-        /// </summary>
-        OverwriteExisting = 1,
-
-        /// <summary>
-        /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
-        /// </summary>
-        ThrowOnExisting = 2,
-        
-        /// <summary>
-        /// Specifies that if existing entry with the same key is encountered, the update will be skipped.
-        /// </summary>
-        SkipIfExists = 3
-    }
-
-    /// <summary>
-    /// Taken from .NET source to create performant specialized dictionary containing structs for Jint.
-    /// </summary>
-    internal sealed class StructDictionary<TValue> where TValue : struct
-    {
-        private static readonly EqualityComparer<string> _comparer; 
-        
-        static StructDictionary()
-        {
-            // we want to use same comparer as default dictionary impl that is hidden from us
-            // .NET Core uses non-randomized hash code generation that is faster than default
-            try
-            {
-                Dictionary<string, TValue> dictionary = new Dictionary<string, TValue>();
-                var field = dictionary.GetType().GetField("_comparer", BindingFlags.Instance | BindingFlags.NonPublic);
-                field = field ?? dictionary.GetType().GetField("comparer", BindingFlags.Instance | BindingFlags.NonPublic);
-                _comparer = field?.GetValue(dictionary) as EqualityComparer<string> ?? EqualityComparer<string>.Default;
-            }
-            catch
-            {
-                _comparer = EqualityComparer<string>.Default;
-            }
-        }
-        
-        private struct Entry
-        {
-            public int hashCode; // Lower 31 bits of hash code, -1 if unused
-            public int next; // Index of next entry, -1 if last
-            public string key; // Key of entry
-            public TValue value; // Value of entry
-        }
-
-        private int[] _buckets;
-        private Entry[] _entries;
-        private int _count;
-        private int _freeList;
-        private int _freeCount;
-        private KeyCollection _keys;
-        private ValueCollection _values;
-
-        public StructDictionary() : this(0)
-        {
-        }
-
-        public StructDictionary(int capacity)
-        {
-            if (capacity > 0) Initialize(capacity);
-        }
-                                
-        public int Count => _count - _freeCount;
-
-        public KeyCollection Keys
-        {
-            get
-            {
-                if (_keys == null) _keys = new KeyCollection(this);
-                return _keys;
-            }
-        }
-
-        public ValueCollection Values
-        {
-            get
-            {
-                if (_values == null) _values = new ValueCollection(this);
-                return _values;
-            }
-        }
-
-        public ref TValue GetItem(string key)
-        {
-            int i = FindEntry(key);
-            if (i >= 0) return ref _entries[i].value;
-            ExceptionHelper.ThrowArgumentException("key " + key + " not part of dictionary");
-            return ref _entries[0].value;
-        }
-
-        public void Clear()
-        {
-            int count = _count;
-            if (count > 0)
-            {
-                Array.Clear(_buckets, 0, _buckets.Length);
-
-                _count = 0;
-                _freeList = -1;
-                _freeCount = 0;
-                Array.Clear(_entries, 0, count);
-            }
-        }
-
-        public bool ContainsKey(string key)
-            => FindEntry(key) >= 0;
-
-        public Enumerator GetEnumerator()
-            => new Enumerator(this, Enumerator.KeyValuePair);
-
-        private int FindEntry(string key)
-        {
-            int i = -1;
-            int[] buckets = _buckets;
-            Entry[] entries = _entries;
-            int collisionCount = 0;
-            if (buckets != null)
-            {
-                int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF;
-                // Value in _buckets is 1-based
-                i = buckets[hashCode % buckets.Length] - 1;
-                do
-                {
-                    // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
-                    // Test in if to drop range check for following array access
-                    if ((uint) i >= (uint) entries.Length ||
-                        (entries[i].hashCode == hashCode && entries[i].key == key))
-                    {
-                        break;
-                    }
-
-                    i = entries[i].next;
-                    if (collisionCount >= entries.Length)
-                    {
-                        // The chain of entries forms a loop; which means a concurrent update has happened.
-                        // Break out of the loop and throw, rather than looping forever.
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    collisionCount++;
-                } while (true);
-            }
-
-            return i;
-        }
-
-        private int Initialize(int capacity)
-        {
-            int size = HashHelpers.GetPrime(capacity);
-
-            _freeList = -1;
-            _buckets = new int[size];
-            _entries = new Entry[size];
-
-            return size;
-        }
-
-        public bool TryInsert(string key, in TValue value, InsertionBehavior behavior)
-        {
-            if (_buckets == null)
-            {
-                Initialize(0);
-            }
-
-            Entry[] entries = _entries;
-
-            int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF;
-
-            int collisionCount = 0;
-            ref int bucket = ref _buckets[hashCode % _buckets.Length];
-            // Value in _buckets is 1-based
-            int i = bucket - 1;
-            do
-            {
-                // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
-                // Test uint in if rather than loop condition to drop range check for following array access
-                if ((uint) i >= (uint) entries.Length)
-                {
-                    break;
-                }
-
-                if (entries[i].hashCode == hashCode && entries[i].key == key)
-                {
-                    if (behavior == InsertionBehavior.OverwriteExisting)
-                    {
-                        entries[i].value = value;
-                        return true;
-                    }
-
-                    if (behavior == InsertionBehavior.SkipIfExists)
-                    {
-                        return true;
-                    }
-
-                    if (behavior == InsertionBehavior.ThrowOnExisting)
-                    {
-                        ExceptionHelper.ThrowArgumentException("key already exists");
-                    }
-
-                    return false;
-                }
-
-                i = entries[i].next;
-                if (collisionCount >= entries.Length)
-                {
-                    // The chain of entries forms a loop; which means a concurrent update has happened.
-                    // Break out of the loop and throw, rather than looping forever.
-                    ExceptionHelper.ThrowInvalidOperationException();
-                }
-
-                collisionCount++;
-            } while (true);
-
-            bool updateFreeList = false;
-            int index;
-            if (_freeCount > 0)
-            {
-                index = _freeList;
-                updateFreeList = true;
-                _freeCount--;
-            }
-            else
-            {
-                int count = _count;
-                if (count == entries.Length)
-                {
-                    Resize();
-                    bucket = ref _buckets[hashCode % _buckets.Length];
-                }
-
-                index = count;
-                _count = count + 1;
-                entries = _entries;
-            }
-
-            ref Entry entry = ref entries[index];
-
-            if (updateFreeList)
-            {
-                _freeList = entry.next;
-            }
-
-            entry.hashCode = hashCode;
-            // Value in _buckets is 1-based
-            entry.next = bucket - 1;
-            entry.key = key;
-            entry.value = value;
-            // Value in _buckets is 1-based
-            bucket = index + 1;
-
-            return true;
-        }
-
-        private void Resize()
-            => Resize(HashHelpers.ExpandPrime(_count), false);
-
-        private void Resize(int newSize, bool forceNewHashCodes)
-        {
-            // Value types never rehash
-            Debug.Assert(!forceNewHashCodes || default(string) == null);
-            Debug.Assert(newSize >= _entries.Length);
-
-            int[] buckets = new int[newSize];
-            Entry[] entries = new Entry[newSize];
-
-            int count = _count;
-            Array.Copy(_entries, 0, entries, 0, count);
-
-            if (forceNewHashCodes)
-            {
-                for (int i = 0; i < count; i++)
-                {
-                    if (entries[i].hashCode >= 0)
-                    {
-                        entries[i].hashCode = (_comparer.GetHashCode(entries[i].key) & 0x7FFFFFFF);
-                    }
-                }
-            }
-
-            for (int i = 0; i < count; i++)
-            {
-                if (entries[i].hashCode >= 0)
-                {
-                    int bucket = entries[i].hashCode % newSize;
-                    // Value in _buckets is 1-based
-                    entries[i].next = buckets[bucket] - 1;
-                    // Value in _buckets is 1-based
-                    buckets[bucket] = i + 1;
-                }
-            }
-
-            _buckets = buckets;
-            _entries = entries;
-        }
-
-        // The overload Remove(string key, out TValue value) is a copy of this method with one additional
-        // statement to copy the value for entry being removed into the output parameter.
-        // Code has been intentionally duplicated for performance reasons.
-        public bool Remove(string key)
-        {
-            int[] buckets = _buckets;
-            Entry[] entries = _entries;
-            int collisionCount = 0;
-            if (buckets != null)
-            {
-                int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF;
-                int bucket = hashCode % buckets.Length;
-                int last = -1;
-                // Value in buckets is 1-based
-                int i = buckets[bucket] - 1;
-                while (i >= 0)
-                {
-                    ref Entry entry = ref entries[i];
-
-                    if (entry.hashCode == hashCode && entry.key == key)
-                    {
-                        if (last < 0)
-                        {
-                            // Value in buckets is 1-based
-                            buckets[bucket] = entry.next + 1;
-                        }
-                        else
-                        {
-                            entries[last].next = entry.next;
-                        }
-
-                        entry.hashCode = -1;
-                        entry.next = _freeList;
-                        entry.key = null;
-                        entry.value = default;
-
-                        _freeList = i;
-                        _freeCount++;
-                        return true;
-                    }
-
-                    last = i;
-                    i = entry.next;
-                    if (collisionCount >= entries.Length)
-                    {
-                        // The chain of entries forms a loop; which means a concurrent update has happened.
-                        // Break out of the loop and throw, rather than looping forever.
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    collisionCount++;
-                }
-            }
-
-            return false;
-        }
-
-        // This overload is a copy of the overload Remove(string key) with one additional
-        // statement to copy the value for entry being removed into the output parameter.
-        // Code has been intentionally duplicated for performance reasons.
-        public bool Remove(string key, out TValue value)
-        {
-            int[] buckets = _buckets;
-            Entry[] entries = _entries;
-            int collisionCount = 0;
-            if (buckets != null)
-            {
-                int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF;
-                int bucket = hashCode % buckets.Length;
-                int last = -1;
-                // Value in buckets is 1-based
-                int i = buckets[bucket] - 1;
-                while (i >= 0)
-                {
-                    ref Entry entry = ref entries[i];
-
-                    if (entry.hashCode == hashCode && entry.key == key)
-                    {
-                        if (last < 0)
-                        {
-                            // Value in buckets is 1-based
-                            buckets[bucket] = entry.next + 1;
-                        }
-                        else
-                        {
-                            entries[last].next = entry.next;
-                        }
-
-                        value = entry.value;
-
-                        entry.hashCode = -1;
-                        entry.next = _freeList;
-                        entry.key = null;
-                        entry.value = default;
-
-                        _freeList = i;
-                        _freeCount++;
-                        return true;
-                    }
-
-                    last = i;
-                    i = entry.next;
-                    if (collisionCount >= entries.Length)
-                    {
-                        // The chain of entries forms a loop; which means a concurrent update has happened.
-                        // Break out of the loop and throw, rather than looping forever.
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    collisionCount++;
-                }
-            }
-
-            value = default;
-            return false;
-        }
-
-        public bool TryGetValue(string key, out TValue value)
-        {
-            int i = FindEntry(key);
-            if (i >= 0)
-            {
-                value = _entries[i].value;
-                return true;
-            }
-
-            value = default;
-            return false;
-        }
-
-        /// <summary>
-        /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
-        /// </summary>
-        public int EnsureCapacity(int capacity)
-        {
-            int currentCapacity = _entries == null ? 0 : _entries.Length;
-            if (currentCapacity >= capacity)
-                return currentCapacity;
-            if (_buckets == null)
-                return Initialize(capacity);
-            int newSize = HashHelpers.GetPrime(capacity);
-            Resize(newSize, forceNewHashCodes: false);
-            return newSize;
-        }
-                                    
-        public struct Enumerator : IEnumerator<KeyValuePair<string, TValue>>,
-            IDictionaryEnumerator
-        {
-            private readonly StructDictionary<TValue> _dictionary;
-            private int _index;
-            private KeyValuePair<string, TValue> _current;
-            private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
-
-            internal const int DictEntry = 1;
-            internal const int KeyValuePair = 2;
-
-            internal Enumerator(StructDictionary<TValue> dictionary, int getEnumeratorRetType)
-            {
-                _dictionary = dictionary;
-                _index = 0;
-                _getEnumeratorRetType = getEnumeratorRetType;
-                _current = new KeyValuePair<string, TValue>();
-            }
-
-            public bool MoveNext()
-            {
-                // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
-                // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
-                while ((uint) _index < (uint) _dictionary._count)
-                {
-                    ref Entry entry = ref _dictionary._entries[_index++];
-
-                    if (entry.hashCode >= 0)
-                    {
-                        _current = new KeyValuePair<string, TValue>(entry.key, entry.value);
-                        return true;
-                    }
-                }
-
-                _index = _dictionary._count + 1;
-                _current = new KeyValuePair<string, TValue>();
-                return false;
-            }
-
-            public KeyValuePair<string, TValue> Current => _current;
-
-            public void Dispose()
-            {
-            }
-
-            object IEnumerator.Current
-            {
-                get
-                {
-                    if (_index == 0 || (_index == _dictionary._count + 1))
-                    {
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    if (_getEnumeratorRetType == DictEntry)
-                    {
-                        return new DictionaryEntry(_current.Key, _current.Value);
-                    }
-                    else
-                    {
-                        return new KeyValuePair<string, TValue>(_current.Key, _current.Value);
-                    }
-                }
-            }
-
-            void IEnumerator.Reset()
-            {
-                _index = 0;
-                _current = new KeyValuePair<string, TValue>();
-            }
-
-            DictionaryEntry IDictionaryEnumerator.Entry
-            {
-                get
-                {
-                    if (_index == 0 || (_index == _dictionary._count + 1))
-                    {
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    return new DictionaryEntry(_current.Key, _current.Value);
-                }
-            }
-
-            object IDictionaryEnumerator.Key
-            {
-                get
-                {
-                    if (_index == 0 || (_index == _dictionary._count + 1))
-                    {
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    return _current.Key;
-                }
-            }
-
-            object IDictionaryEnumerator.Value
-            {
-                get
-                {
-                    if (_index == 0 || (_index == _dictionary._count + 1))
-                    {
-                        ExceptionHelper.ThrowInvalidOperationException();
-                    }
-
-                    return _current.Value;
-                }
-            }
-        }
-
-        public sealed class KeyCollection
-        {
-            private readonly StructDictionary<TValue> _dictionary;
-
-            public KeyCollection(StructDictionary<TValue> dictionary)
-            {
-                if (dictionary == null)
-                {
-                    ExceptionHelper.ThrowArgumentNullException(nameof(dictionary));
-                }
-
-                _dictionary = dictionary;
-            }
-
-            public Enumerator GetEnumerator()
-                => new Enumerator(_dictionary);
-
-            public int Count => _dictionary.Count;
-
-            public struct Enumerator : IEnumerator<string>, IEnumerator
-            {
-                private readonly StructDictionary<TValue> _dictionary;
-                private int _index;
-                private string _currenstring;
-
-                internal Enumerator(StructDictionary<TValue> dictionary)
-                {
-                    _dictionary = dictionary;
-                    _index = 0;
-                    _currenstring = default;
-                }
-
-                public void Dispose()
-                {
-                }
-
-                public bool MoveNext()
-                {
-                    while ((uint) _index < (uint) _dictionary._count)
-                    {
-                        ref Entry entry = ref _dictionary._entries[_index++];
-
-                        if (entry.hashCode >= 0)
-                        {
-                            _currenstring = entry.key;
-                            return true;
-                        }
-                    }
-
-                    _index = _dictionary._count + 1;
-                    _currenstring = default;
-                    return false;
-                }
-
-                public string Current => _currenstring;
-
-                object IEnumerator.Current
-                {
-                    get
-                    {
-                        if (_index == 0 || (_index == _dictionary._count + 1))
-                        {
-                            ExceptionHelper.ThrowInvalidOperationException();
-                        }
-
-                        return _currenstring;
-                    }
-                }
-
-                void IEnumerator.Reset()
-                {
-                    _index = 0;
-                    _currenstring = default;
-                }
-            }
-        }
-
-        public sealed class ValueCollection
-        {
-            private readonly StructDictionary<TValue> _dictionary;
-
-            public ValueCollection(StructDictionary<TValue> dictionary)
-            {
-                _dictionary = dictionary;
-            }
-
-            public Enumerator GetEnumerator()
-                => new Enumerator(_dictionary);
-
-            public int Count => _dictionary.Count;
-
-            public struct Enumerator : IEnumerator<TValue>
-            {
-                private readonly StructDictionary<TValue> _dictionary;
-                private int _index;
-                private TValue _currentValue;
-
-                internal Enumerator(StructDictionary<TValue> dictionary)
-                {
-                    _dictionary = dictionary;
-                    _index = 0;
-                    _currentValue = default;
-                }
-
-                public void Dispose()
-                {
-                }
-
-                public bool MoveNext()
-                {
-                    while ((uint) _index < (uint) _dictionary._count)
-                    {
-                        ref Entry entry = ref _dictionary._entries[_index++];
-
-                        if (entry.hashCode >= 0)
-                        {
-                            _currentValue = entry.value;
-                            return true;
-                        }
-                    }
-
-                    _index = _dictionary._count + 1;
-                    _currentValue = default;
-                    return false;
-                }
-
-                public TValue Current => _currentValue;
-
-                object IEnumerator.Current
-                {
-                    get
-                    {
-                        if (_index == 0 || (_index == _dictionary._count + 1))
-                        {
-                            ExceptionHelper.ThrowInvalidOperationException();
-                        }
-
-                        return _currentValue;
-                    }
-                }
-
-                void IEnumerator.Reset()
-                {
-                    _index = 0;
-                    _currentValue = default;
-                }
-            }
-        }
-
-        private static class HashHelpers
-        {
-            public const int HashCollisionThreshold = 100;
-
-            // This is the maximum prime smaller than Array.MaxArrayLength
-            public const int MaxPrimeArrayLength = 0x7FEFFFFD;
-
-            public const int HashPrime = 101;
-
-            // Table of prime numbers to use as hash table sizes. 
-            // A typical resize algorithm would pick the smallest prime number in this array
-            // that is larger than twice the previous capacity. 
-            // Suppose our Hashtable currently has capacity x and enough elements are added 
-            // such that a resize needs to occur. Resizing first computes 2x then finds the 
-            // first prime in the table greater than 2x, i.e. if primes are ordered 
-            // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. 
-            // Doubling is important for preserving the asymptotic complexity of the 
-            // hashtable operations such as add.  Having a prime guarantees that double 
-            // hashing does not lead to infinite loops.  IE, your hash function will be 
-            // h1(key) + i*h2(key), 0 <= i < size.  h2 and the size must be relatively prime.
-            // We prefer the low computation costs of higher prime numbers over the increased
-            // memory allocation of a fixed prime number i.e. when right sizing a HashSet.
-            public static readonly int[] primes =
-            {
-                3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
-                1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
-                17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
-                187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
-                1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
-            };
-
-            public static bool IsPrime(int candidate)
-            {
-                if ((candidate & 1) != 0)
-                {
-                    int limit = (int) Math.Sqrt(candidate);
-                    for (int divisor = 3; divisor <= limit; divisor += 2)
-                    {
-                        if ((candidate % divisor) == 0)
-                            return false;
-                    }
-
-                    return true;
-                }
-
-                return (candidate == 2);
-            }
-
-            public static int GetPrime(int min)
-            {
-                if (min < 0)
-                    throw new ArgumentException();
-
-                for (int i = 0; i < primes.Length; i++)
-                {
-                    int prime = primes[i];
-                    if (prime >= min)
-                        return prime;
-                }
-
-                //outside of our predefined table. 
-                //compute the hard way. 
-                for (int i = (min | 1); i < int.MaxValue; i += 2)
-                {
-                    if (IsPrime(i) && ((i - 1) % HashPrime != 0))
-                        return i;
-                }
-
-                return min;
-            }
-
-            // Returns size of hashtable to grow to.
-            public static int ExpandPrime(int oldSize)
-            {
-                int newSize = 2 * oldSize;
-
-                // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow.
-                // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
-                if ((uint) newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
-                {
-                    Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength");
-                    return MaxPrimeArrayLength;
-                }
-
-                return GetPrime(newSize);
-            }
-        }
-    }
-}