PageRenderTime 43ms CodeModel.GetById 15ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/IronPython_2_0/Src/IronPython/Runtime/CommonDictionaryStorage.cs

#
C# | 703 lines | 462 code | 115 blank | 126 comment | 135 complexity | c793dd41569d909df638108ecf722d0c MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Microsoft Public License. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Microsoft Public License, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Microsoft Public License.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16using System; using Microsoft;
 17using System.Collections.Generic;
 18using System.Diagnostics;
 19using System.Runtime.Serialization;
 20using Microsoft.Scripting.Actions;
 21
 22using Microsoft.Scripting;
 23using Microsoft.Scripting.Generation;
 24
 25using IronPython.Runtime.Operations;
 26using IronPython.Runtime.Types;
 27
 28namespace IronPython.Runtime {
 29    /// <summary>
 30    /// General purpose storage used for most PythonDictionarys.
 31    /// 
 32    /// This dictionary storage is thread safe for multiple readers or writers.
 33    /// 
 34    /// Mutations to the dictionary involves a simple locking strategy of
 35    /// locking on the DictionaryStorage object to ensure that only one
 36    /// mutation happens at a time.
 37    /// 
 38    /// Reads against the dictionary happen lock free.  When the dictionary is mutated
 39    /// it is either adding or removing buckets in a thread-safe manner so that the readers
 40    /// will either see a consistent picture as if the read occured before or after the mutation.
 41    /// 
 42    /// When resizing the dictionary the buckets are replaced atomically so that the reader
 43    /// sees the new buckets or the old buckets.  When reading the reader first reads
 44    /// the buckets and then calls a static helper function to do the read from the bucket
 45    /// array to ensure that readers are not seeing multiple bucket arrays.
 46    /// </summary>
 47    [Serializable]
 48    internal sealed class CommonDictionaryStorage : DictionaryStorage
 49#if !SILVERLIGHT
 50        , ISerializable, IDeserializationCallback 
 51#endif
 52    {
 53        private Bucket[] _buckets;
 54        private int _count;
 55        private Func<object, int> _hashFunc;
 56        private Func<object, object, bool> _eqFunc;
 57        private Type _keyType;
 58        private const int InitialBucketSize = 7;
 59        private const int ResizeMultiplier = 3;
 60
 61        // pre-created delegate instances shared by all homogeneous dictionaries for primitive types.
 62        private static readonly Func<object, int> _primitiveHash = PrimitiveHash, _doubleHash = DoubleHash, _intHash = IntHash, _tupleHash = TupleHash, _genericHash = GenericHash;
 63        private static readonly Func<object, object, bool> _intEquals = IntEquals, _doubleEquals = DoubleEquals, _stringEquals = StringEquals, _tupleEquals = TupleEquals, _genericEquals = GenericEquals, _objectEq = System.Object.ReferenceEquals;
 64        private static readonly Type _polymorphicType = typeof(CommonDictionaryStorage);
 65
 66        /// <summary>
 67        /// Creates a new dictionary storage with no buckets
 68        /// </summary>
 69        public CommonDictionaryStorage() {
 70        }
 71
 72        /// <summary>
 73        /// Creates a new dictionary storage with no buckets
 74        /// </summary>
 75        public CommonDictionaryStorage(int count) {
 76            _buckets = new Bucket[count + 1];
 77        }
 78
 79        /// <summary>
 80        /// Creates a new dictionary geting values/keys from the
 81        /// items arary
 82        /// </summary>
 83        public CommonDictionaryStorage(object[] items, bool isHomogeneous)
 84            : this(Math.Max(items.Length / 2, InitialBucketSize)) {
 85            // always called w/ items, and items should be even (key/value pairs)
 86            Debug.Assert(items.Length > 0 && (items.Length & 0x01) == 0);
 87
 88            Type t = CompilerHelpers.GetType(items[1]);
 89
 90            if (!isHomogeneous) {
 91                for (int i = 1; i < items.Length / 2; i++) {
 92                    if (CompilerHelpers.GetType(items[i * 2 + 1]) != t) {
 93                        SetHeterogeneousSites();
 94                        t = null;
 95                        break;
 96                    }
 97                }
 98            }
 99
100            if (t != null) {
101                // homogeneous collection
102                UpdateHelperFunctions(t, items[1]);
103            }
104
105            for (int i = 0; i < items.Length / 2; i++) {
106                AddOne(items[i * 2 + 1], items[i * 2]);
107            }
108        }
109
110        private void AddItems(object[] items) {
111            for (int i = 0; i < items.Length / 2; i++) {
112                AddNoLock(items[i * 2 + 1], items[i * 2]);
113            }
114        }
115
116        /// <summary>
117        /// Creates a new dictionary storage with the given set of buckets
118        /// and size.  Used when cloning the dictionary storage.
119        /// </summary>
120        private CommonDictionaryStorage(Bucket[] buckets, int count, Type keyType, Func<object, int> hashFunc, Func<object, object, bool> eqFunc) {
121            _buckets = buckets;
122            _count = count;
123            _keyType = keyType;
124            _hashFunc = hashFunc;
125            _eqFunc = eqFunc;
126        }
127
128#if !SILVERLIGHT
129        private CommonDictionaryStorage(SerializationInfo info, StreamingContext context) {
130            // remember the serialization info, we'll deserialize when we get the callback.  This
131            // enables special types like DBNull.Value to successfully be deserialized inside of us.  We
132            // store the serialization info in a special bucket so we don't have an extra field just for
133            // serialization
134            _buckets = new Bucket[] { new DeserializationBucket(info) };
135        }
136#endif
137
138        /// <summary>
139        /// Adds a new item to the dictionary, replacing an existing one if it already exists.
140        /// </summary>
141        public override void Add(object key, object value) {
142            lock (this) {
143                AddNoLock(key, value);
144            }
145        }
146
147        public override void AddNoLock(object key, object value) {
148            if (_buckets == null) {
149                Initialize();
150            }
151
152            Type t = CompilerHelpers.GetType(key);
153            if (t != _keyType && _keyType != typeof(CommonDictionaryStorage)) {
154                UpdateHelperFunctions(t, key);
155            }
156
157            AddOne(key, value);
158        }
159
160        private void AddOne(object key, object value) {
161            if (Add(_buckets, key, value)) {
162                _count++;
163
164                if (_count >= _buckets.Length) {
165                    // grow the hash table
166                    EnsureSize(_buckets.Length * ResizeMultiplier);
167                }
168            }
169        }
170
171        private void UpdateHelperFunctions(Type t, object key) {
172            if (_keyType == null) {
173                // first time through, get the sites for this specific type...
174                if (t == typeof(int)) {
175                    _hashFunc = _intHash;
176                    _eqFunc = _intEquals;
177                } else if (t == typeof(string)) {
178                    _hashFunc = _primitiveHash;
179                    _eqFunc = _stringEquals;
180                } else if (t == typeof(double)) {
181                    _hashFunc = _doubleHash;
182                    _eqFunc = _doubleEquals;
183                } else if (t == typeof(PythonTuple)) {
184                    _hashFunc = _tupleHash;
185                    _eqFunc = _tupleEquals;
186                } else if (t == typeof(Type).GetType()) {    // this odd check checks for RuntimeType.
187                    _hashFunc = _primitiveHash;
188                    _eqFunc = _objectEq;
189                } else {
190                    PythonType pt = DynamicHelpers.GetPythonType(key);
191
192                    // random type, but still homogeneous... get a shared site for this type.
193                    var hashSite = DefaultContext.DefaultPythonContext.GetHashSite(pt);
194                    var equalSite = DefaultContext.DefaultPythonContext.GetEqualSite(pt);
195
196                    AssignSiteDelegates(hashSite, equalSite);
197                }
198
199                _keyType = t;
200            } else if (_keyType != typeof(CommonDictionaryStorage)) {
201                // 2nd time through, we're adding a new type so we have mutliple types now, 
202                // make a new site for this storage
203
204                SetHeterogeneousSites();
205
206                // we need to clone the buckets so any lock-free readers will only see
207                // the old buckets which are homogeneous
208                _buckets = (Bucket[])_buckets.Clone();
209            }
210            // else we have already created a new site this dictionary
211        }
212
213        private void SetHeterogeneousSites() {
214            var hashSite = DefaultContext.DefaultPythonContext.MakeHashSite();
215            var equalSite = DefaultContext.DefaultPythonContext.MakeEqualSite();
216
217            AssignSiteDelegates(hashSite, equalSite);
218
219            _keyType = typeof(CommonDictionaryStorage);
220        }
221
222        private void AssignSiteDelegates(CallSite<Func<CallSite, object, int>> hashSite, CallSite<Func<CallSite, object, object, bool>> equalSite) {
223            _hashFunc = (o) => hashSite.Target(hashSite, o);
224            _eqFunc = (o1, o2) => equalSite.Target(equalSite, o1, o2);
225        }
226
227        private void EnsureSize(int newSize) {
228            if (_buckets.Length >= newSize) {
229                return;
230            }
231
232            Bucket[] newBuckets = new Bucket[newSize];
233
234            for (int i = 0; i < _buckets.Length; i++) {
235                Bucket curBucket = _buckets[i];
236                while (curBucket != null) {
237                    Bucket next = curBucket.Next;
238
239                    AddWorker(newBuckets, curBucket.Key, curBucket.Value, curBucket.HashCode);
240
241                    curBucket = next;
242                }
243            }
244
245            _buckets = newBuckets;
246        }
247
248        /// <summary>
249        /// Initializes the buckets to their initial capacity, the caller
250        /// must check if the buckets are empty first.
251        /// </summary>
252        private void Initialize() {
253            _buckets = new Bucket[InitialBucketSize];
254        }
255
256        /// <summary>
257        /// Static add helper that works over a single set of buckets.  Used for
258        /// both the normal add case as well as the resize case.
259        /// </summary>
260        private bool Add(Bucket[] buckets, object key, object value) {
261            int hc = Hash(key);
262
263            return AddWorker(buckets, key, value, hc);
264        }
265
266        private bool AddWorker(Bucket[] buckets, object key, object value, int hc) {
267            int index = hc % buckets.Length;
268            Bucket prev = buckets[index];
269            Bucket cur = prev;
270
271            while (cur != null) {
272                if (cur.HashCode == hc && _eqFunc(key, cur.Key)) {
273                    cur.Value = value;
274                    return false;
275                }
276
277                prev = cur;
278                cur = cur.Next;
279            }
280
281            if (prev != null) {
282                Debug.Assert(prev.Next == null);
283                prev.Next = new Bucket(hc, key, value, null);
284            } else {
285                buckets[index] = new Bucket(hc, key, value, null);
286            }
287
288            return true;
289        }
290
291        /// <summary>
292        /// Removes an entry from the dictionary and returns true if the
293        /// entry was removed or false.
294        /// </summary>
295        public override bool Remove(object key) {
296            object dummy;
297            return TryRemoveValue(key, out dummy);
298        }
299
300        /// <summary>
301        /// Removes an entry from the dictionary and returns true if the
302        /// entry was removed or false.  The key will always be hashed
303        /// so if it is unhashable an exception will be thrown - even
304        /// if the dictionary has no buckets.
305        /// </summary>
306        internal bool RemoveAlwaysHash(object key) {
307            lock (this) {
308                object dummy;
309                return TryRemoveNoLock(key, out dummy);
310            }
311        }
312
313        public override bool TryRemoveValue(object key, out object value) {
314            lock (this) {
315                if (!HasAnyValues(_buckets)) {
316                    value = null;
317                    return false;
318                }
319                
320
321                return TryRemoveNoLock(key, out value);
322            }
323        }
324
325        private bool TryRemoveNoLock(object key, out object value) {
326            Func<object, int> hashFunc;
327            Func<object, object, bool> eqFunc;
328            if (CompilerHelpers.GetType(key) == _keyType || _keyType == _polymorphicType) {
329                hashFunc = _hashFunc;
330                eqFunc = _eqFunc;
331            } else {
332                hashFunc = _genericHash;
333                eqFunc = _genericEquals;
334            }
335
336            int hc = hashFunc(key) & Int32.MaxValue;
337
338            if (_buckets == null) {
339                value = null;
340                return false;
341            }
342
343            int index = hc % _buckets.Length;
344            Bucket bucket = _buckets[index];
345            Bucket prev = bucket;
346            while (bucket != null) {
347                if (bucket.HashCode == hc && eqFunc(key, bucket.Key)) {
348                    value = bucket.Value;
349                    if (prev == bucket) {
350                        _buckets[index] = bucket.Next;
351                    } else {
352                        prev.Next = bucket.Next;
353                    }
354                    _count--;
355                    
356                    return true;
357                }
358                prev = bucket;
359                bucket = bucket.Next;
360            }
361            
362            value = null;
363            return false;
364        }
365
366        /// <summary>
367        /// Checks to see if the key exists in the dictionary.
368        /// </summary>
369        public override bool Contains(object key) {
370            return Contains(_buckets, key);
371        }
372
373        /// <summary>
374        /// Static helper to see if the key exists in the provided bucket array.
375        /// 
376        /// Used so the contains check can run against a buckets while a writer
377        /// replaces the buckets.
378        /// </summary>
379        private bool Contains(Bucket[] buckets, object key) {
380            object res;
381            return TryGetValue(buckets, key, out res);
382        }
383
384        private bool HasAnyValues(Bucket[] buckets) {
385            return buckets != null && _hashFunc != null;
386        }
387
388        /// <summary>
389        /// Trys to get the value associated with the given key and returns true
390        /// if it's found or false if it's not present.
391        /// </summary>
392        public override bool TryGetValue(object key, out object value) {
393            return TryGetValue(_buckets, key, out value);
394        }
395
396        /// <summary>
397        /// Static helper to try and get the value from the dictionary.
398        /// 
399        /// Used so the value lookup can run against a buckets while a writer
400        /// replaces the buckets.
401        /// </summary>
402        private bool TryGetValue(Bucket[] buckets, object key, out object value) {
403            if (HasAnyValues(buckets)) {
404                int hc;
405                Func<object, object, bool> eqFunc;
406                if (CompilerHelpers.GetType(key) == _keyType || _keyType == typeof(CommonDictionaryStorage)) {
407                    hc = _hashFunc(key) & Int32.MaxValue;
408                    eqFunc = _eqFunc;
409                } else {
410                    hc = _genericHash(key) & Int32.MaxValue;
411                    eqFunc = _genericEquals;
412                }
413
414                Bucket bucket = buckets[hc % buckets.Length];
415                while (bucket != null) {
416                    if (bucket.HashCode == hc && eqFunc(key, bucket.Key)) {
417                        value = bucket.Value;
418                        return true;
419                    }
420                    bucket = bucket.Next;
421                }
422            }
423
424            value = null;
425            return false;
426        }
427
428        /// <summary>
429        /// Returns the number of key/value pairs currently in the dictionary.
430        /// </summary>
431        public override int Count {
432            get { return _count; }
433        }
434
435        /// <summary>
436        /// Clears the contents of the dictionary.
437        /// </summary>
438        public override void Clear() {
439            lock (this) {
440                if (_buckets != null) {
441                    _buckets = new Bucket[8];
442                    _count = 0;
443                }
444            }
445        }
446
447        public override List<KeyValuePair<object, object>> GetItems() {
448            lock (this) {
449                List<KeyValuePair<object, object>> res = new List<KeyValuePair<object, object>>(Count);
450                if (_buckets != null) {
451                    for (int i = 0; i < _buckets.Length; i++) {
452                        Bucket curBucket = _buckets[i];
453                        while (curBucket != null) {
454                            res.Add(new KeyValuePair<object, object>(curBucket.Key, curBucket.Value));
455
456                            curBucket = curBucket.Next;
457                        }
458                    }
459                }
460                return res;
461            }
462        }
463
464        public override bool HasNonStringAttributes() {
465            lock (this) {
466                if (_keyType != typeof(string) && _buckets != null) {
467                    for (int i = 0; i < _buckets.Length; i++) {
468                        Bucket curBucket = _buckets[i];
469                        while (curBucket != null) {
470                            if (!(curBucket.Key is string)) {
471                                return true;
472                            }
473                            
474                            curBucket = curBucket.Next;
475                        }
476                    }
477                }
478            }
479
480            return false;
481        }
482
483        /// <summary>
484        /// Clones the storage returning a new DictionaryStorage object.
485        /// </summary>
486        public override DictionaryStorage Clone() {
487            lock (this) {
488                if (_buckets == null) {
489                    return new CommonDictionaryStorage();
490                }
491
492                Bucket[] resBuckets = new Bucket[_buckets.Length];
493                for (int i = 0; i < _buckets.Length; i++) {
494                    if (_buckets[i] != null) {
495                        resBuckets[i] = _buckets[i].Clone();
496                    }
497                }
498
499                return new CommonDictionaryStorage(resBuckets, Count, _keyType, _hashFunc, _eqFunc);
500            }
501        }
502
503        public override void CopyTo(DictionaryStorage/*!*/ into) {
504            Debug.Assert(into != null);
505
506            if (_buckets != null) {
507                using (new OrderedLocker(this, into)) {
508                    CommonDictionaryStorage commonInto = into as CommonDictionaryStorage;
509                    if (commonInto != null) {
510                        CommonCopyTo(commonInto);
511                    } else {
512                        UncommonCopyTo(into);
513                    }
514                }
515            }
516        }
517
518        private void CommonCopyTo(CommonDictionaryStorage into) {
519            if (into._buckets == null) {
520                into._buckets = new Bucket[_buckets.Length];
521            } else {
522                int curSize = into._buckets.Length;
523                while (curSize < _count + into._count) {
524                    curSize *= ResizeMultiplier;
525                }
526                into.EnsureSize(curSize);
527            }
528
529            if (into._keyType == null) {
530                into._keyType = _keyType;
531                into._hashFunc = _hashFunc;
532                into._eqFunc = _eqFunc;
533            } else if (into._keyType != _keyType) {
534                SetHeterogeneousSites();
535            }
536
537            for (int i = 0; i < _buckets.Length; i++) {
538                Bucket curBucket = _buckets[i];
539                while (curBucket != null) {
540                    if (AddWorker(into._buckets, curBucket.Key, curBucket.Value, curBucket.HashCode)) {
541                        into._count++;
542                    }
543                    curBucket = curBucket.Next;
544                }
545            }            
546        }
547
548        private void UncommonCopyTo(DictionaryStorage into) {
549            for (int i = 0; i < _buckets.Length; i++) {
550                Bucket curBucket = _buckets[i];
551                while (curBucket != null) {
552                    into.AddNoLock(curBucket.Key, curBucket.Value);
553
554                    curBucket = curBucket.Next;
555                }
556            }
557        }
558
559        /// <summary>
560        /// Helper to hash the given key w/ support for null.
561        /// </summary>
562        private int Hash(object key) {
563            if (key is string) return key.GetHashCode() & Int32.MaxValue;
564
565            return _hashFunc(key) & Int32.MaxValue;
566        }
567
568        /// <summary>
569        /// Used to store a single hashed key/value and a linked list of
570        /// collisions.
571        /// 
572        /// Bucket is not serializable because it stores the computed hash
573        /// code which could change between serialization and deserialization.
574        /// </summary>
575        private class Bucket {
576            public object Key;          // the key to be hashed
577            public object Value;        // the value associated with the key
578            public Bucket Next;         // the next chained bucket when there's a collision
579            public int HashCode;        // the hash code of the contained key.
580
581            public Bucket() {
582            }
583
584            public Bucket(int hashCode, object key, object value, Bucket next) {
585                HashCode = hashCode;
586                Key = key;
587                Value = value;
588                Next = next;
589            }
590
591            public Bucket Clone() {
592                return new Bucket(HashCode, Key, Value, CloneNext());
593            }
594
595            private Bucket CloneNext() {
596                if (Next == null) return null;
597                return Next.Clone();
598            }
599        }
600
601        #region Hash/Equality Delegates
602
603        private static int PrimitiveHash(object o) {
604            return o.GetHashCode();
605        }
606
607        private static int IntHash(object o) {
608            return (int)o;
609        }
610
611        private static int DoubleHash(object o) {
612            return DoubleOps.__hash__((double)o);
613        }
614
615        private static int GenericHash(object o) {
616            return PythonOps.Hash(DefaultContext.Default, o);
617        }
618
619        private static int TupleHash(object o) {
620            return ((IValueEquality)o).GetValueHashCode();
621        }
622
623        private static bool StringEquals(object o1, object o2) {
624            return (string)o1 == (string)o2;
625        }
626
627        private static bool IntEquals(object o1, object o2) {
628            return (int)o1 == (int)o2;
629        }
630
631        private static bool DoubleEquals(object o1, object o2) {
632            return (double)o1 == (double)o2;
633        }
634
635        private static bool TupleEquals(object o1, object o2) {
636            return ((IValueEquality)o1).ValueEquals(o2);
637        }
638
639        private static bool GenericEquals(object o1, object o2) {
640            return PythonOps.EqualRetBool(o1, o2);
641        }
642
643        #endregion
644
645#if !SILVERLIGHT
646
647        /// <summary>
648        /// Special marker bucket used during deserialization to not add
649        /// an extra field to the dictionary storage type.
650        /// </summary>
651        private class DeserializationBucket : Bucket {            
652            public readonly SerializationInfo/*!*/ SerializationInfo;
653
654            public DeserializationBucket(SerializationInfo info) {
655                SerializationInfo = info;
656            }
657        }
658
659        private DeserializationBucket GetDeserializationBucket() {
660            if (_buckets == null) {
661                return null;
662            }
663
664            if (_buckets.Length != 1) {
665                return null;
666            }
667
668            return _buckets[0] as DeserializationBucket;
669        }
670
671        #region ISerializable Members
672
673        public void GetObjectData(SerializationInfo info, StreamingContext context) {
674            info.AddValue("buckets", GetItems());            
675        }
676
677        #endregion
678
679        #region IDeserializationCallback Members
680
681        void IDeserializationCallback.OnDeserialization(object sender) {
682            DeserializationBucket bucket = GetDeserializationBucket();
683            if (bucket == null) {
684                // we've received multiple OnDeserialization callbacks, only 
685                // deserialize after the 1st one
686                return;
687            }
688
689            SerializationInfo info = bucket.SerializationInfo;
690            _buckets = null;
691
692            var buckets = (List<KeyValuePair<object, object>>)info.GetValue("buckets", typeof(List<KeyValuePair<object, object>>));
693
694            foreach (KeyValuePair<object, object> kvp in buckets) {
695                Add(kvp.Key, kvp.Value);
696            }
697        }
698
699        #endregion
700#endif
701    }
702
703}