diff --git a/BeefLibs/corlib/src/Array.bf b/BeefLibs/corlib/src/Array.bf index a9830201..1059a288 100644 --- a/BeefLibs/corlib/src/Array.bf +++ b/BeefLibs/corlib/src/Array.bf @@ -111,6 +111,12 @@ namespace System arr[i] = default(T); } + public static void Clear(T* arr, int length) + { + for (int i = 0; i < length; i++) + arr[i] = default(T); + } + public static void Copy(T[] arrayFrom, int srcOffset, T2[] arrayTo, int dstOffset, int length) where T : var where T2 : var { if (((Object)arrayTo == (Object)arrayFrom) && (dstOffset > srcOffset)) diff --git a/BeefLibs/corlib/src/Collections/Generic/List.bf b/BeefLibs/corlib/src/Collections/Generic/List.bf index d8dd2aa1..b8bf53e1 100644 --- a/BeefLibs/corlib/src/Collections/Generic/List.bf +++ b/BeefLibs/corlib/src/Collections/Generic/List.bf @@ -35,16 +35,6 @@ namespace System.Collections.Generic const int_cosize DynAllocFlag = (int_cosize)0x80000000; #endif -#if BF_ENABLE_REALTIME_LEAK_CHECK - static DbgRawAllocData sRawAllocData; - public static this() - { - sRawAllocData.mMarkFunc = null; - sRawAllocData.mMaxStackTrace = 1; - sRawAllocData.mType = typeof(T); - } -#endif - private T* mItems; private int_cosize mSize; private int_cosize mAllocSizeAndFlags; @@ -262,13 +252,7 @@ namespace System.Collections.Generic protected T* Alloc(int size) { -#if BF_ENABLE_REALTIME_LEAK_CHECK - // We don't want to use the default mark function because the GC will mark the entire array, - // whereas we have a custom marking routine because we only want to mark up to mSize - return (T*)Internal.Dbg_RawAlloc(size * strideof(T), &sRawAllocData); -#else - return new T[size]*(?); -#endif + return Internal.AllocRawArrayUnmarked(size); } protected void Free(T* val) diff --git a/BeefLibs/corlib/src/Collections/Generic/Queue.bf b/BeefLibs/corlib/src/Collections/Generic/Queue.bf index 13502867..98609ae5 100644 --- a/BeefLibs/corlib/src/Collections/Generic/Queue.bf +++ b/BeefLibs/corlib/src/Collections/Generic/Queue.bf @@ -10,12 +10,22 @@ namespace System.Collections.Generic { using System; using System.Diagnostics; + using System.Threading; /// A simple Queue of generic items. Internally it is implemented as a /// circular buffer, so Enqueue can be O(n). Dequeue is O(1). public class Queue : IEnumerable //, System.Collections.ICollection, IReadOnlyCollection { - private T[] mArray; +#if BF_LARGE_COLLECTIONS + const int_cosize SizeFlags = 0x7FFFFFFF'FFFFFFFF; + const int_cosize DynAllocFlag = (int_cosize)0x80000000'00000000; +#else + const int_cosize SizeFlags = 0x7FFFFFFF; + const int_cosize DynAllocFlag = (int_cosize)0x80000000; +#endif + + private T* mItems; + private int_cosize mAllocSizeAndFlags; private int_cosize mHead; // First valid element in the queue private int_cosize mTail; // Last valid element in the queue private int_cosize mSize; // Number of elements. @@ -29,26 +39,44 @@ namespace System.Collections.Generic private const int32 cShrinkThreshold = 32; private const int32 cGrowFactor = 200; // double each time private const int32 cDefaultCapacity = 4; - static T[] sEmptyArray = new T[0] ~ delete _; - + /// Creates a queue with room for capacity objects. The default initial /// capacity and grow factor are used. public this() { - mArray = sEmptyArray; } /// Creates a queue with room for capacity objects. The default grow factor /// is used. + [AllowAppend] public this(int capacity) { + T* items = append T[capacity]* (?); Debug.Assert(capacity >= 0); - mArray = new T[capacity]; + mItems = items; mHead = 0; mTail = 0; mSize = 0; + if (capacity > 0) + { + mItems = items; + mAllocSizeAndFlags = (int_cosize)(capacity & SizeFlags); + } } - + + public ~this() + { + if (IsDynAlloc) + { + var items = mItems; +#if BF_ENABLE_REALTIME_LEAK_CHECK + mItems= null; + Interlocked.Fence(); +#endif + Free(items); + } + } + // Fills a Queue with the elements of an ICollection. Uses the enumerator // to get each of the elements. // @@ -71,25 +99,43 @@ namespace System.Collections.Generic } } }*/ - + + public int AllocSize + { + [Inline] + get + { + return mAllocSizeAndFlags & SizeFlags; + } + } + + public bool IsDynAlloc + { + [Inline] + get + { + return (mAllocSizeAndFlags & DynAllocFlag) != 0; + } + } public int Count { get { return mSize; } } - + + protected T* Alloc(int size) + { + return Internal.AllocRawArrayUnmarked(size); + } + + protected void Free(T* val) + { + delete (void*)val; + } + /// Removes all items from the queue. public void Clear() { -#if BF_ENABLE_REALTIME_LEAK_CHECK - if (mHead < mTail) - Array.Clear(mArray, mHead, mSize); - else - { - Array.Clear(mArray, mHead, mArray.Count - mHead); - Array.Clear(mArray, 0, mTail); - } -#endif mHead = 0; mTail = 0; mSize = 0; @@ -97,7 +143,7 @@ namespace System.Collections.Generic mVersion++; #endif } - + /// CopyTo copies a collection into an Array, starting at a particular /// index into the array. public void CopyTo(T[] array, int arrayIndex) @@ -109,12 +155,15 @@ namespace System.Collections.Generic int numToCopy = (arrayLen - arrayIndex < mSize) ? (arrayLen - arrayIndex) : mSize; if (numToCopy == 0) return; - int firstPart = (mArray.Count - mHead < numToCopy) ? mArray.Count - mHead : numToCopy; - Array.Copy(mArray, mHead, array, arrayIndex, firstPart); + int firstPart = (AllocSize - mHead < numToCopy) ? AllocSize - mHead : numToCopy; + //Array.Copy(mArray, mHead, array, arrayIndex, firstPart); + Internal.MemCpy(&array.[Friend]GetRef(arrayIndex), mItems + mHead, firstPart * strideof(T), alignof(T)); + numToCopy -= firstPart; if (numToCopy > 0) { - Array.Copy(mArray, 0, array, arrayIndex + mArray.Count - mHead, numToCopy); + //Array.Copy(mArray, 0, array, arrayIndex + AllocSize - mHead, numToCopy); + Internal.MemCpy(&array.[Friend]GetRef(arrayIndex + AllocSize - mHead), mItems, numToCopy * strideof(T), alignof(T)); } } @@ -122,18 +171,18 @@ namespace System.Collections.Generic /// Adds item to the tail of the queue. public void Enqueue(T item) { - if (mSize == mArray.Count) + if (mSize == AllocSize) { - int newcapacity = (int)((int64)mArray.Count * (int64)cGrowFactor / 100); - if (newcapacity < mArray.Count + cMinimumGrow) + int newcapacity = (int)((int64)AllocSize * (int64)cGrowFactor / 100); + if (newcapacity < AllocSize + cMinimumGrow) { - newcapacity = mArray.Count + cMinimumGrow; + newcapacity = AllocSize + cMinimumGrow; } SetCapacity(newcapacity); } - mArray[mTail] = item; - mTail = (mTail + 1) % (int_cosize)mArray.Count; + mItems[mTail] = item; + mTail = (mTail + 1) % (int_cosize)AllocSize; mSize++; #if VERSION_QUEUE mVersion++; @@ -155,9 +204,8 @@ namespace System.Collections.Generic //ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyQueue); Runtime.FatalError(); - T removed = mArray[mHead]; - mArray[mHead] = default(T); - mHead = (mHead + 1) % (int_cosize)mArray.Count; + T removed = mItems[mHead]; + mHead = (mHead + 1) % (int_cosize)AllocSize; mSize--; #if VERSION_QUEUE mVersion++; @@ -171,7 +219,7 @@ namespace System.Collections.Generic public T Peek() { Debug.Assert(mSize != 0); - return mArray[mHead]; + return mItems[mHead]; } /// Returns true if the queue contains at least one object equal to item. @@ -184,51 +232,33 @@ namespace System.Collections.Generic { if (((Object)item) == null) { - if (((Object)mArray[index]) == null) + if (((Object)mItems[index]) == null) return true; } - else if (mArray[index] != null && mArray[index] == item) + else if (mItems[index] != null && mItems[index] == item) { return true; } - index = (index + 1) % mArray.Count; + index = (index + 1) % AllocSize; } return false; } T GetElement(int i) { - return mArray[(mHead + i) % mArray.Count]; + return mItems[(mHead + i) % AllocSize]; } - - /// Iterates over the objects in the queue, returning an array of the - /// objects in the Queue, or an empty array if the queue is empty. - /// The order of elements in the array is first in to last in, the same - /// order produced by successive calls to Dequeue. - public T[] ToArray() + + ref T GetElementRef(int i) { - T[] arr = new T[mSize]; - if (mSize == 0) - return arr; - - if (mHead < mTail) - { - Array.Copy(mArray, mHead, arr, 0, mSize); - }else - { - Array.Copy(mArray, mHead, arr, 0, mArray.Count - mHead); - Array.Copy(mArray, 0, arr, mArray.Count - mHead, mTail); - } - - return arr; + return ref mItems[(mHead + i) % AllocSize]; } - - + // PRIVATE Grows or shrinks the buffer to hold capacity objects. Capacity // must be >= _size. - private void SetCapacity(int capacity) + private void SetCapacity(int value) { - T[] newarray = new T[capacity]; + /*T* newarray = new T[capacity]*; if (mSize > 0) { if (mHead < mTail) @@ -236,22 +266,82 @@ namespace System.Collections.Generic Array.Copy(mArray, mHead, newarray, 0, mSize); }else { - Array.Copy(mArray, mHead, newarray, 0, mArray.Count - mHead); - Array.Copy(mArray, 0, newarray, mArray.Count - mHead, mTail); + Array.Copy(mArray, mHead, newarray, 0, AllocSize - mHead); + Array.Copy(mArray, 0, newarray, AllocSize - mHead, mTail); } + }*/ + + if (value > 0) + { + T* newItems = Alloc(value); + +#if DBG + int typeId = typeof(T).GetTypeId(); + if (typeId == sDebugTypeId) + { + Debug.WriteLine("Alloc {0} {1} {2}", scope Object[] { this, newItems, sDebugIdx } ); + sDebugIdx++; + } +#endif + + if (mSize > 0) + { + if (mHead < mTail) + { + Internal.MemCpy(newItems, mItems + mHead, mSize * strideof(T), alignof(T)); + + }else + { + Internal.MemCpy(newItems, mItems + mHead, (AllocSize - mHead) * strideof(T), alignof(T)); + Internal.MemCpy(newItems + (AllocSize - mHead), mItems, mTail * strideof(T), alignof(T)); + } + + } + + var oldItems = mItems; + mItems = newItems; + if (IsDynAlloc) + { +#if BF_ENABLE_REALTIME_LEAK_CHECK + // We need to avoid scanning a deleted mItems + Interlocked.Fence(); +#endif + Free(oldItems); + } + mAllocSizeAndFlags = (.)(value | DynAllocFlag); + } + else + { + if (IsDynAlloc) + Free(mItems); + mItems = null; + mAllocSizeAndFlags = 0; } - mArray = newarray; mHead = 0; - mTail = (mSize == capacity) ? 0 : mSize; + mTail = (mSize == value) ? 0 : mSize; #if VERSION_QUEUE mVersion++; #endif } + protected override void GCMarkMembers() + { + if (mItems == null) + return; + let type = typeof(T); + if ((type.[Friend]mTypeFlags & .WantsMark) == 0) + return; + int allocSize = AllocSize; + for (int i < mSize) + { + GC.Mark_Unbound(mItems[(i + mHead) % allocSize]); + } + } + public void TrimExcess() { - int32 threshold = (int32)(((double)mArray.Count) * 0.9); + int32 threshold = (int32)(((double)AllocSize) * 0.9); if (mSize < threshold) { SetCapacity(mSize); @@ -261,14 +351,14 @@ namespace System.Collections.Generic /// Implements an enumerator for a Queue. The enumerator uses the /// internal version number of the list to ensure that no modifications are /// made to the list while an enumeration is in progress. - public struct Enumerator : IEnumerator + public struct Enumerator : IRefEnumerator { private Queue mQueue; private int32 mIndex; // -1 = not started, -2 = ended/disposed #if VERSION_QUEUE private int32 mVersion; #endif - private T mCurrentElement; + private T* mCurrentElement; public this(Queue q) { @@ -277,7 +367,7 @@ namespace System.Collections.Generic mVersion = mQueue.mVersion; #endif mIndex = -1; - mCurrentElement = default(T); + mCurrentElement = null; } #if VERSION_QUEUE @@ -291,7 +381,7 @@ namespace System.Collections.Generic public void Dispose() mut { mIndex = -2; - mCurrentElement = default(T); + mCurrentElement = null; } public bool MoveNext() mut @@ -307,11 +397,11 @@ namespace System.Collections.Generic if (mIndex == mQueue.mSize) { mIndex = -2; - mCurrentElement = default(T); + mCurrentElement = null; return false; } - mCurrentElement = mQueue.GetElement(mIndex); + mCurrentElement = &mQueue.GetElementRef(mIndex); return true; } @@ -326,7 +416,22 @@ namespace System.Collections.Generic else Runtime.FatalError("Enumeration ended"); } - return mCurrentElement; + return *mCurrentElement; + } + } + + public ref T CurrentRef + { + get + { + if (mIndex < 0) + { + if (mIndex == -1) + Runtime.FatalError("Enumeration not started"); + else + Runtime.FatalError("Enumeration ended"); + } + return ref *mCurrentElement; } } @@ -345,6 +450,13 @@ namespace System.Collections.Generic return .Err; return Current; } + + public Result GetNextRef() mut + { + if (!MoveNext()) + return .Err; + return &CurrentRef; + } } } } diff --git a/BeefLibs/corlib/src/Internal.bf b/BeefLibs/corlib/src/Internal.bf index 24eafddf..77a89b07 100644 --- a/BeefLibs/corlib/src/Internal.bf +++ b/BeefLibs/corlib/src/Internal.bf @@ -8,6 +8,17 @@ namespace System public Type mType; public void* mMarkFunc; public int32 mMaxStackTrace; + + public struct Unmarked + { + public static DbgRawAllocData sRawAllocData; + public static this() + { + sRawAllocData.mMarkFunc = null; + sRawAllocData.mMaxStackTrace = 1; + sRawAllocData.mType = typeof(T); + } + } } [AlwaysInclude] @@ -81,6 +92,17 @@ namespace System sModuleHandle = handle; } + public static T* AllocRawArrayUnmarked(int size) + { +#if BF_ENABLE_REALTIME_LEAK_CHECK + // We don't want to use the default mark function because the GC will mark the entire array, + // whereas we have a custom marking routine because we only want to mark up to mSize + return (T*)Internal.Dbg_RawAlloc(size * strideof(T), &DbgRawAllocData.Unmarked.sRawAllocData); +#else + return new T[size]*(?); +#endif + } + public static Object ObjectAlloc(TypeInstance typeInst, int size) { #if BF_ENABLE_OBJECT_DEBUG_FLAGS diff --git a/IDE/dist/BeefDbgVis.toml b/IDE/dist/BeefDbgVis.toml index 5d5b711a..59284843 100644 --- a/IDE/dist/BeefDbgVis.toml +++ b/IDE/dist/BeefDbgVis.toml @@ -219,10 +219,36 @@ DisplayString = "{{ count={mSize} }}" [[Type.Expand.Item]] Name = "[Count]" Value = "mSize" +[[Type.Expand.Item]] +Condition = "__getHighBits(mAllocSizeAndFlags, 1) == 1" +Name = "[AllocSize]" +Value = "__clearHighBits(mAllocSizeAndFlags, 1)" +[[Type.Expand.Item]] +Condition = "__getHighBits(mAllocSizeAndFlags, 1) == 0" +Name = "[InternalSize]" +Value = "__clearHighBits(mAllocSizeAndFlags, 1)" [Type.Expand.ArrayItems] Size = "mSize" ValuePointer = "mItems" +[[Type]] +Name = "System.Collections.Generic.Queue<*>" +DisplayString = "{{ count={mSize} }}" +[[Type.Expand.Item]] +Name = "[Count]" +Value = "mSize" +[[Type.Expand.Item]] +Condition = "__getHighBits(mAllocSizeAndFlags, 1) == 1" +Name = "[AllocSize]" +Value = "__clearHighBits(mAllocSizeAndFlags, 1)" +[[Type.Expand.Item]] +Condition = "__getHighBits(mAllocSizeAndFlags, 1) == 0" +Name = "[InternalSize]" +Value = "__clearHighBits(mAllocSizeAndFlags, 1)" +[Type.Expand.IndexListItems] +Size = "mSize" +ValueNode = "mItems[($i + mHead) % __clearHighBits(mAllocSizeAndFlags, 2)]" + [[Type]] Name = "System.Collections.Generic.Dictionary<*, *>" DisplayString = "{{ count={mCount - mFreeCount} }}"