/*******************************************************************/ /* WARNING */ /* This file should be identical in the Bartok and Singularity */ /* depots. Master copy resides in Bartok Depot. Changes should be */ /* made to Bartok Depot and propagated to Singularity Depot. */ /*******************************************************************/ // // Copyright (c) Microsoft Corporation. All rights reserved. // namespace System.GCs { using Microsoft.Bartok.Options; using Microsoft.Bartok.Runtime; using System.Runtime.CompilerServices; using System.Threading; #if SINGULARITY using Microsoft.Singularity; #endif /// /// This class supports Allocate/Free operations using memory obtained /// from the PageManager. /// /// Objects that are small enough that multiple instances of them can fit /// on a page are allocated from pages of identically sized memory cells. /// /// Large objects are allocated on separate pages. /// /// This class is designed to be thread safe on both allocation and free /// operations. At this stage it is required that RecycleGlobalPages is /// called periodically to reconsider previously full pages for inclusion /// in allocation lists. /// /// The free operation is currently more expensive than required due to /// space accounting. /// /// This class also keeps up to date memory accounting information. Note /// that modifications to this data is not synchronized so it is possible /// that the counts drift from actual figures over time. /// internal unsafe struct SegregatedFreeList /* : Allocator */ { #region Mixins [MixinConditional("SegregatedFreeList")] [MixinConditional("AllThreadMixins")] [Mixin(typeof(Thread))] private class SegregatedFreeListThread : Object { // Thread-specific segregated free-list allocator [RequiredByBartok] internal SegregatedFreeList segregatedFreeList; } [Inline] private static SegregatedFreeListThread MixinThread(Thread t) { return (SegregatedFreeListThread) (Object) t; } #endregion #region Global (Safe from the context of any thread) #region Constants /// /// Small objects. /// internal const PageType SMALL_OBJ_PAGE = PageType.Owner2; /// /// This is a page that is being set up. /// internal const PageType INIT_PAGE = PageType.Owner4; /// /// Large objects. /// internal const PageType LARGE_OBJ_PAGE = PageType.Owner3; /// /// BUGBUG: Duplicated constant. /// internal const int LOG_PAGE_SIZE = 12; /// /// The size of each block (which contains a single object size) /// internal const int BLOCK_SIZE = (1 << LOG_PAGE_SIZE) - PageHeader.SIZEOF; /// /// The threshold where objects become large objects. /// internal const int LARGE_OBJECT_THRESHOLD = (BLOCK_SIZE >> 1) + 1; /// /// The largest possible size class. /// internal const int SIZE_CLASSES = 33 + ((LARGE_OBJECT_THRESHOLD-1) >> 7); #endregion #region Size Class Mappings /// /// Returns the size class index for an object of the specified size. /// /// BUGBUG: We hope that from an inlined allocation site this is /// resolved as a constant. That is why this was changed to remove /// indirection through an index lookup table. /// [Inline] private static int GetSizeClass(UIntPtr bytes) { // Exact request rounds down to lower size class. bytes = bytes - new UIntPtr(1); // Sizes 0 -> 64 are in classes 0 -> 15 // Sizes 72 -> 128 are in classes 16 -> 23 // Sizes 140 -> 256 are in classes 24 -> 31 // Sizes 256 -> 512 are in classes 32 -> 35 // We skip a power of 2 because large objects are rare. //Sizes 512 and up are in classes 36 -> 41 if (bytes < 64) return 0 + ((int)bytes >> 2); if (bytes < 128) return 8 + ((int)bytes >> 3); if (bytes < 256) return 16 + ((int)bytes >> 4); if (bytes < 512) return 28 + ((int)bytes >> 6); if (bytes < LARGE_OBJECT_THRESHOLD) return 32 + ((int)bytes >> 7); throw new Exception("GetSizeClass called on large object size"); } /// /// Returns the cell size for a given size class. /// private static UIntPtr GetCellSize(int sizeClass) { VTable.Assert(sizeClass < SIZE_CLASSES, "Attempt cellSize for invalid sizeClass"); uint sc = (uint)sizeClass + 1; uint bytes; if (sc <= 16) bytes = (sc - 0) << 2; else if (sc <= 24) bytes = (sc - 8) << 3; else if (sc <= 32) bytes = (sc - 16) << 4; else if (sc <= 36) bytes = (sc - 28) << 6; else bytes = (sc - 32) << 7; return new UIntPtr(bytes); } #endregion #region Memory Accounting /// /// A count of the bytes allocated for small objects. /// internal static UIntPtr SmallBytes; /// /// A count of the bytes for objects the process of being freed. /// internal static UIntPtr SmallFreedBytes; /// /// A count of the small pages in the process of being freed. /// internal static UIntPtr SmallFreedPages; /// /// A count of the large pages in the process of being freed. /// internal static UIntPtr LargeFreedPages; /// /// A count of the bytes allocated for large objects. /// internal static UIntPtr LargeBytes { get { return PageTable.RegionSize(LargePages); } } /// /// The number of pages reserved for large objects. /// internal static UIntPtr LargePages; /// /// The number of pages reserved for small objects. /// internal static UIntPtr SmallPages; /// /// This is the total size of data (including object headers) /// internal static UIntPtr TotalBytes { get { return SmallBytes + LargeBytes; } } /// /// The number of pages managed by the alloc heap, including space /// occupied by empty cells. /// internal static UIntPtr ReservedBytes { get { return PageTable.RegionSize(SmallPages) + LargeBytes; } } /// /// Increment the counter of the number of small bytes allocated. /// [Inline] private static void AddSmallBytes(UIntPtr newBytes) { SmallBytes += newBytes; GC.newBytesSinceGC += newBytes; } /// /// Increment the counter of the number of large bytes allocated. /// [Inline] private static void AddLargePages(UIntPtr pageCount) { LargePages += pageCount; GC.newBytesSinceGC += PageTable.RegionSize(pageCount); } /// /// Decrement the counter of the number of small bytes allocated. /// [Inline] private static void SubSmallBytes(UIntPtr newBytes) { SmallFreedBytes += newBytes; } /// /// Decrement the counter of the number of large pages allocated. /// [Inline] private static void SubLargePages(UIntPtr pageCount) { LargeFreedPages += pageCount; } /// /// Increment the counter of the number of small pages allocated. /// [Inline] private static void AddSmallPage() { SmallPages += new UIntPtr(1); AddSmallBytes(PageTable.PageSize); } /// /// Decrement the counter of the number of small pages allocated. /// [Inline] private static void SubSmallPage(PageHeader *page) { SmallFreedPages += new UIntPtr(1); SubSmallBytes(PageTable.PageSize - (page->cellSize * (UIntPtr) page->freeCount)); } /// /// Subtract and zero the freed data counts. /// internal static void CommitFreedData() { SmallPages -= SmallFreedPages; SmallFreedPages = new UIntPtr(0); LargePages -= LargeFreedPages; LargeFreedPages = new UIntPtr(0); SmallBytes -= SmallFreedBytes; SmallFreedBytes = new UIntPtr(0); } #endregion /// /// When notified of the creation of a new thread we initialize the /// alloc heap in that thread. /// internal unsafe static void NewThreadNotification(Thread newThread, bool initial) { SegregatedFreeListThread mixinThread = MixinThread(newThread); if (initial) { // Initialise the initial thread. mixinThread.segregatedFreeList.localPages = (PageHeader*[]) BootstrapMemory.Allocate(typeof(PageHeader*[]), SIZE_CLASSES); mixinThread.segregatedFreeList.freeList = (UIntPtr[]) BootstrapMemory.Allocate(typeof(UIntPtr[]), SIZE_CLASSES); } else { // We have to create the thread-specific array of pages. mixinThread.segregatedFreeList.freeList = new UIntPtr[SIZE_CLASSES]; mixinThread.segregatedFreeList.localPages = new PageHeader*[SIZE_CLASSES]; } } /// /// A thread has finished, so we release any local pages. /// internal static void DeadThreadNotification(Thread deadThread) { SegregatedFreeListThread mixinThread = MixinThread(deadThread); for(int i=0; i < SIZE_CLASSES; i++) { if (mixinThread.segregatedFreeList.localPages[i] != null) { mixinThread.segregatedFreeList.ReleaseLocalPage(i); } } } /// /// Allocate a large object. Large objects don't share pages with /// any other objects. Get memory for the object directly from the /// PageManager. /// [ManualRefCounts] private static unsafe UIntPtr AllocateLarge(uint alignment, UIntPtr bytes, Thread currentThread) { UIntPtr pageCount = PageTable.PageCount(PageTable.PagePad(bytes)); bool fCleanPages = true; UIntPtr page = PageManager.EnsurePages(currentThread, pageCount, INIT_PAGE, ref fCleanPages); UIntPtr regionSize = PageTable.RegionSize(pageCount); AddLargePages(pageCount); int unusedBytes = (int) (regionSize - bytes); int unusedCacheLines = unusedBytes >> 5; int offset; if (unusedCacheLines != 0) { offset = (largeOffset % unusedCacheLines) << 5; largeOffset = (largeOffset + 1) & ((int)PageTable.PageSize - 1); } else { offset = 0; } UIntPtr pageAddr = PageTable.PageAddr(page); UIntPtr startAddr = pageAddr + offset + PreHeader.Size; UIntPtr resultAddr = Allocator.AlignedObjectPtr(startAddr, alignment); short shortOff = (short) (resultAddr - pageAddr); VTable.Assert(shortOff > 0, "offset not positive"); PageTable.SetExtra(page, shortOff); // Ready to be visited PageTable.SetType(page, pageCount, LARGE_OBJ_PAGE); return resultAddr; } /// /// Free the specified object. For large objects the page becomes /// immediately available. For small objects it may require a call /// to RecycleGlobalPages. /// [ManualRefCounts] internal static void Free(Object obj) { UIntPtr objectStart = Magic.addressOf(obj) - PreHeader.Size; UIntPtr page = PageTable.Page(objectStart); PageType pageType = PageTable.Type(page); // Free the object if (pageType == SMALL_OBJ_PAGE) { FreeSmall(obj); } else { VTable.Assert(pageType == LARGE_OBJ_PAGE, "Found GC Page not small or large"); FreeLarge(obj); } } /// /// Free a small object. /// [ManualRefCounts] internal static void FreeSmall(Object obj) { UIntPtr objectAddr = Magic.addressOf(obj); // Put the object memory cell on the freelist for the page UIntPtr pageAddr = PageTable.PageAlign(objectAddr); PageHeader *pageHeader = (PageHeader*) pageAddr; UIntPtr cellStart; if (obj.vtable.baseAlignment > UIntPtr.Size) { cellStart = FindSmallCell(objectAddr); VTable.Assert(cellStart > (UIntPtr) pageHeader && cellStart <= objectAddr && cellStart+pageHeader->cellSize >= objectAddr, "find small cell found invalid start"); } else { cellStart = objectAddr - PreHeader.Size; } Util.MemClear(cellStart, pageHeader->cellSize); SubSmallBytes(pageHeader->cellSize); UIntPtr oldFreeList; cellStart = (cellStart + PreHeader.Size); do { oldFreeList = pageHeader->freeList; *(UIntPtr *)cellStart = oldFreeList; } while (Interlocked.CompareExchange(ref pageHeader->freeList, cellStart, oldFreeList) != oldFreeList); Interlocked.Increment(ref pageHeader->freeCount); } internal unsafe struct TempList { private UIntPtr next; internal void Add(UIntPtr memAddr) { *(UIntPtr *) memAddr = this.next; this.next = memAddr; } internal UIntPtr GetList() { UIntPtr result = this.next; this.next = UIntPtr.Zero; return result; } } [ManualRefCounts] internal static void FreeSmallList(ref TempList tempList) { UIntPtr cell = tempList.GetList(); if (cell != UIntPtr.Zero) { PageHeader *pageHeader = (PageHeader *) PageTable.PageAlign(cell); UIntPtr newChain = UIntPtr.Zero; UIntPtr newChainTail = cell + PreHeader.Size; UIntPtr cellSize = pageHeader->cellSize; int count = 0; do { count++; UIntPtr next = *(UIntPtr *) cell; Util.MemClear(cell, cellSize); UIntPtr cellPtrAddr = cell + PreHeader.Size; *(UIntPtr *) cellPtrAddr = newChain; newChain = cellPtrAddr; cell = next; } while (cell != UIntPtr.Zero); SubSmallBytes((UIntPtr) count * cellSize); UIntPtr oldFreeList; do { oldFreeList = pageHeader->freeList; *(UIntPtr*)newChainTail = oldFreeList; } while (Interlocked.CompareExchange(ref pageHeader->freeList, newChain, oldFreeList) != oldFreeList); int oldFreeCount, newFreeCount; do { oldFreeCount = pageHeader->freeCount; newFreeCount = oldFreeCount + count; } while (Interlocked.CompareExchange(ref pageHeader->freeCount, newFreeCount, oldFreeCount) != oldFreeCount); } } /// /// Free a large object. /// [ManualRefCounts] internal static void FreeLarge(Object obj) { UIntPtr objectStart = Magic.addressOf(obj) - PreHeader.Size; UIntPtr firstPage = PageTable.Page(objectStart); // Release the page(s) that the object resides on UIntPtr pageAddr = PageTable.PageAlign(objectStart); UIntPtr objectSize = ObjectLayout.Sizeof(obj); UIntPtr limitPage = PageTable.Page(PageTable.PagePad(objectStart + objectSize)); UIntPtr pageCount = limitPage - firstPage; PageTable.SetType(firstPage, pageCount, INIT_PAGE); PageTable.SetExtra(PageTable.Page(pageAddr), 0); PageManager.ReleaseUnusedPages(firstPage, pageCount, false); SubLargePages(pageCount); } /// /// Given a possibly interior pointer to an object, return the /// real address of the object. /// internal static unsafe UIntPtr Find(UIntPtr addr) { UIntPtr page = PageTable.Page(addr); PageType pageType = PageTable.Type(page); VTable.Assert(PageTable.IsGcPage(pageType), "Attempt find on non-GC page"); if (pageType == SMALL_OBJ_PAGE) { return FindSmall(addr); } else { return FindLarge(addr); } } /// /// Find a small object (after determining it is on a small page) /// private static UIntPtr FindSmall(UIntPtr cellAddr) { UIntPtr objectAddr = FindSmallCell(cellAddr) + PreHeader.Size; objectAddr = Allocator.SkipAlignment(objectAddr); return objectAddr; } /// /// Find the cell for a given object address /// private static unsafe UIntPtr FindSmallCell(UIntPtr addr) { UIntPtr pageAddr = PageTable.PageAlign(addr - PreHeader.Size); PageHeader *pageHeader = (PageHeader *) pageAddr; UIntPtr firstAddr = pageAddr + PageHeader.SIZEOF; return addr - ((int)(addr - firstAddr) % (int)pageHeader->cellSize); } /// /// Find a large object (after determining it is on a large page) /// private static UIntPtr FindLarge(UIntPtr addr) { UIntPtr page = PageTable.Page(addr - PreHeader.Size); short brickData = PageTable.Extra(page); while (brickData == 0) { page--; brickData = PageTable.Extra(page); VTable.Assert(PageTable.Type(page) == LARGE_OBJ_PAGE, "page type invalid"); } return (PageTable.PageAddr(page) + brickData); } internal abstract class ObjectVisitor : ObjectLayout.ObjectVisitor { internal virtual void VisitSmall(Object obj, UIntPtr memAddr) { this.Visit(obj); } internal virtual void VisitSmallPageEnd() { } internal virtual UIntPtr VisitLarge(Object obj) { return this.Visit(obj); } internal override UIntPtr Visit(Object obj) { VTable.NotReached("Someone forgot an override method in a "+ "subclass of SegregatedFreeList.ObjectVisitor"); return UIntPtr.Zero; } } // Wraps an SegregatedFreeList.ObjectVisitor around a // ObjectLayout.ObjectVisitor. // Both large and small objects are visited by the same // ObjectLayout.ObjectVisitor. internal class ObjectVisitorWrapper : ObjectVisitor { private ObjectLayout.ObjectVisitor visitor; internal ObjectVisitorWrapper(ObjectLayout.ObjectVisitor visitor) { this.visitor = visitor; } internal override void VisitSmall(Object obj, UIntPtr memAddr) { this.visitor.Visit(obj); } internal override UIntPtr VisitLarge(Object obj) { return this.visitor.Visit(obj); } } /// /// Visit each object in the heap across all pages. /// [ManualRefCounts] internal static void VisitAllObjects(ObjectVisitor visitor) { VisitObjects(UIntPtr.Zero, PageTable.pageTableCount, visitor); } /// /// Visit each object in the heap across a range of pages. /// /// This can be run concurrent to allocations, but not frees. /// [ManualRefCounts] internal static void VisitObjects(UIntPtr lowPage, UIntPtr highPage, ObjectVisitor visitor) { for (UIntPtr i = lowPage; i < highPage; i++) { PageType pageType = PageTable.MyType(i); if (pageType == INIT_PAGE) { // Not yet ready for allocation so we can't visit it... } else if (pageType == SMALL_OBJ_PAGE) { VisitSmallObjects(i, visitor); } else if (pageType == LARGE_OBJ_PAGE) { UIntPtr j = i + 1; while (j < highPage && PageTable.MyType(j) == LARGE_OBJ_PAGE) { j++; } UIntPtr largeObjectPageCount = VisitLargeObject(i, visitor); i += largeObjectPageCount - 1; VTable.Assert(i <= j); } } } internal static void VisitObjects(UIntPtr lowPage, UIntPtr highPage, ObjectLayout.ObjectVisitor visitor) { ObjectVisitor myObjectVisitor = visitor as ObjectVisitor; VTable.Assert(myObjectVisitor != null, "SegregatedFreeList requires specialized ObjectVisitor"); VisitObjects(lowPage, highPage, myObjectVisitor); } /// /// Visit small objects in a single page. /// [ManualRefCounts] private static unsafe void VisitSmallObjects(UIntPtr page, ObjectVisitor visitor) { VTable.Assert(PageTable.Type(page) == SMALL_OBJ_PAGE, "Visiting small objects on invalid page"); UIntPtr pageAddr = PageTable.PageAddr(page); PageHeader *pageHeader = (PageHeader *) pageAddr; UIntPtr cellSize = pageHeader->cellSize; VTable.Assert(cellSize != UIntPtr.Zero, "zero cellSize visiting small"); UIntPtr lowAddr = pageAddr + PageHeader.SIZEOF; UIntPtr highAddr = PageTable.PagePad(lowAddr); while (lowAddr <= highAddr - cellSize) { UIntPtr objectAddr = lowAddr + PreHeader.Size; objectAddr = Allocator.SkipAlignment(objectAddr); Object maybeObject = Magic.fromAddress(objectAddr); UIntPtr vtablePtr = *maybeObject.VTableFieldAddr; if (vtablePtr != 0) { // In Singularity, it is not always allowed // to compute PageTable.Page(UIntPtr.Zero); UIntPtr vtablePage = PageTable.Page(vtablePtr); if (vtablePage != page) { // We have found a slot containing an object visitor.VisitSmall(maybeObject, lowAddr); } } lowAddr += cellSize; } visitor.VisitSmallPageEnd(); } /// /// Visit a large object on the specified page. /// [ManualRefCounts] private static UIntPtr VisitLargeObject(UIntPtr page, ObjectVisitor visitor) { VTable.Assert(PageTable.Type(page) == LARGE_OBJ_PAGE, "Visiting large object on invalid page"); // Find the object UIntPtr pageAddr = PageTable.PageAddr(page); short brickData = PageTable.Extra(page); if (brickData == 0) { // Possibly in the process of being allocated. return new UIntPtr(1); } UIntPtr objectAddr = pageAddr + brickData; Object obj = Magic.fromAddress(objectAddr); if (obj.vtable == null) { // Memory has been allocated, but object is not initialized return new UIntPtr(1); } // Visit the object UIntPtr objectSize = visitor.VisitLarge(obj); // Return the page count UIntPtr objectEnd = objectAddr + objectSize - PreHeader.Size; UIntPtr limitPage = PageTable.Page(PageTable.PagePad(objectEnd)); return limitPage - page; } /// /// This is the the free list of pages to allocate from. /// private static UIntPtr[] globalFreePages; /// /// This is the list of pages released by threads. These pages must /// be periodically processes to release them back for allocation if /// necessary. /// private static UIntPtr[] globalPages; // Used by RecycleGlobalPages to avoid the ABA problem of // lock-free data structures. private static UIntPtr[] stolenGlobalFreePages; private static UIntPtr[] stolenGlobalFullPages; /// /// Initialize the alloc heap by setting up the heads for all the /// linked lists. /// [PreInitRefCounts] internal static unsafe void Initialize() { // Global array of allocated pages globalPages = (UIntPtr[]) BootstrapMemory.Allocate(typeof(UIntPtr[]), SIZE_CLASSES); // Global array of pages with free elements globalFreePages = (UIntPtr[]) BootstrapMemory.Allocate(typeof(UIntPtr[]), SIZE_CLASSES); // Temporary list holders used by RecycleGlobalPages stolenGlobalFullPages = (UIntPtr[]) BootstrapMemory.Allocate(typeof(UIntPtr[]), SIZE_CLASSES); stolenGlobalFreePages = (UIntPtr[]) BootstrapMemory.Allocate(typeof(UIntPtr[]), SIZE_CLASSES); } /// /// Take all global pages that have had elements freed and put them in /// the allocation queues. /// internal static void RecycleGlobalPages() { RecycleGlobalPagesPhase1(); RecycleGlobalPagesPhase2(); } internal static void RecycleGlobalPagesPhase1() { for (int i=0; i < SIZE_CLASSES; i++) { // Steal chains VTable.Assert(stolenGlobalFullPages[i] == UIntPtr.Zero); VTable.Assert(stolenGlobalFreePages[i] == UIntPtr.Zero); stolenGlobalFullPages[i] = AtomicPopChain(ref globalPages[i]); stolenGlobalFreePages[i] = AtomicPopChain(ref globalFreePages[i]); } } internal static void RecycleGlobalPagesPhase2() { for (int i=0; i < SIZE_CLASSES; i++) { UIntPtr globalFull = stolenGlobalFullPages[i]; stolenGlobalFullPages[i] = UIntPtr.Zero; UIntPtr globalFree = stolenGlobalFreePages[i]; stolenGlobalFreePages[i] = UIntPtr.Zero; // Start with free pages (they can not become full) UIntPtr current = globalFree; // New free and full chains PageHeader *freeHead = null, freeTail = null; PageHeader *fullHead = null, fullTail = null; // Determine starting point if (current == UIntPtr.Zero) { current = globalFull; globalFull = UIntPtr.Zero; if (current == UIntPtr.Zero) { continue; } } // Number of cells of this size class in a block int cells = BLOCK_SIZE / (int) GetCellSize(i); VTable.Assert(cells > 0 && cells < (BLOCK_SIZE >> 1), "invalid cell count"); // Iterate through list while (current != UIntPtr.Zero) { PageHeader *page = (PageHeader*) current; current = page->nextPage; if (page->freeCount == cells) { // Completely Free Page SubSmallPage(page); UIntPtr pageNum = PageTable.Page(new UIntPtr(page)); PageTable.SetType(pageNum, INIT_PAGE); PageTable.SetExtra(pageNum, 0); PageManager.ReleaseUnusedPages(pageNum, new UIntPtr(1), false); } else if (page->freeCount > 0) { // Partially Free Page AddPageToList(page, ref freeHead, ref freeTail); } else { // Completely Full Page AddPageToList(page, ref fullHead, ref fullTail); } if (current == UIntPtr.Zero) { // Finished the free list, onto the full list. current = globalFull; globalFull = UIntPtr.Zero; } } // Reinsert values onto the free and full chains if (fullHead != null) { AtomicPushChain(ref globalPages[i], fullHead, fullTail); } if (freeHead != null) { AtomicPushChain(ref globalFreePages[i], freeHead, freeTail); } } } /// /// Add a page onto a local linked list (possibly the first page) /// private static void AddPageToList(PageHeader *page, ref PageHeader *head, ref PageHeader *tail) { if (head == null) { page->nextPage = UIntPtr.Zero; head = page; tail = page; return; } tail->nextPage = new UIntPtr(page); tail = page; } /// /// Atomically push a value onto the linked list. /// private static void AtomicPush(ref UIntPtr head, PageHeader *page) { AtomicPushChain(ref head, page, page); } /// /// Atomically remove a value from the linked list. Returns null if the /// list is empty. /// private static UIntPtr AtomicPop(ref UIntPtr head) { UIntPtr oldHead; PageHeader *oldPage; UIntPtr newHead; do { oldHead = head; if (oldHead == UIntPtr.Zero) { // Empty list. return UIntPtr.Zero; } oldPage = (PageHeader*) oldHead; newHead = oldPage->nextPage; } while(oldHead != Interlocked.CompareExchange (ref head, newHead, oldHead)); oldPage->nextPage = UIntPtr.Zero; return oldHead; } /// /// Steal an entire list. /// private static UIntPtr AtomicPopChain(ref UIntPtr head) { return Interlocked.Exchange(ref head, UIntPtr.Zero); } /// /// Push a whole chain onto a list. /// private static void AtomicPushChain(ref UIntPtr head, PageHeader *chainHead, PageHeader *chainTail) { UIntPtr oldHead; UIntPtr newHead = new UIntPtr(chainHead); do { oldHead = head; chainTail->nextPage = oldHead; } while (oldHead != Interlocked.CompareExchange (ref head, newHead, oldHead)); } /// /// This struct represents the header data stored in each /// small object page. /// /// BUGBUG: Not space efficient. /// private struct PageHeader { internal const int SIZEOF = 16; /// /// The next page in the linked list. /// internal UIntPtr nextPage; /// /// The head of the free list for this page. This is not /// used when a page is assigned to a thread. /// internal UIntPtr freeList; /// /// The cell size for objects in this page. /// internal UIntPtr cellSize; /// /// The number of cells that have been freed. This is used /// for accounting purposes. /// internal int freeCount; } #endregion #region Local (Safe from the context of owner thread) /// /// This is a thread's local free list for each size class. /// [RequiredByBartok] private UIntPtr[] freeList; /// /// This is a thread's local set of pages for each size class. /// private PageHeader*[] localPages; [ManualRefCounts] internal static UIntPtr Allocate(Thread thread, UIntPtr bytes, uint alignment) { SegregatedFreeListThread mixinThread = MixinThread(thread); return mixinThread.segregatedFreeList.Allocate(bytes, alignment, thread); } [ManualRefCounts] internal UIntPtr Allocate(UIntPtr bytes, uint alignment, Thread thread) { UIntPtr resultAddr = this.AllocateFast(bytes, alignment); if (resultAddr == UIntPtr.Zero) { resultAddr = this.AllocateSlow(bytes, alignment, thread); } return resultAddr; } [Inline] [ManualRefCounts] internal static UIntPtr AllocateFast(Thread thread, UIntPtr bytes, uint alignment) { SegregatedFreeListThread mixinThread = MixinThread(thread); return mixinThread.segregatedFreeList.AllocateFast(bytes, alignment); } [RequiredByBartok] [Inline] [DisableBoundsChecks] public static unsafe Object CompilerAllocateMarkSweep (VTable vtable, Thread currentThread, UIntPtr bytes, uint alignment) { VTable.Assert((alignment == 4) || ((alignment == 8) && (UIntPtr.Size == 8)) || ((alignment == 8) && (PreHeader.Size == 4)), "Unsupported object layout"); VTable.Assert(UIntPtr.Size == PreHeader.Size, "Unsupported preheader size"); VTable.Assert (Util.IsAligned((uint) PreHeader.Size + (uint)PostHeader.Size, alignment), "Unsupported header sizes"); VTable.Assert(bytes < LARGE_OBJECT_THRESHOLD, "CompilerAllocate called for large object"); // Room to ensure alignment bool alignRequired = (alignment > UIntPtr.Size); if (alignRequired) { bytes = bytes + alignment - UIntPtr.Size; } int sizeClass = GetSizeClass(bytes); SegregatedFreeListThread mixinThread = MixinThread(currentThread); UIntPtr region = mixinThread.segregatedFreeList.freeList[sizeClass]; if (region != UIntPtr.Zero) { mixinThread.segregatedFreeList.freeList[sizeClass] = *(UIntPtr *)region; // Zero out the free list data structure. However, if the // vtable is at offset zero, then we're going to overwrite it // anyway. (and the optimizer does not see this through the // unmanaged/managed conversion) if (Magic.OffsetOfVTable != UIntPtr.Zero) { *(UIntPtr *)region = UIntPtr.Zero; } UIntPtr objAddr = region; if((alignment == 8) && (UIntPtr.Size == 4)) { // Since 'objAddr' will be the actual object reference, we // want to misalign it here so that the object payload will // be aligned. (We know that PostHeader.Size & 8 == 4 // because the PreHeader is 4 and the sum of the PreHeader // and PostHeader sizes is a multiple of alignment (8)) // Store alignment token at objAddr. This will be where an // alignment token should go if it is required... Allocator.WriteAlignment(objAddr); // ... (align if necessary) ... objAddr = Util.Align(objAddr, (UIntPtr) alignment); // ... or where the object header will be if alignment was // not necessary. This code zeroes the object header // regardless and avoids a branch in this fast path. *(UIntPtr *)objAddr = UIntPtr.Zero; // Finally misalign 'objAddr' objAddr += PreHeader.Size; } Object obj = Magic.fromAddress(objAddr); obj.vtable = vtable; return obj; } return GC.AllocateObjectNoInline(vtable, currentThread); } [Inline] [ManualRefCounts] private UIntPtr AllocateFast(UIntPtr bytes, uint alignment) { // Room to ensure alignment bool alignRequired = (alignment > UIntPtr.Size); if (alignRequired) { bytes = bytes + alignment - UIntPtr.Size; } // Is this a large object? if (!(bytes < LARGE_OBJECT_THRESHOLD)) { return UIntPtr.Zero; } int sizeClass = GetSizeClass(bytes); UIntPtr region = AllocateSmallFast(sizeClass); if (region == UIntPtr.Zero) { return UIntPtr.Zero; } else { UIntPtr resultAddr = Allocator.AlignedObjectPtr(region, alignment); return resultAddr; } } [Inline] [ManualRefCounts] internal static UIntPtr AllocateSlow(Thread thread, UIntPtr bytes, uint alignment) { SegregatedFreeListThread mixinThread = MixinThread(thread); return mixinThread.segregatedFreeList.AllocateSlow(bytes, alignment, thread); } [NoInline] [ManualRefCounts] private UIntPtr AllocateSlow(UIntPtr bytes, uint alignment, Thread currentThread) { // Room to ensure alignment bool alignRequired = (alignment > UIntPtr.Size); if (alignRequired) { bytes = bytes + alignment - UIntPtr.Size; } // Is this a large object? if (!(bytes < LARGE_OBJECT_THRESHOLD)) { return AllocateLarge(alignment, bytes, currentThread); } int sizeClass = GetSizeClass(bytes); UIntPtr region = AllocateSmall(sizeClass, currentThread); UIntPtr resultAddr = Allocator.AlignedObjectPtr(region, alignment); return resultAddr; } /// /// Used to attempt to spread large objects across pages to avoid /// higher cache conflicts on low page addresses. /// private static int largeOffset; /// /// Allocate an object of a specified size class from the /// thread's local block. /// [Inline] [ManualRefCounts] private UIntPtr AllocateSmall(int sizeClass, Thread currentThread) { UIntPtr region = freeList[sizeClass]; if (region != UIntPtr.Zero) { freeList[sizeClass] = *(UIntPtr*)region; *(UIntPtr*)region = UIntPtr.Zero; return region; } else { return AllocateSmallSlow(sizeClass, currentThread); } } [Inline] private UIntPtr AllocateSmallFast(int sizeClass) { UIntPtr region = freeList[sizeClass]; if (region != UIntPtr.Zero) { freeList[sizeClass] = *(UIntPtr*)region; *(UIntPtr*)region = UIntPtr.Zero; return region; } else { return UIntPtr.Zero; } } /// /// Get a new page and allocate into it. /// /// /// [ManualRefCounts] private UIntPtr AllocateSmallSlow(int sizeClass, Thread currentThread) { // Return our old page if (localPages[sizeClass] != null) { ReleaseLocalPage(sizeClass); } // Get a new one. localPages[sizeClass] = GetLocalPage(sizeClass, currentThread); VTable.Assert(localPages[sizeClass] != null, "no local page"); // Read (and then zero) the free list. freeList[sizeClass] = Interlocked.Exchange (ref localPages[sizeClass]->freeList, UIntPtr.Zero); VTable.Assert(freeList[sizeClass] != UIntPtr.Zero, "GetLocalPage returned empty page"); // Allocate off the free list return AllocateSmallFast(sizeClass); } /// /// Release a local allocation page into the pool of consumed pages. /// [ManualRefCounts] private void ReleaseLocalPage(int sizeClass) { PageHeader *page = localPages[sizeClass]; // Prepare the page to be released if (freeList[sizeClass] != UIntPtr.Zero) { // We are releasing the page 'early'. UIntPtr pageFreeList = freeList[sizeClass]; freeList[sizeClass] = UIntPtr.Zero; // Save our local free list as the page's free list UIntPtr oldFreeList = Interlocked.CompareExchange (ref page->freeList, pageFreeList, UIntPtr.Zero); // Count the reclaimed cells. int freeCount = 0; UIntPtr next = pageFreeList; while (next != UIntPtr.Zero) { next = *(UIntPtr*)next; freeCount++; } SubSmallBytes(new UIntPtr((uint)freeCount * (uint)page->cellSize)); if (oldFreeList != UIntPtr.Zero) { // Page already had a free list, follow it to the end. while(*(UIntPtr*)oldFreeList != UIntPtr.Zero) { oldFreeList = *(UIntPtr*)oldFreeList; } // And stitch the free lists together *(UIntPtr*)oldFreeList = pageFreeList; } // Set the reclaimed cells. int old; do { old = page->freeCount; } while (old != Interlocked.CompareExchange (ref page->freeCount, old + freeCount, old)); } // Atomically insert the page in the globalPages queue. AtomicPush(ref globalPages[sizeClass], page); } /// /// Either reuse an existing or make a new page to allocate into. /// [ManualRefCounts] private PageHeader * GetLocalPage(int sizeClass, Thread currentThread) { GC.CheckForNeededGCWork(currentThread); UIntPtr page = AtomicPop(ref globalFreePages[sizeClass]); if (page != UIntPtr.Zero) { // We got the page PageHeader *pageHeader = (PageHeader*) page; VTable.Assert(pageHeader->freeCount > 0, "empty FreePage"); AddSmallBytes(new UIntPtr((uint)pageHeader->freeCount * (uint)pageHeader->cellSize)); pageHeader->freeCount = 0; return pageHeader; } // Create a new local page. return NewLocalPage(sizeClass, currentThread); } /// /// Create a new page to allocate into /// [ManualRefCounts] private PageHeader * NewLocalPage(int sizeClass, Thread currentThread) { VTable.Assert(sizeClass > 0, "non-positive sizeClass"); bool fCleanPages = true; UIntPtr page = PageManager.EnsurePages(currentThread, (UIntPtr) 1, INIT_PAGE, ref fCleanPages); AddSmallPage(); UIntPtr pageAddr = PageTable.PageAddr(page); PageTable.SetExtra(page, (short) sizeClass); PageHeader *pageHeader = (PageHeader *) pageAddr; // Set up the free list of free slots UIntPtr stride = GetCellSize(sizeClass); VTable.Assert(stride != UIntPtr.Zero, "Zero Stride"); pageHeader->cellSize = stride; UIntPtr cursor = pageAddr + PageHeader.SIZEOF + PreHeader.Size; pageHeader->freeList = cursor; UIntPtr limit = PageTable.PageAddr(page+1) - stride + PreHeader.Size; UIntPtr nextAddr = cursor + stride; while (nextAddr <= limit) { *(UIntPtr*)cursor = nextAddr; cursor = nextAddr; nextAddr = cursor + stride; } PageTable.SetType(page, SMALL_OBJ_PAGE); return pageHeader; } #endregion } }