/*******************************************************************/ /* 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.Threading; using System.Runtime.CompilerServices; internal unsafe class SequentialStoreBuffer : RememberedSet { internal static SequentialStoreBuffer instance; private struct ThreadLocal { internal UIntPtr overflowValue; [AccessedByRuntime("referenced from halforgc.asm")] internal UIntPtr *cursor; [AccessedByRuntime("referenced from halforgc.asm")] internal UIntPtr *limit; } [MixinConditional("SSB")] [MixinConditional("AllThreadMixins")] [Mixin(typeof(Thread))] private class SequentialStoreBufferThread : Object { [AccessedByRuntime("referenced from brtforgc.asm")] internal ThreadLocal ssb; } private static SequentialStoreBufferThread MixinThread(Thread t) { return (SequentialStoreBufferThread) (Object) t; } internal static void Initialize() { Initialize(128, PageTable.PageSize); } internal static void Initialize(int count, UIntPtr size) { writeBufferSize = ((int) size * count) / UIntPtr.Size; chunkSize = (int) size; UIntPtr memorySize = (UIntPtr) UIntPtr.Size * (uint) writeBufferSize; writeBuffer = (UIntPtr *) PageManager.AllocateNonheapMemory(null, memorySize); VTable.Assert(writeBuffer != null); AdjustChunkSize(1); SequentialStoreBuffer.instance = (SequentialStoreBuffer) BootstrapMemory.Allocate(typeof(SequentialStoreBuffer)); } [Inline] internal override void RecordReference(ref Object reference, Object toObj) { Record((UIntPtr) Magic.toPointer(ref reference)); } [Inline] internal override void RecordReference(UIntPtr* fromAddr, Object toObj) { Record((UIntPtr) fromAddr); } [Inline] internal override void RecordClonedObject(Object clonedObject) { Record(Magic.addressOf(clonedObject) + 1); } internal override void Clean() { for (int i = 0; i < Thread.threadTable.Length; i++) { Thread t = Thread.threadTable[i]; if (t != null) { Clean(t); } } } internal override void Clean(Thread thread) { UIntPtr wbCursor = (UIntPtr) MixinThread(thread).ssb.cursor; UIntPtr wbLimit = (UIntPtr) MixinThread(thread).ssb.limit; if (wbCursor < wbLimit) { VTable.Assert(wbCursor != UIntPtr.Zero); Util.MemClear(wbCursor, wbLimit - wbCursor); } } [RequiredByBartok] [Inline] public static void Record(UIntPtr value) { Thread currentThread = Thread.CurrentThread; if (MixinThread(currentThread).ssb.cursor == MixinThread(currentThread).ssb.limit) { RecordSlow(currentThread, value); } else { *MixinThread(currentThread).ssb.cursor = value; MixinThread(currentThread).ssb.cursor++; } } private static void RecordSlow(Thread currentThread, UIntPtr value) { // Try to acquire a new chunk of the store buffer while (writeBufferIndex < writeBufferSize) { int oldIndex = writeBufferIndex; int newIndex = oldIndex + chunkSize; if (Interlocked.CompareExchange(ref writeBufferIndex, newIndex, oldIndex) == oldIndex) { // We secured a new block of write buffer for this thread UIntPtr *cursor = writeBuffer + oldIndex; *cursor = value; cursor++; MixinThread(currentThread).ssb.cursor = cursor; MixinThread(currentThread).ssb.limit = writeBuffer + newIndex; return; } } // We have run out of write barrier space if (StopTheWorldCollector.CurrentPhase == StopTheWorldCollector.STWPhase.SingleThreaded) { VTable.DebugBreak(); } VTable.Assert(MixinThread(currentThread).ssb.overflowValue == UIntPtr.Zero); MixinThread(currentThread).ssb.overflowValue = value; GC.InvokeCollection(currentThread); while (MixinThread(currentThread).ssb.overflowValue != UIntPtr.Zero) { // Another thread must have taken charge of performing the // collection and hadn't yet assigned a GCRequest to the // current thread. Give the other thread a chance to do // some work before we try invoking the collector again. Thread.Yield(); GC.InvokeCollection(currentThread); } } internal override void Uniquify() { // Sort the write buffer. // TODO: Would like sort that is in-place, O(n lg n) worst-case, and // O(n) when nearly-sorted. bool changed = true; for(int max = writeBufferIndex; changed; --max) { changed = false; for(int i = 1; i < max; ++i) { if(writeBuffer[i-1] > writeBuffer[i]) { changed = true; UIntPtr t = writeBuffer[i-1]; writeBuffer[i-1] = writeBuffer[i]; writeBuffer[i] = t; } } } // Remove duplicates int dest = 0; UIntPtr last = UIntPtr.Zero; for (int i = 0; i < writeBufferIndex; i++) { UIntPtr current = *(writeBuffer + i); if (current != last) { VTable.Assert(current > last); *(writeBuffer + dest) = current; dest++; last = current; if ((current & 1) != 0) { // The entire object is marked, skip interior addresses UIntPtr objPtr = current - 1; VTable vtable = Magic.fromAddress(objPtr).vtable; UIntPtr size = ObjectLayout.ObjectSize(objPtr, vtable); VTable.Assert(size > 0); UIntPtr objLimit = objPtr + size; i++; while (i < writeBufferIndex && *(writeBuffer + i) < objLimit) { i++; } i--; } } } writeBufferIndex = dest; // Remove duplicates hiding in the overflow slot in thread objects! for (int i = 0; i < Thread.threadTable.Length; i++) { Thread t = Thread.threadTable[i]; if (t != null) { UIntPtr overflowPtr = MixinThread(t).ssb.overflowValue; if (overflowPtr != UIntPtr.Zero) { int left = 0; int right = writeBufferIndex; while (left < right - 1) { int mid = (left + right) / 2; if (*(writeBuffer + mid) <= overflowPtr) { left = mid; } else { right = mid; } } UIntPtr foundPtr = *(writeBuffer + left); if (overflowPtr == foundPtr) { // Found an exact duplicate MixinThread(t).ssb.overflowValue = UIntPtr.Zero; continue; } else if ((foundPtr & 1) != 0) { UIntPtr objAddr = foundPtr - 1; VTable vtable = Magic.fromAddress(objAddr).vtable; UIntPtr size = ObjectLayout.ObjectSize(objAddr, vtable); if (overflowPtr < objAddr + size) { // found a pointer into a checked object MixinThread(t).ssb.overflowValue = UIntPtr.Zero; } } else if ((overflowPtr & 1) != 0) { UIntPtr objAddr = overflowPtr - 1; VTable vtable = Magic.fromAddress(objAddr).vtable; UIntPtr size = ObjectLayout.ObjectSize(objAddr, vtable); UIntPtr objLimit = objAddr + size; int skipCount = 0; int probe = left + 1; while (probe < writeBufferIndex && *(writeBuffer + probe) < objLimit) { probe++; skipCount++; } if (skipCount > 0) { while (probe < writeBufferIndex) { *(writeBuffer + left) = *(writeBuffer + probe); left++; probe++; } } } } } } } // REVIEW: This is marked inline because the adaptive collector calls // it with two different NonNullReferenceVisitors and we need to avoid // the virtual call. We really want to specialize the method. [Inline] internal override void Scan(NonNullReferenceVisitor ptrVisitor, PageType genToCollect) { for (int i = 0; i < writeBufferIndex; i++) { UIntPtr tag = *(writeBuffer + i); if (tag != UIntPtr.Zero) { if ((tag & 0x1) == 0) { UIntPtr *fieldLoc = (UIntPtr *) tag; if (*fieldLoc != UIntPtr.Zero) { ptrVisitor.Visit(fieldLoc); } } else { UIntPtr objectPtr = (UIntPtr) (tag - 1); Object obj = Magic.fromAddress(objectPtr); ptrVisitor.VisitReferenceFields(obj); } } } // Handle the overflow in the thread objects for (int i = 0; i < Thread.threadTable.Length; i++) { Thread t = Thread.threadTable[i]; if (t != null) { UIntPtr tag = MixinThread(t).ssb.overflowValue; if (tag != UIntPtr.Zero) { if ((tag & 0x1) == 0) { UIntPtr *fieldLoc = (UIntPtr *) tag; if (*fieldLoc != UIntPtr.Zero) { ptrVisitor.Visit(fieldLoc); } } else { UIntPtr objectPtr = (UIntPtr) (tag - 1); Object obj = Magic.fromAddress(objectPtr); ptrVisitor.VisitReferenceFields(obj); } } } } } internal override void Reset() { int threadCount = 0; for (int i = 0; i < Thread.threadTable.Length; i++) { Thread t = Thread.threadTable[i]; if (t != null) { threadCount++; MixinThread(t).ssb.overflowValue = UIntPtr.Zero; MixinThread(t).ssb.cursor = null; MixinThread(t).ssb.limit = null; } } AdjustChunkSize(threadCount); } private static void AdjustChunkSize(int threadCount) { writeBufferIndex = 0; // Adjust the size of the write barrier chunks int testSize = writeBufferSize; while (threadCount > 0) { threadCount >>= 1; testSize >>= 1; } if (testSize < chunkSize) { VTable.Assert(testSize > 0); chunkSize = testSize; } } internal static int chunkSize; internal static UIntPtr *writeBuffer; internal static int writeBufferIndex; internal static int writeBufferSize; } }